Achill Gilgenast 8c5afc5ee6
community/py3-libpass: new aport
https://github.com/notypecheck/passlib
Maintained fork of py3-passlib, a password hashing library for Python

Dependency of radicale
2026-01-10 22:59:25 +01:00

404 lines
15 KiB
Diff

From 68e583357a29dc5d93afa66c1077c73d5168afa3 Mon Sep 17 00:00:00 2001
From: notypecheck <50728601+notypecheck@users.noreply.github.com>
Date: Sat, 18 Oct 2025 12:01:08 +0300
Subject: [PATCH] test: fix bcrypt tests
---
tests/conftest.py | 24 ++++
tests/test_handlers_bcrypt.py | 254 ++++++++++++++++++++--------------
tests/test_handlers_django.py | 14 +-
tests/utils.py | 7 +-
4 files changed, 187 insertions(+), 112 deletions(-)
create mode 100644 tests/conftest.py
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..4c68161
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,24 @@
+from typing import Any
+
+import pytest
+
+from passlib.handlers.bcrypt import bcrypt
+
+
+@pytest.fixture(scope="session")
+def bcrypt_backend_raises_on_wraparound() -> bool:
+ try:
+ bcrypt.hash(secret="abc" * 100)
+ except ValueError:
+ return True
+ return False
+
+
+@pytest.fixture(scope="class")
+def bcrypt_backend_raises_on_wraparound_unittest(
+ request: Any,
+ bcrypt_backend_raises_on_wraparound: bool,
+):
+ request.cls.bcrypt_backend_raises_on_wraparound = (
+ bcrypt_backend_raises_on_wraparound
+ )
diff --git a/tests/test_handlers_bcrypt.py b/tests/test_handlers_bcrypt.py
index b49c1fb..18ff632 100644
--- a/tests/test_handlers_bcrypt.py
+++ b/tests/test_handlers_bcrypt.py
@@ -6,14 +6,10 @@
import pytest
from passlib import hash
-from passlib.handlers.bcrypt import (
- IDENT_2,
- IDENT_2A,
- IDENT_2B,
- IDENT_2X,
- IDENT_2Y,
-)
+from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y
+from passlib.handlers.bcrypt import bcrypt as bcrypt_handler
from passlib.utils import repeat_string, to_bytes
+from passlib.utils.handlers import GenericHandler
from tests.test_handlers import UPASS_TABLE
from tests.utils import TEST_MODE, HandlerCase
from tests.utils_ import no_warnings
@@ -26,101 +22,7 @@ class _bcrypt_test(HandlerCase):
reduce_default_rounds = True
fuzz_salts_need_bcrypt_repair = True
- known_correct_hashes = [
- #
- # from JTR 1.7.9
- #
- ("U*U*U*U*", "$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O"),
- ("U*U***U", "$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq"),
- ("U*U***U*", "$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi"),
- ("*U*U*U*U", "$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2"),
- ("", "$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO"),
- #
- # test vectors from http://www.openwall.com/crypt v1.2
- # note that this omits any hashes that depend on crypt_blowfish's
- # various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password,
- # and any 2x hashes); and only contain hashes which are correct
- # under both crypt_blowfish 1.2 AND OpenBSD.
- #
- ("U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"),
- ("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"),
- ("U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"),
- ("", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"),
- (
- "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789chars after 72 are ignored",
- "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui",
- ),
- (b"\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"),
- (
- b"\xff\xa3345",
- "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
- ),
- (b"\xa3ab", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"),
- (
- b"\xaa" * 72 + b"chars after 72 are ignored as usual",
- "$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6",
- ),
- (
- b"\xaa\x55" * 36,
- "$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy",
- ),
- (
- b"\x55\xaa\xff" * 24,
- "$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe",
- ),
- # keeping one of their 2y tests, because we are supporting that.
- (b"\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"),
- #
- # 8bit bug (fixed in 2y/2b)
- #
- # NOTE: see assert_lacks_8bit_bug() for origins of this test vector.
- (b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"),
- #
- # bsd wraparound bug (fixed in 2b)
- #
- # NOTE: if backend is vulnerable, password will hash the same as '0'*72
- # ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"),
- # rather than same as ("0123456789"*8)[:72]
- # 255 should be sufficient, but checking
- (
- ("0123456789" * 26)[:254],
- "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
- ),
- (
- ("0123456789" * 26)[:255],
- "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
- ),
- (
- ("0123456789" * 26)[:256],
- "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
- ),
- (
- ("0123456789" * 26)[:257],
- "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
- ),
- #
- # from py-bcrypt tests
- #
- ("", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s."),
- ("a", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u"),
- ("abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi"),
- (
- "abcdefghijklmnopqrstuvwxyz",
- "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq",
- ),
- (
- "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
- "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS",
- ),
- #
- # custom test vectors
- #
- # ensures utf-8 used for unicode
- (UPASS_TABLE, "$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG"),
- # ensure 2b support
- (UPASS_TABLE, "$2b$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG"),
- ]
+ known_correct_hashes = []
if TEST_MODE("full"):
#
@@ -356,8 +258,153 @@ def test_needs_update_w_padding(self):
assert not bcrypt.needs_update(GOOD1)
+@pytest.mark.usefixtures("bcrypt_backend_raises_on_wraparound_unittest")
+class bcrypt_test(_bcrypt_test):
+ __test__ = False
+
+ def test_secret_with_truncate_size(self) -> None:
+ if not self.bcrypt_backend_raises_on_wraparound: # type: ignore[attr-defined]
+ super().test_secret_with_truncate_size()
+
+ def test_77_fuzz_input(self, threaded: bool = False) -> None:
+ if not self.bcrypt_backend_raises_on_wraparound: # type: ignore[attr-defined]
+ super().test_77_fuzz_input(threaded=threaded)
+
+
# create test cases for specific backends
-bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt")
+bcrypt_bcrypt_test = bcrypt_test.create_backend_case("bcrypt")
+
+
+@pytest.fixture(scope="session")
+def handler() -> type[GenericHandler]:
+ return bcrypt_handler
+
+
+@pytest.mark.parametrize(
+ ("secret", "hash"),
+ [
+ #
+ # from JTR 1.7.9
+ #
+ ("U*U*U*U*", "$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O"),
+ ("U*U***U", "$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq"),
+ ("U*U***U*", "$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi"),
+ ("*U*U*U*U", "$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2"),
+ ("", "$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO"),
+ #
+ # test vectors from http://www.openwall.com/crypt v1.2
+ # note that this omits any hashes that depend on crypt_blowfish's
+ # various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password,
+ # and any 2x hashes); and only contain hashes which are correct
+ # under both crypt_blowfish 1.2 AND OpenBSD.
+ #
+ ("U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"),
+ ("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"),
+ ("U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"),
+ ("", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"),
+ (
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789chars after 72 are ignored",
+ "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui",
+ ),
+ (b"\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"),
+ (
+ b"\xff\xa3345",
+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
+ ),
+ (b"\xa3ab", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"),
+ (
+ b"\xaa" * 72 + b"chars after 72 are ignored as usual",
+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6",
+ ),
+ (
+ b"\xaa\x55" * 36,
+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy",
+ ),
+ (
+ b"\x55\xaa\xff" * 24,
+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe",
+ ),
+ # keeping one of their 2y tests, because we are supporting that.
+ (b"\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"),
+ #
+ # 8bit bug (fixed in 2y/2b)
+ #
+ # NOTE: see assert_lacks_8bit_bug() for origins of this test vector.
+ (b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"),
+ #
+ # bsd wraparound bug (fixed in 2b)
+ #
+ # NOTE: if backend is vulnerable, password will hash the same as '0'*72
+ # ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"),
+ # rather than same as ("0123456789"*8)[:72]
+ # 255 should be sufficient, but checking
+ (
+ ("0123456789" * 26)[:254],
+ "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
+ ),
+ (
+ ("0123456789" * 26)[:255],
+ "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
+ ),
+ (
+ ("0123456789" * 26)[:256],
+ "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
+ ),
+ (
+ ("0123456789" * 26)[:257],
+ "$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi",
+ ),
+ #
+ # from py-bcrypt tests
+ #
+ ("", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s."),
+ ("a", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u"),
+ ("abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi"),
+ (
+ "abcdefghijklmnopqrstuvwxyz",
+ "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq",
+ ),
+ (
+ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
+ "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS",
+ ),
+ #
+ # custom test vectors
+ #
+ # ensures utf-8 used for unicode
+ (UPASS_TABLE, "$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG"),
+ # ensure 2b support
+ (UPASS_TABLE, "$2b$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG"),
+ ],
+)
+def test_known_hashes(
+ secret: str,
+ hash: str,
+ bcrypt_backend_raises_on_wraparound: bool,
+ handler: GenericHandler,
+) -> None:
+ assert handler.truncate_size
+
+ if bcrypt_backend_raises_on_wraparound and len(secret) > handler.truncate_size:
+ return
+
+ assert handler.verify(secret=secret, hash=hash)
+
+
+def test_with_truncate_size(
+ handler: GenericHandler,
+ bcrypt_backend_raises_on_wraparound: bool,
+) -> None:
+ if bcrypt_backend_raises_on_wraparound:
+ return
+
+ assert handler.truncate_size
+
+ long_secret = "abc" * handler.truncate_size
+
+ hashed = handler.hash(secret=long_secret)
+ assert handler.verify(secret=long_secret[: handler.truncate_size], hash=hashed)
class _bcrypt_sha256_test(HandlerCase):
@@ -591,5 +638,4 @@ def test_calc_digest_v2(self):
assert result == bcrypt_digest
-# create test cases for specific backends
bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt")
diff --git a/tests/test_handlers_django.py b/tests/test_handlers_django.py
index f40338e..32d25d5 100644
--- a/tests/test_handlers_django.py
+++ b/tests/test_handlers_django.py
@@ -3,6 +3,8 @@
import warnings
from unittest import SkipTest, skipUnless
+import pytest
+
from passlib import hash
from passlib.utils import repeat_string
from tests.test_ext_django import (
@@ -14,9 +16,6 @@
from tests.test_handlers_argon2 import _base_argon2_test
from tests.utils import HandlerCase, TestCase
-# module
-
-
# standard string django uses
UPASS_LETMEIN = "l\xe8tmein"
@@ -280,6 +279,7 @@ class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper):
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
+@pytest.mark.usefixtures("bcrypt_backend_raises_on_wraparound_unittest")
class django_bcrypt_test(HandlerCase, _DjangoHelper):
"""test django_bcrypt"""
@@ -321,6 +321,14 @@ def random_ident(self):
# XXX: enable this to check 2a / 2b?
return None
+ def test_secret_with_truncate_size(self):
+ if not self.bcrypt_backend_raises_on_wraparound:
+ super().test_truncate_error_setting()
+
+ def test_77_fuzz_input(self, threaded: bool = False) -> None:
+ if not self.bcrypt_backend_raises_on_wraparound: # type: ignore[attr-defined]
+ super().test_77_fuzz_input(threaded=threaded)
+
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper):
diff --git a/tests/utils.py b/tests/utils.py
index b08847d..c228384 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -2059,13 +2059,10 @@ def test_secret_wo_truncate_size(self):
alt_secret = secret[:-1] + alt
assert not self.do_verify(alt_secret, hash), "full password not used in digest"
- def test_secret_w_truncate_size(self):
+ def test_secret_with_truncate_size(self):
"""
test password size limits raise truncate_error (if appropriate)
"""
- # --------------------------------------------------
- # check if test is applicable
- # --------------------------------------------------
handler = self.handler
truncate_size = handler.truncate_size
if not truncate_size:
@@ -2075,7 +2072,7 @@ def test_secret_w_truncate_size(self):
# setup vars
# --------------------------------------------------
# try to get versions w/ and w/o truncate_error set.
- # set to None if policy isn't configruable
+ # set to None if policy isn't configurable
size_error_type = exc.PasswordSizeError
if "truncate_error" in handler.setting_kwds:
without_error = handler.using(truncate_error=False)