Загрузить файлы в «venv/Lib/site-packages/passlib/tests»

This commit is contained in:
2026-07-02 18:51:54 +00:00
parent ac6973375d
commit 56b5e37802
5 changed files with 2149 additions and 0 deletions

View File

@@ -0,0 +1,688 @@
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
#=============================================================================
# imports
#=============================================================================
from __future__ import with_statement
# core
import logging; log = logging.getLogger(__name__)
import os
import warnings
# site
# pkg
from passlib import hash
from passlib.handlers.bcrypt import IDENT_2, IDENT_2X
from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input
from passlib.utils.compat import irange, PY3
from passlib.tests.utils import HandlerCase, TEST_MODE
from passlib.tests.test_handlers import UPASS_TABLE
# module
#=============================================================================
# bcrypt
#=============================================================================
class _bcrypt_test(HandlerCase):
"""base for BCrypt test cases"""
handler = hash.bcrypt
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'),
]
if TEST_MODE("full"):
#
# add some extra tests related to 2/2a
#
CONFIG_2 = '$2$05$' + '.'*22
CONFIG_A = '$2a$05$' + '.'*22
known_correct_hashes.extend([
("", CONFIG_2 + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'),
("", CONFIG_A + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'),
("abc", CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
("abc", CONFIG_A + 'ev6gDwpVye3oMCUpLY85aTpfBNHD0Ga'),
("abc"*23, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
("abc"*23, CONFIG_A + '2kIdfSj/4/R/Q6n847VTvc68BXiRYZC'),
("abc"*24, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
("abc"*24, CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
("abc"*24+'x', CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
("abc"*24+'x', CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
])
known_correct_configs = [
('$2a$04$uM6csdM8R9SXTex/gbTaye', UPASS_TABLE,
'$2a$04$uM6csdM8R9SXTex/gbTayezuvzFEufYGd2uB6of7qScLjQ4GwcD4G'),
]
known_unidentified_hashes = [
# invalid minor version
"$2f$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
"$2`$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
]
known_malformed_hashes = [
# bad char in otherwise correct hash
# \/
"$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
# unsupported (but recognized) minor version
"$2x$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
# rounds not zero-padded (py-bcrypt rejects this, therefore so do we)
'$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'
# NOTE: salts with padding bits set are technically malformed,
# but we can reliably correct & issue a warning for that.
]
platform_crypt_support = [
("freedbsd|openbsd|netbsd", True),
("darwin", False),
("linux", None), # may be present via addon, e.g. debian's libpam-unix2
("solaris", None), # depends on system policy
]
#===================================================================
# override some methods
#===================================================================
def setUp(self):
# ensure builtin is enabled for duration of test.
if TEST_MODE("full") and self.backend == "builtin":
key = "PASSLIB_BUILTIN_BCRYPT"
orig = os.environ.get(key)
if orig:
self.addCleanup(os.environ.__setitem__, key, orig)
else:
self.addCleanup(os.environ.__delitem__, key)
os.environ[key] = "true"
super(_bcrypt_test, self).setUp()
# silence this warning, will come up a bunch during testing of old 2a hashes.
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
def populate_settings(self, kwds):
# builtin is still just way too slow.
if self.backend == "builtin":
kwds.setdefault("rounds", 4)
super(_bcrypt_test, self).populate_settings(kwds)
#===================================================================
# fuzz testing
#===================================================================
def crypt_supports_variant(self, hash):
"""check if OS crypt is expected to support given ident"""
from passlib.handlers.bcrypt import bcrypt, IDENT_2X, IDENT_2Y
from passlib.utils import safe_crypt
ident = bcrypt.from_string(hash)
return (safe_crypt("test", ident + "04$5BJqKfqMQvV7nS.yUguNcu") or "").startswith(ident)
fuzz_verifiers = HandlerCase.fuzz_verifiers + (
"fuzz_verifier_bcrypt",
"fuzz_verifier_pybcrypt",
"fuzz_verifier_bcryptor",
)
def fuzz_verifier_bcrypt(self):
# test against bcrypt, if available
from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, _detect_pybcrypt
from passlib.utils import to_native_str, to_bytes
try:
import bcrypt
except ImportError:
return
if _detect_pybcrypt():
return
def check_bcrypt(secret, hash):
"""bcrypt"""
secret = to_bytes(secret, self.FuzzHashGenerator.password_encoding)
if hash.startswith(IDENT_2B):
# bcrypt <1.1 lacks 2B support
hash = IDENT_2A + hash[4:]
elif hash.startswith(IDENT_2):
# bcrypt doesn't support $2$ hashes; but we can fake it
# using the $2a$ algorithm, by repeating the password until
# it's 72 chars in length.
hash = IDENT_2A + hash[3:]
if secret:
secret = repeat_string(secret, 72)
elif hash.startswith(IDENT_2Y) and bcrypt.__version__ == "3.0.0":
hash = IDENT_2B + hash[4:]
hash = to_bytes(hash)
try:
return bcrypt.hashpw(secret, hash) == hash
except ValueError:
raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret))
return check_bcrypt
def fuzz_verifier_pybcrypt(self):
# test against py-bcrypt, if available
from passlib.handlers.bcrypt import (
IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y,
_PyBcryptBackend,
)
from passlib.utils import to_native_str
loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False)
if not loaded:
return
from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod
lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2
def check_pybcrypt(secret, hash):
"""pybcrypt"""
secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding)
if len(secret) > 200: # vulnerable to wraparound bug
secret = secret[:200]
if hash.startswith((IDENT_2B, IDENT_2Y)):
hash = IDENT_2A + hash[4:]
try:
if lock:
with lock:
return bcrypt_mod.hashpw(secret, hash) == hash
else:
return bcrypt_mod.hashpw(secret, hash) == hash
except ValueError:
raise ValueError("py-bcrypt rejected hash: %r" % (hash,))
return check_pybcrypt
def fuzz_verifier_bcryptor(self):
# test against bcryptor if available
from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y, IDENT_2B
from passlib.utils import to_native_str
try:
from bcryptor.engine import Engine
except ImportError:
return
def check_bcryptor(secret, hash):
"""bcryptor"""
secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding)
if hash.startswith((IDENT_2B, IDENT_2Y)):
hash = IDENT_2A + hash[4:]
elif hash.startswith(IDENT_2):
# bcryptor doesn't support $2$ hashes; but we can fake it
# using the $2a$ algorithm, by repeating the password until
# it's 72 chars in length.
hash = IDENT_2A + hash[3:]
if secret:
secret = repeat_string(secret, 72)
return Engine(False).hash_key(secret, hash) == hash
return check_bcryptor
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def generate(self):
opts = super(_bcrypt_test.FuzzHashGenerator, self).generate()
secret = opts['secret']
other = opts['other']
settings = opts['settings']
ident = settings.get('ident')
if ident == IDENT_2X:
# 2x is just recognized, not supported. don't test with it.
del settings['ident']
elif ident == IDENT_2 and other and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret):
# avoid false failure due to flaw in 0-revision bcrypt:
# repeated strings like 'abc' and 'abcabc' hash identically.
opts['secret'], opts['other'] = self.random_password_pair()
return opts
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(5, 8, 6, 1)
#===================================================================
# custom tests
#===================================================================
known_incorrect_padding = [
# password, bad hash, good hash
# 2 bits of salt padding set
# ("loppux", # \/
# "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C",
# "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C"),
("test", # \/
'$2a$04$oaQbBqq8JnSM1NHRPQGXORY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO',
'$2a$04$oaQbBqq8JnSM1NHRPQGXOOY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO'),
# all 4 bits of salt padding set
# ("Passlib11", # \/
# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK",
# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK"),
("test", # \/
"$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS",
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"),
# bad checksum padding
("test", # \/
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV",
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"),
]
def test_90_bcrypt_padding(self):
"""test passlib correctly handles bcrypt padding bits"""
self.require_TEST_MODE("full")
#
# prevents reccurrence of issue 25 (https://code.google.com/p/passlib/issues/detail?id=25)
# were some unused bits were incorrectly set in bcrypt salt strings.
# (fixed since 1.5.3)
#
bcrypt = self.handler
corr_desc = ".*incorrectly set padding bits"
#
# test hash() / genconfig() don't generate invalid salts anymore
#
def check_padding(hash):
assert hash.startswith(("$2a$", "$2b$")) and len(hash) >= 28, \
"unexpectedly malformed hash: %r" % (hash,)
self.assertTrue(hash[28] in '.Oeu',
"unused bits incorrectly set in hash: %r" % (hash,))
for i in irange(6):
check_padding(bcrypt.genconfig())
for i in irange(3):
check_padding(bcrypt.using(rounds=bcrypt.min_rounds).hash("bob"))
#
# test genconfig() corrects invalid salts & issues warning.
#
with self.assertWarningList(["salt too large", corr_desc]):
hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True)
self.assertEqual(hash, "$2b$05$" + "." * (22 + 31))
#
# test public methods against good & bad hashes
#
samples = self.known_incorrect_padding
for pwd, bad, good in samples:
# make sure genhash() corrects bad configs, leaves good unchanged
with self.assertWarningList([corr_desc]):
self.assertEqual(bcrypt.genhash(pwd, bad), good)
with self.assertWarningList([]):
self.assertEqual(bcrypt.genhash(pwd, good), good)
# make sure verify() works correctly with good & bad hashes
with self.assertWarningList([corr_desc]):
self.assertTrue(bcrypt.verify(pwd, bad))
with self.assertWarningList([]):
self.assertTrue(bcrypt.verify(pwd, good))
# make sure normhash() corrects bad hashes, leaves good unchanged
with self.assertWarningList([corr_desc]):
self.assertEqual(bcrypt.normhash(bad), good)
with self.assertWarningList([]):
self.assertEqual(bcrypt.normhash(good), good)
# make sure normhash() leaves non-bcrypt hashes alone
self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc")
def test_needs_update_w_padding(self):
"""needs_update corrects bcrypt padding"""
# NOTE: see padding test above for details about issue this detects
bcrypt = self.handler.using(rounds=4)
# PASS1 = "test"
# bad contains invalid 'c' char at end of salt:
# \/
BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
self.assertTrue(bcrypt.needs_update(BAD1))
self.assertFalse(bcrypt.needs_update(GOOD1))
#===================================================================
# eoc
#===================================================================
# create test cases for specific backends
bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt")
bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt")
bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor")
class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")):
# os crypt doesn't support non-utf8 secret bytes
known_correct_hashes = [row for row in _bcrypt_test.known_correct_hashes
if is_safe_crypt_input(row[0])]
# os crypt backend doesn't currently implement a per-call fallback if it fails
has_os_crypt_fallback = False
bcrypt_builtin_test = _bcrypt_test.create_backend_case("builtin")
#=============================================================================
# bcrypt
#=============================================================================
class _bcrypt_sha256_test(HandlerCase):
"base for BCrypt-SHA256 test cases"
handler = hash.bcrypt_sha256
reduce_default_rounds = True
forbidden_characters = None
fuzz_salts_need_bcrypt_repair = True
known_correct_hashes = [
#-------------------------------------------------------------------
# custom test vectors for old v1 format
#-------------------------------------------------------------------
# empty
("",
'$bcrypt-sha256$2a,5$E/e/2AOhqM5W/KJTFQzLce$F6dYSxOdAEoJZO2eoHUZWZljW/e0TXO'),
# ascii
("password",
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
# unicode / utf8
(UPASS_TABLE,
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
(UPASS_TABLE.encode("utf-8"),
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
# ensure 2b support
("password",
'$bcrypt-sha256$2b,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
(UPASS_TABLE,
'$bcrypt-sha256$2b,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
(repeat_string("abc123", 72),
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$r/hyEtqJ0teqPEmfTLoZ83ciAI1Q74.'),
(repeat_string("abc123", 72) + "qwr",
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$021KLEif6epjot5yoxk0m8I0929ohEa'),
(repeat_string("abc123", 72) + "xyz",
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$7.1kgpHduMGEjvM3fX6e/QCvfn6OKja'),
#-------------------------------------------------------------------
# custom test vectors for v2 format
# TODO: convert to v2 format
#-------------------------------------------------------------------
# empty
("",
'$bcrypt-sha256$v=2,t=2b,r=5$E/e/2AOhqM5W/KJTFQzLce$WFPIZKtDDTriqWwlmRFfHiOTeheAZWe'),
# ascii
("password",
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
# unicode / utf8
(UPASS_TABLE,
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
(UPASS_TABLE.encode("utf-8"),
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
(repeat_string("abc123", 72),
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zu1cloESVFIOsUIo7fCEgkdHaI9SSue'),
(repeat_string("abc123", 72) + "qwr",
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$CBF9csfEdW68xv3DwE6xSULXMtqEFP.'),
(repeat_string("abc123", 72) + "xyz",
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zC/1UDUG2ofEXB6Onr2vvyFzfhEOS3S'),
]
known_correct_configs =[
# v1
('$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe',
"password", '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
# v2
('$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe',
"password", '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
]
known_malformed_hashes = [
#-------------------------------------------------------------------
# v1 format
#-------------------------------------------------------------------
# bad char in otherwise correct hash
# \/
'$bcrypt-sha256$2a,5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unrecognized bcrypt variant
'$bcrypt-sha256$2c,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unsupported bcrypt variant
'$bcrypt-sha256$2x,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# rounds zero-padded
'$bcrypt-sha256$2a,05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# config string w/ $ added
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$',
#-------------------------------------------------------------------
# v2 format
#-------------------------------------------------------------------
# bad char in otherwise correct hash
# \/
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unsupported version (for this format)
'$bcrypt-sha256$v=1,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unrecognized version
'$bcrypt-sha256$v=3,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unrecognized bcrypt variant
'$bcrypt-sha256$v=2,t=2c,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# unsupported bcrypt variant
'$bcrypt-sha256$v=2,t=2a,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
'$bcrypt-sha256$v=2,t=2x,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# rounds zero-padded
'$bcrypt-sha256$v=2,t=2b,r=05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
# config string w/ $ added
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$',
]
#===================================================================
# override some methods -- cloned from bcrypt
#===================================================================
def setUp(self):
# ensure builtin is enabled for duration of test.
if TEST_MODE("full") and self.backend == "builtin":
key = "PASSLIB_BUILTIN_BCRYPT"
orig = os.environ.get(key)
if orig:
self.addCleanup(os.environ.__setitem__, key, orig)
else:
self.addCleanup(os.environ.__delitem__, key)
os.environ[key] = "enabled"
super(_bcrypt_sha256_test, self).setUp()
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
def populate_settings(self, kwds):
# builtin is still just way too slow.
if self.backend == "builtin":
kwds.setdefault("rounds", 4)
super(_bcrypt_sha256_test, self).populate_settings(kwds)
#===================================================================
# override ident tests for now
#===================================================================
def require_many_idents(self):
raise self.skipTest("multiple idents not supported")
def test_30_HasOneIdent(self):
# forbidding ident keyword, we only support "2b" for now
handler = self.handler
handler(use_defaults=True)
self.assertRaises(ValueError, handler, ident="$2y$", use_defaults=True)
#===================================================================
# fuzz testing -- cloned from bcrypt
#===================================================================
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(5, 8, 6, 1)
def random_ident(self):
return "2b"
#===================================================================
# custom tests
#===================================================================
def test_using_version(self):
# default to v2
handler = self.handler
self.assertEqual(handler.version, 2)
# allow v1 explicitly
subcls = handler.using(version=1)
self.assertEqual(subcls.version, 1)
# forbid unknown ver
self.assertRaises(ValueError, handler.using, version=999)
# allow '2a' only for v1
subcls = handler.using(version=1, ident="2a")
self.assertRaises(ValueError, handler.using, ident="2a")
def test_calc_digest_v2(self):
"""
test digest calc v2 matches bcrypt()
"""
from passlib.hash import bcrypt
from passlib.crypto.digest import compile_hmac
from passlib.utils.binary import b64encode
# manually calc intermediary digest
salt = "nyKYxTAvjmy6lMDYMl11Uu"
secret = "test"
temp_digest = compile_hmac("sha256", salt.encode("ascii"))(secret.encode("ascii"))
temp_digest = b64encode(temp_digest).decode("ascii")
self.assertEqual(temp_digest, "J5TlyIDm+IcSWmKiDJm+MeICndBkFVPn4kKdJW8f+xY=")
# manually final hash from intermediary
# XXX: genhash() could be useful here
bcrypt_digest = bcrypt(ident="2b", salt=salt, rounds=12)._calc_checksum(temp_digest)
self.assertEqual(bcrypt_digest, "M0wE0Ov/9LXoQFCe.jRHu3MSHPF54Ta")
self.assertTrue(bcrypt.verify(temp_digest, "$2b$12$" + salt + bcrypt_digest))
# confirm handler outputs same thing.
# XXX: genhash() could be useful here
result = self.handler(ident="2b", salt=salt, rounds=12)._calc_checksum(secret)
self.assertEqual(result, bcrypt_digest)
#===================================================================
# eoc
#===================================================================
# create test cases for specific backends
bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt")
bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt")
bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor")
class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")):
@classmethod
def _get_safe_crypt_handler_backend(cls):
return bcrypt_os_crypt_test._get_safe_crypt_handler_backend()
has_os_crypt_fallback = False
bcrypt_sha256_builtin_test = _bcrypt_sha256_test.create_backend_case("builtin")
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,457 @@
"""
passlib.tests.test_handlers_cisco - tests for Cisco-specific algorithms
"""
#=============================================================================
# imports
#=============================================================================
from __future__ import absolute_import, division, print_function
# core
import logging
log = logging.getLogger(__name__)
# site
# pkg
from passlib import hash, exc
from passlib.utils.compat import u
from .utils import UserHandlerMixin, HandlerCase, repeat_string
from .test_handlers import UPASS_TABLE
# module
__all__ = [
"cisco_pix_test",
"cisco_asa_test",
"cisco_type7_test",
]
#=============================================================================
# shared code for cisco PIX & ASA
#=============================================================================
class _PixAsaSharedTest(UserHandlerMixin, HandlerCase):
"""
class w/ shared info for PIX & ASA tests.
"""
__unittest_skip = True # for TestCase
requires_user = False # for UserHandlerMixin
#: shared list of hashes which should be identical under pix & asa7
#: (i.e. combined secret + user < 17 bytes)
pix_asa_shared_hashes = [
#
# http://www.perlmonks.org/index.pl?node_id=797623
#
(("cisco", ""), "2KFQnbNIdI.2KYOU"), # confirmed ASA 9.6
#
# http://www.hsc.fr/ressources/breves/pix_crack.html.en
#
(("hsc", ""), "YtT8/k6Np8F1yz2c"), # confirmed ASA 9.6
#
# www.freerainbowtables.com/phpBB3/viewtopic.php?f=2&t=1441
#
(("", ""), "8Ry2YjIyt7RRXU24"), # confirmed ASA 9.6
(("cisco", "john"), "hN7LzeyYjw12FSIU"),
(("cisco", "jack"), "7DrfeZ7cyOj/PslD"),
#
# http://comments.gmane.org/gmane.comp.security.openwall.john.user/2529
#
(("ripper", "alex"), "h3mJrcH0901pqX/m"),
(("cisco", "cisco"), "3USUcOPFUiMCO4Jk"),
(("cisco", "cisco1"), "3USUcOPFUiMCO4Jk"),
(("CscFw-ITC!", "admcom"), "lZt7HSIXw3.QP7.R"),
("cangetin", "TynyB./ftknE77QP"),
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
#
# http://openwall.info/wiki/john/sample-hashes
#
(("phonehome", "rharris"), "zyIIMSYjiPm0L7a6"),
#
# http://www.openwall.com/lists/john-users/2010/08/08/3
#
(("cangetin", ""), "TynyB./ftknE77QP"),
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
#
# from JTR 1.7.9
#
("test1", "TRPEas6f/aa6JSPL"),
("test2", "OMT6mXmAvGyzrCtp"),
("test3", "gTC7RIy1XJzagmLm"),
("test4", "oWC1WRwqlBlbpf/O"),
("password", "NuLKvvWGg.x9HEKO"),
("0123456789abcdef", ".7nfVBEIEu4KbF/1"),
#
# http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html#wp5472
#
(("1234567890123456", ""), "feCkwUGktTCAgIbD"), # canonical source
(("watag00s1am", ""), "jMorNbK0514fadBh"), # canonical source
#
# custom
#
(("cisco1", "cisco1"), "jmINXNH6p1BxUppp"),
# ensures utf-8 used for unicode
(UPASS_TABLE, 'CaiIvkLMu2TOHXGT'),
#
# passlib reference vectors
#
# Some of these have been confirmed on various ASA firewalls,
# and the exact version is noted next to each hash.
# Would like to verify these under more PIX & ASA versions.
#
# Those without a note are generally an extrapolation,
# to ensure the code stays consistent, but for various reasons,
# hasn't been verified.
#
# * One such case is usernames w/ 1 & 2 digits --
# ASA (9.6 at least) requires 3+ digits in username.
#
# The following hashes (below 13 chars) should be identical for PIX/ASA.
# Ones which differ are listed separately in the known_correct_hashes
# list for the two test classes.
#
# 4 char password
(('1234', ''), 'RLPMUQ26KL4blgFN'), # confirmed ASA 9.6
# 8 char password
(('01234567', ''), '0T52THgnYdV1tlOF'), # confirmed ASA 9.6
(('01234567', '3'), '.z0dT9Alkdc7EIGS'),
(('01234567', '36'), 'CC3Lam53t/mHhoE7'),
(('01234567', '365'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
(('01234567', '3333'), '.z0dT9Alkdc7EIGS'), # confirmed ASA 9.6
(('01234567', '3636'), 'CC3Lam53t/mHhoE7'), # confirmed ASA 9.6
(('01234567', '3653'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
(('01234567', 'adm'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
(('01234567', 'adma'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
(('01234567', 'admad'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
(('01234567', 'user'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
(('01234567', 'user1234'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
# 12 char password
(('0123456789ab', ''), 'S31BxZOGlAigndcJ'), # confirmed ASA 9.6
(('0123456789ab', '36'), 'wFqSX91X5.YaRKsi'),
(('0123456789ab', '365'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
(('0123456789ab', '3333'), 'mcXPL/vIZcIxLUQs'), # confirmed ASA 9.6
(('0123456789ab', '3636'), 'wFqSX91X5.YaRKsi'), # confirmed ASA 9.6
(('0123456789ab', '3653'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
(('0123456789ab', 'user'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
(('0123456789ab', 'user1234'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
# NOTE: remaining reference vectors for 13+ char passwords
# are split up between cisco_pix & cisco_asa tests.
# unicode passwords
# ASA supposedly uses utf-8 encoding, but entering non-ascii
# chars is error-prone, and while UTF-8 appears to be intended,
# observed behaviors include:
# * ssh cli stripping non-ascii chars entirely
# * ASDM web iface double-encoding utf-8 strings
((u("t\xe1ble").encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'),
((u("t\xe1ble").encode("utf-8").decode("latin-1").encode("utf-8"),
'user'), 'cMvFC2XVBmK/68yB'), # confirmed ASA 9.6 when typed into ASDM
]
def test_calc_digest_spoiler(self):
"""
_calc_checksum() -- spoil oversize passwords during verify
for details, see 'spoil_digest' flag instead that function.
this helps cisco_pix/cisco_asa implement their policy of
``.truncate_verify_reject=True``.
"""
def calc(secret, for_hash=False):
return self.handler(use_defaults=for_hash)._calc_checksum(secret)
# short (non-truncated) password
short_secret = repeat_string("1234", self.handler.truncate_size)
short_hash = calc(short_secret)
# longer password should have totally different hash,
# to prevent verify from matching (i.e. "spoiled").
long_secret = short_secret + "X"
long_hash = calc(long_secret)
self.assertNotEqual(long_hash, short_hash)
# spoiled hash should depend on whole secret,
# so that output isn't predictable
alt_long_secret = short_secret + "Y"
alt_long_hash = calc(alt_long_secret)
self.assertNotEqual(alt_long_hash, short_hash)
self.assertNotEqual(alt_long_hash, long_hash)
# for hash(), should throw error if password too large
calc(short_secret, for_hash=True)
self.assertRaises(exc.PasswordSizeError, calc, long_secret, for_hash=True)
self.assertRaises(exc.PasswordSizeError, calc, alt_long_secret, for_hash=True)
#=============================================================================
# cisco pix
#=============================================================================
class cisco_pix_test(_PixAsaSharedTest):
handler = hash.cisco_pix
#: known correct pix hashes
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
#
# passlib reference vectors (PIX-specific)
#
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
# and general notes about the 'passlib reference vectors' test set.
#
# All of the following are PIX-specific, as ASA starts
# to use a different padding size at 13 characters.
#
# TODO: these need confirming w/ an actual PIX system.
#
# 13 char password
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'),
(('0123456789abc', '3'), 'ylJTd/qei66WZe3w'),
(('0123456789abc', '36'), 'hDx8QRlUhwd6bU8N'),
(('0123456789abc', '365'), 'vYOOtnkh1HXcMrM7'),
(('0123456789abc', '3333'), 'ylJTd/qei66WZe3w'),
(('0123456789abc', '3636'), 'hDx8QRlUhwd6bU8N'),
(('0123456789abc', '3653'), 'vYOOtnkh1HXcMrM7'),
(('0123456789abc', 'user'), 'f4/.SALxqDo59mfV'),
(('0123456789abc', 'user1234'), 'f4/.SALxqDo59mfV'),
# 14 char password
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'),
(('0123456789abcd', '3'), 'f5lvmqWYj9gJqkIH'),
(('0123456789abcd', '36'), 'OJJ1Khg5HeAYBH1c'),
(('0123456789abcd', '365'), 'OJJ1Khg5HeAYBH1c'),
(('0123456789abcd', '3333'), 'f5lvmqWYj9gJqkIH'),
(('0123456789abcd', '3636'), 'OJJ1Khg5HeAYBH1c'),
(('0123456789abcd', '3653'), 'OJJ1Khg5HeAYBH1c'),
(('0123456789abcd', 'adm'), 'DbPLCFIkHc2SiyDk'),
(('0123456789abcd', 'adma'), 'DbPLCFIkHc2SiyDk'),
(('0123456789abcd', 'user'), 'WfO2UiTapPkF/FSn'),
(('0123456789abcd', 'user1234'), 'WfO2UiTapPkF/FSn'),
# 15 char password
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'),
(('0123456789abcde', '3'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', '36'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', '365'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', '3333'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', '3636'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', '3653'), 'lYbwBu.f82OIApQB'),
(('0123456789abcde', 'adm'), 'KgKx1UQvdR/09i9u'),
(('0123456789abcde', 'adma'), 'KgKx1UQvdR/09i9u'),
(('0123456789abcde', 'user'), 'qLopkenJ4WBqxaZN'),
(('0123456789abcde', 'user1234'), 'qLopkenJ4WBqxaZN'),
# 16 char password
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', '36'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', '365'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', '3333'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', '3636'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', '3653'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', 'user'), '.7nfVBEIEu4KbF/1'),
(('0123456789abcdef', 'user1234'), '.7nfVBEIEu4KbF/1'),
]
#=============================================================================
# cisco asa
#=============================================================================
class cisco_asa_test(_PixAsaSharedTest):
handler = hash.cisco_asa
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
#
# passlib reference vectors (ASA-specific)
#
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
# and general notes about the 'passlib reference vectors' test set.
#
# 13 char password
# NOTE: past this point, ASA pads to 32 bytes instead of 16
# for all cases where user is set (secret + 4 bytes > 16),
# but still uses 16 bytes for enable pwds (secret <= 16).
# hashes w/ user WON'T match PIX, but "enable" passwords will.
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'), # confirmed ASA 9.6
(('0123456789abc', '36'), 'FRV9JG18UBEgX0.O'),
(('0123456789abc', '365'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
(('0123456789abc', '3333'), 'NmrkP98nT7RAeKZz'), # confirmed ASA 9.6
(('0123456789abc', '3636'), 'FRV9JG18UBEgX0.O'), # confirmed ASA 9.6
(('0123456789abc', '3653'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
(('0123456789abc', 'user'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
(('0123456789abc', 'user1234'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
# 14 char password
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'), # confirmed ASA 9.6
(('0123456789abcd', '3'), 'yxGoujXKPduTVaYB'),
(('0123456789abcd', '36'), 'W0jckhnhjnr/DiT/'),
(('0123456789abcd', '365'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
(('0123456789abcd', '3333'), 'yxGoujXKPduTVaYB'), # confirmed ASA 9.6
(('0123456789abcd', '3636'), 'W0jckhnhjnr/DiT/'), # confirmed ASA 9.6
(('0123456789abcd', '3653'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
(('0123456789abcd', 'adm'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
(('0123456789abcd', 'adma'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
(('0123456789abcd', 'user'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
(('0123456789abcd', 'user1234'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
# 15 char password
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'), # confirmed ASA 9.6
(('0123456789abcde', '3'), 'nAZrQoHaL.fgrIqt'),
(('0123456789abcde', '36'), '2GxIQ6ICE795587X'),
(('0123456789abcde', '365'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
(('0123456789abcde', '3333'), 'nAZrQoHaL.fgrIqt'), # confirmed ASA 9.6
(('0123456789abcde', '3636'), '2GxIQ6ICE795587X'), # confirmed ASA 9.6
(('0123456789abcde', '3653'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
(('0123456789abcde', 'adm'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
(('0123456789abcde', 'adma'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
(('0123456789abcde', 'user'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
(('0123456789abcde', 'user1234'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
# 16 char password
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'), # confirmed ASA 9.6
(('0123456789abcdef', '36'), 'GhI8.yFSC5lwoafg'),
(('0123456789abcdef', '365'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
(('0123456789abcdef', '3333'), 'Ghdi1IlsswgYzzMH'), # confirmed ASA 9.6
(('0123456789abcdef', '3636'), 'GhI8.yFSC5lwoafg'), # confirmed ASA 9.6
(('0123456789abcdef', '3653'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
(('0123456789abcdef', 'user'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
(('0123456789abcdef', 'user1234'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
# 17 char password
# NOTE: past this point, ASA pads to 32 bytes instead of 16
# for ALL cases, since secret > 16 bytes even for enable pwds;
# and so none of these rest here should match PIX.
(('0123456789abcdefq', ''), 'bKshl.EN.X3CVFRQ'), # confirmed ASA 9.6
(('0123456789abcdefq', '36'), 'JAeTXHs0n30svlaG'),
(('0123456789abcdefq', '365'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
(('0123456789abcdefq', '3333'), 'USEJbxI6.VY4ecBP'), # confirmed ASA 9.6
(('0123456789abcdefq', '3636'), 'JAeTXHs0n30svlaG'), # confirmed ASA 9.6
(('0123456789abcdefq', '3653'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
(('0123456789abcdefq', 'user'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
(('0123456789abcdefq', 'user1234'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
# 27 char password
(('0123456789abcdefqwertyuiopa', ''), '4wp19zS3OCe.2jt5'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', '36'), 'PjUoGqWBKPyV9qOe'),
(('0123456789abcdefqwertyuiopa', '365'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', '3333'), 'rd/ZMuGTJFIb2BNG'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', '3636'), 'PjUoGqWBKPyV9qOe'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', '3653'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', 'user'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopa', 'user1234'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
# 28 char password
# NOTE: past this point, ASA stops appending the username AT ALL,
# even though there's still room for the first few chars.
(('0123456789abcdefqwertyuiopas', ''), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopas', '36'), 'W6nbOddI0SutTK7m'),
(('0123456789abcdefqwertyuiopas', '365'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopas', 'user'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopas', 'user1234'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
# 32 char password
# NOTE: this is max size that ASA allows, and throws error for larger
(('0123456789abcdefqwertyuiopasdfgh', ''), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopasdfgh', '36'), '5hPT/iC6DnoBxo6a'),
(('0123456789abcdefqwertyuiopasdfgh', '365'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopasdfgh', 'user'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
(('0123456789abcdefqwertyuiopasdfgh', 'user1234'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
]
#=============================================================================
# cisco type 7
#=============================================================================
class cisco_type7_test(HandlerCase):
handler = hash.cisco_type7
salt_bits = 4
salt_type = int
known_correct_hashes = [
#
# http://mccltd.net/blog/?p=1034
#
("secure ", "04480E051A33490E"),
#
# http://insecure.org/sploits/cisco.passwords.html
#
("Its time to go to lunch!",
"153B1F1F443E22292D73212D5300194315591954465A0D0B59"),
#
# http://blog.ioshints.info/2007/11/type-7-decryption-in-cisco-ios.html
#
("t35t:pa55w0rd", "08351F1B1D431516475E1B54382F"),
#
# http://www.m00nie.com/2011/09/cisco-type-7-password-decryption-and-encryption-with-perl/
#
("hiImTesting:)", "020E0D7206320A325847071E5F5E"),
#
# http://packetlife.net/forums/thread/54/
#
("cisco123", "060506324F41584B56"),
("cisco123", "1511021F07257A767B"),
#
# source ?
#
('Supe&8ZUbeRp4SS', "06351A3149085123301517391C501918"),
#
# custom
#
# ensures utf-8 used for unicode
(UPASS_TABLE, '0958EDC8A9F495F6F8A5FD'),
]
known_unidentified_hashes = [
# salt with hex value
"0A480E051A33490E",
# salt value > 52. this may in fact be valid, but we reject it for now
# (see docs for more).
'99400E4812',
]
def test_90_decode(self):
"""test cisco_type7.decode()"""
from passlib.utils import to_unicode, to_bytes
handler = self.handler
for secret, hash in self.known_correct_hashes:
usecret = to_unicode(secret)
bsecret = to_bytes(secret)
self.assertEqual(handler.decode(hash), usecret)
self.assertEqual(handler.decode(hash, None), bsecret)
self.assertRaises(UnicodeDecodeError, handler.decode,
'0958EDC8A9F495F6F8A5FD', 'ascii')
def test_91_salt(self):
"""test salt value border cases"""
handler = self.handler
self.assertRaises(TypeError, handler, salt=None)
handler(salt=None, use_defaults=True)
self.assertRaises(TypeError, handler, salt='abc')
self.assertRaises(ValueError, handler, salt=-10)
self.assertRaises(ValueError, handler, salt=100)
self.assertRaises(TypeError, handler.using, salt='abc')
self.assertRaises(ValueError, handler.using, salt=-10)
self.assertRaises(ValueError, handler.using, salt=100)
with self.assertWarningList("salt/offset must be.*"):
subcls = handler.using(salt=100, relaxed=True)
self.assertEqual(subcls(use_defaults=True).salt, 52)
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,413 @@
"""passlib.tests.test_handlers_django - tests for passlib hash algorithms"""
#=============================================================================
# imports
#=============================================================================
from __future__ import with_statement
# core
import logging; log = logging.getLogger(__name__)
import re
import warnings
# site
# pkg
from passlib import hash
from passlib.utils import repeat_string
from passlib.utils.compat import u
from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest
from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE
from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \
check_django_hasher_has_backend
# module
#=============================================================================
# django
#=============================================================================
# standard string django uses
UPASS_LETMEIN = u('l\xe8tmein')
def vstr(version):
return ".".join(str(e) for e in version)
class _DjangoHelper(TestCase):
"""
mixin for HandlerCase subclasses that are testing a hasher
which is also present in django.
"""
__unittest_skip = True
#: minimum django version where hash alg is present / that we support testing against
min_django_version = MIN_DJANGO_VERSION
#: max django version where hash alg is present
#: TODO: for a bunch of the tests below, this is just max version where
#: settings.PASSWORD_HASHERS includes it by default -- could add helper to patch
#: desired django hasher back in for duration of test.
#: XXX: change this to "disabled_in_django_version" instead?
max_django_version = None
def _require_django_support(self):
# make sure min django version
if DJANGO_VERSION < self.min_django_version:
raise self.skipTest("Django >= %s not installed" % vstr(self.min_django_version))
if self.max_django_version and DJANGO_VERSION > self.max_django_version:
raise self.skipTest("Django <= %s not installed" % vstr(self.max_django_version))
# make sure django has a backend for specified hasher
name = self.handler.django_name
if not check_django_hasher_has_backend(name):
raise self.skipTest('django hasher %r not available' % name)
return True
extra_fuzz_verifiers = HandlerCase.fuzz_verifiers + (
"fuzz_verifier_django",
)
def fuzz_verifier_django(self):
try:
self._require_django_support()
except SkipTest:
return None
from django.contrib.auth.hashers import check_password
def verify_django(secret, hash):
"""django/check_password"""
if self.handler.name == "django_bcrypt" and hash.startswith("bcrypt$$2y$"):
hash = hash.replace("$$2y$", "$$2a$")
if isinstance(secret, bytes):
secret = secret.decode("utf-8")
return check_password(secret, hash)
return verify_django
def test_90_django_reference(self):
"""run known correct hashes through Django's check_password()"""
self._require_django_support()
# XXX: esp. when it's no longer supported by django,
# should verify it's *NOT* recognized
from django.contrib.auth.hashers import check_password
assert self.known_correct_hashes
for secret, hash in self.iter_known_hashes():
self.assertTrue(check_password(secret, hash),
"secret=%r hash=%r failed to verify" %
(secret, hash))
self.assertFalse(check_password('x' + secret, hash),
"mangled secret=%r hash=%r incorrect verified" %
(secret, hash))
def test_91_django_generation(self):
"""test against output of Django's make_password()"""
self._require_django_support()
# XXX: esp. when it's no longer supported by django,
# should verify it's *NOT* recognized
from passlib.utils import tick
from django.contrib.auth.hashers import make_password
name = self.handler.django_name # set for all the django_* handlers
end = tick() + self.max_fuzz_time/2
generator = self.FuzzHashGenerator(self, self.getRandom())
while tick() < end:
secret, other = generator.random_password_pair()
if not secret: # django rejects empty passwords.
continue
if isinstance(secret, bytes):
secret = secret.decode("utf-8")
hash = make_password(secret, hasher=name)
self.assertTrue(self.do_identify(hash))
self.assertTrue(self.do_verify(secret, hash))
self.assertFalse(self.do_verify(other, hash))
class django_disabled_test(HandlerCase):
"""test django_disabled"""
handler = hash.django_disabled
disabled_contains_salt = True
known_correct_hashes = [
# *everything* should hash to "!", and nothing should verify
("password", "!"),
("", "!"),
(UPASS_TABLE, "!"),
]
known_alternate_hashes = [
# django 1.6 appends random alpnum string
("!9wa845vn7098ythaehasldkfj", "password", "!"),
]
class django_des_crypt_test(HandlerCase, _DjangoHelper):
"""test django_des_crypt"""
handler = hash.django_des_crypt
max_django_version = (1,9)
known_correct_hashes = [
# ensures only first two digits of salt count.
("password", 'crypt$c2$c2M87q...WWcU'),
("password", 'crypt$c2e86$c2M87q...WWcU'),
("passwordignoreme", 'crypt$c2.AZ$c2M87q...WWcU'),
# ensures utf-8 used for unicode
(UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'),
(UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'),
(u("hell\u00D6"), "crypt$sa$saykDgk3BPZ9E"),
# prevent regression of issue 22
("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'),
]
known_alternate_hashes = [
# ensure django 1.4 empty salt field is accepted;
# but that salt field is re-filled (for django 1.0 compatibility)
('crypt$$c2M87q...WWcU', "password", 'crypt$c2$c2M87q...WWcU'),
]
known_unidentified_hashes = [
'sha1$aa$bb',
]
known_malformed_hashes = [
# checksum too short
'crypt$c2$c2M87q',
# salt must be >2
'crypt$f$c2M87q...WWcU',
# make sure first 2 chars of salt & chk field agree.
'crypt$ffe86$c2M87q...WWcU',
]
class django_salted_md5_test(HandlerCase, _DjangoHelper):
"""test django_salted_md5"""
handler = hash.django_salted_md5
max_django_version = (1,9)
known_correct_hashes = [
# test extra large salt
("password", 'md5$123abcdef$c8272612932975ee80e8a35995708e80'),
# test django 1.4 alphanumeric salt
("test", 'md5$3OpqnFAHW5CT$54b29300675271049a1ebae07b395e20'),
# ensures utf-8 used for unicode
(UPASS_USD, 'md5$c2e86$92105508419a81a6babfaecf876a2fa0'),
(UPASS_TABLE, 'md5$d9eb8$01495b32852bffb27cf5d4394fe7a54c'),
]
known_unidentified_hashes = [
'sha1$aa$bb',
]
known_malformed_hashes = [
# checksum too short
'md5$aa$bb',
]
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def random_salt_size(self):
# workaround for django14 regression --
# 1.4 won't accept hashes with empty salt strings, unlike 1.3 and earlier.
# looks to be fixed in a future release -- https://code.djangoproject.com/ticket/18144
# for now, we avoid salt_size==0 under 1.4
handler = self.handler
default = handler.default_salt_size
assert handler.min_salt_size == 0
lower = 1
upper = handler.max_salt_size or default*4
return self.randintgauss(lower, upper, default, default*.5)
class django_salted_sha1_test(HandlerCase, _DjangoHelper):
"""test django_salted_sha1"""
handler = hash.django_salted_sha1
max_django_version = (1,9)
known_correct_hashes = [
# test extra large salt
("password",'sha1$123abcdef$e4a1877b0e35c47329e7ed7e58014276168a37ba'),
# test django 1.4 alphanumeric salt
("test", 'sha1$bcwHF9Hy8lxS$6b4cfa0651b43161c6f1471ce9523acf1f751ba3'),
# ensures utf-8 used for unicode
(UPASS_USD, 'sha1$c2e86$0f75c5d7fbd100d587c127ef0b693cde611b4ada'),
(UPASS_TABLE, 'sha1$6d853$ef13a4d8fb57aed0cb573fe9c82e28dc7fd372d4'),
# generic password
("MyPassword", 'sha1$54123$893cf12e134c3c215f3a76bd50d13f92404a54d3'),
]
known_unidentified_hashes = [
'md5$aa$bb',
]
known_malformed_hashes = [
# checksum too short
'sha1$c2e86$0f75',
]
# reuse custom random_salt_size() helper...
FuzzHashGenerator = django_salted_md5_test.FuzzHashGenerator
class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
"""test django_pbkdf2_sha256"""
handler = hash.django_pbkdf2_sha256
known_correct_hashes = [
#
# custom - generated via django 1.4 hasher
#
('not a password',
'pbkdf2_sha256$10000$kjVJaVz6qsnJ$5yPHw3rwJGECpUf70daLGhOrQ5+AMxIJdz1c3bqK1Rs='),
(UPASS_TABLE,
'pbkdf2_sha256$10000$bEwAfNrH1TlQ$OgYUblFNUX1B8GfMqaCYUK/iHyO0pa7STTDdaEJBuY0='),
]
class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper):
"""test django_pbkdf2_sha1"""
handler = hash.django_pbkdf2_sha1
known_correct_hashes = [
#
# custom - generated via django 1.4 hashers
#
('not a password',
'pbkdf2_sha1$10000$wz5B6WkasRoF$atJmJ1o+XfJxKq1+Nu1f1i57Z5I='),
(UPASS_TABLE,
'pbkdf2_sha1$10000$KZKWwvqb8BfL$rw5pWsxJEU4JrZAQhHTCO+u0f5Y='),
]
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
class django_bcrypt_test(HandlerCase, _DjangoHelper):
"""test django_bcrypt"""
handler = hash.django_bcrypt
# XXX: not sure when this wasn't in default list anymore. somewhere in [2.0 - 2.2]
max_django_version = (2, 0)
fuzz_salts_need_bcrypt_repair = True
known_correct_hashes = [
#
# just copied and adapted a few test vectors from bcrypt (above),
# since django_bcrypt is just a wrapper for the real bcrypt class.
#
('', 'bcrypt$$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
('abcdefghijklmnopqrstuvwxyz',
'bcrypt$$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
(UPASS_TABLE,
'bcrypt$$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'),
]
# NOTE: the following have been cloned from _bcrypt_test()
def populate_settings(self, kwds):
# speed up test w/ lower rounds
kwds.setdefault("rounds", 4)
super(django_bcrypt_test, self).populate_settings(kwds)
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(5, 8, 6, 1)
def random_ident(self):
# omit multi-ident tests, only $2a$ counts for this class
# XXX: enable this to check 2a / 2b?
return None
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper):
"""test django_bcrypt_sha256"""
handler = hash.django_bcrypt_sha256
forbidden_characters = None
fuzz_salts_need_bcrypt_repair = True
known_correct_hashes = [
#
# custom - generated via django 1.6 hasher
#
('',
'bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu'),
(UPASS_LETMEIN,
'bcrypt_sha256$$2a$08$NDjSAIcas.EcoxCRiArvT.MkNiPYVhrsrnJsRkLueZOoV1bsQqlmC'),
(UPASS_TABLE,
'bcrypt_sha256$$2a$06$kCXUnRFQptGg491siDKNTu8RxjBGSjALHRuvhPYNFsa4Ea5d9M48u'),
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
(repeat_string("abc123",72),
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OySmyXA8FoY4PjGizjE1QSDfuL5MXNni'),
(repeat_string("abc123",72)+"qwr",
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61Ocy0BEz1RK6xslSNi8PlaLX2pe7x/KQG'),
(repeat_string("abc123",72)+"xyz",
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OvY2zoRVUa2Pugv2ExVOUT2YmhvxUFUa'),
]
known_malformed_hashers = [
# data in django salt field
'bcrypt_sha256$xyz$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu',
]
# NOTE: the following have been cloned from _bcrypt_test()
def populate_settings(self, kwds):
# speed up test w/ lower rounds
kwds.setdefault("rounds", 4)
super(django_bcrypt_sha256_test, self).populate_settings(kwds)
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(5, 8, 6, 1)
def random_ident(self):
# omit multi-ident tests, only $2a$ counts for this class
# XXX: enable this to check 2a / 2b?
return None
from passlib.tests.test_handlers_argon2 import _base_argon2_test
@skipUnless(hash.argon2.has_backend(), "no argon2 backends available")
class django_argon2_test(HandlerCase, _DjangoHelper):
"""test django_bcrypt"""
handler = hash.django_argon2
# NOTE: most of this adapted from _base_argon2_test & argon2pure test
known_correct_hashes = [
# sample test
("password", 'argon2$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'),
# sample w/ all parameters different
("password", 'argon2$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'),
# generated from django 1.10.3
(UPASS_LETMEIN, 'argon2$argon2i$v=19$m=512,t=2,p=2$V25jN1l4UUJZWkR1$MxpA1BD2Gh7+D79gaAw6sQ'),
]
def setUpWarnings(self):
super(django_argon2_test, self).setUpWarnings()
warnings.filterwarnings("ignore", ".*Using argon2pure backend.*")
def do_stub_encrypt(self, handler=None, **settings):
# overriding default since no way to get stub config from argon2._calc_hash()
# (otherwise test_21b_max_rounds blocks trying to do max rounds)
handler = (handler or self.handler).using(**settings)
self = handler.wrapped(use_defaults=True)
self.checksum = self._stub_checksum
assert self.checksum
return handler._wrap_hash(self.to_string())
def test_03_legacy_hash_workflow(self):
# override base method
raise self.skipTest("legacy 1.6 workflow not supported")
class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator):
def random_type(self):
# override default since django only uses type I (see note in class)
return "I"
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(1, 3, 2, 1)
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,480 @@
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
#=============================================================================
# imports
#=============================================================================
# core
import logging
log = logging.getLogger(__name__)
import warnings
# site
# pkg
from passlib import hash
from passlib.utils.compat import u
from passlib.tests.utils import TestCase, HandlerCase
from passlib.tests.test_handlers import UPASS_WAV
# module
#=============================================================================
# ldap_pbkdf2_{digest}
#=============================================================================
# NOTE: since these are all wrappers for the pbkdf2_{digest} hasehs,
# they don't extensive separate testing.
class ldap_pbkdf2_test(TestCase):
def test_wrappers(self):
"""test ldap pbkdf2 wrappers"""
self.assertTrue(
hash.ldap_pbkdf2_sha1.verify(
"password",
'{PBKDF2}1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI',
)
)
self.assertTrue(
hash.ldap_pbkdf2_sha256.verify(
"password",
'{PBKDF2-SHA256}1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg'
'.fJPeq1h/gXXY7acBp9/6c.tmQ'
)
)
self.assertTrue(
hash.ldap_pbkdf2_sha512.verify(
"password",
'{PBKDF2-SHA512}1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
)
)
#=============================================================================
# pbkdf2 hashes
#=============================================================================
class atlassian_pbkdf2_sha1_test(HandlerCase):
handler = hash.atlassian_pbkdf2_sha1
known_correct_hashes = [
#
# generated using Jira
#
("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'),
(UPASS_WAV,
"{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"),
]
known_malformed_hashes = [
# bad char ---\/
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy!0IPksHChwoTAVYFrhsgoq8/p'
# bad size, missing padding
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/'
# bad size, with correct padding
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/='
]
class pbkdf2_sha1_test(HandlerCase):
handler = hash.pbkdf2_sha1
known_correct_hashes = [
("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'),
(UPASS_WAV,
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'),
]
known_malformed_hashes = [
# zero padded rounds field
'$pbkdf2$01212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
# empty rounds field
'$pbkdf2$$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
# too many field
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc$',
]
class pbkdf2_sha256_test(HandlerCase):
handler = hash.pbkdf2_sha256
known_correct_hashes = [
("password",
'$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ'
),
(UPASS_WAV,
'$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI'
),
]
class pbkdf2_sha512_test(HandlerCase):
handler = hash.pbkdf2_sha512
known_correct_hashes = [
("password",
'$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
),
(UPASS_WAV,
'$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt'
'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q'
),
]
class cta_pbkdf2_sha1_test(HandlerCase):
handler = hash.cta_pbkdf2_sha1
known_correct_hashes = [
#
# test vectors from original implementation
#
(u("hashy the \N{SNOWMAN}"), '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='),
#
# custom
#
("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="),
(UPASS_WAV,
"$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="),
]
class dlitz_pbkdf2_sha1_test(HandlerCase):
handler = hash.dlitz_pbkdf2_sha1
known_correct_hashes = [
#
# test vectors from original implementation
#
('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'),
('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'),
('dcl', '$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL'),
('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'),
(UPASS_WAV,
'$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'),
]
class grub_pbkdf2_sha512_test(HandlerCase):
handler = hash.grub_pbkdf2_sha512
known_correct_hashes = [
#
# test vectors generated from cmd line tool
#
# salt=32 bytes
(UPASS_WAV,
'grub.pbkdf2.sha512.10000.BCAC1CEC5E4341C8C511C529'
'7FA877BE91C2817B32A35A3ECF5CA6B8B257F751.6968526A'
'2A5B1AEEE0A29A9E057336B48D388FFB3F600233237223C21'
'04DE1752CEC35B0DD1ED49563398A282C0F471099C2803FBA'
'47C7919CABC43192C68F60'),
# salt=64 bytes
('toomanysecrets',
'grub.pbkdf2.sha512.10000.9B436BB6978682363D5C449B'
'BEAB322676946C632208BC1294D51F47174A9A3B04A7E4785'
'986CD4EA7470FAB8FE9F6BD522D1FC6C51109A8596FB7AD48'
'7C4493.0FE5EF169AFFCB67D86E2581B1E251D88C777B98BA'
'2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4'
'634083F9A20F4B6F339A52FBD6BED618E527B'),
]
#=============================================================================
# scram hash
#=============================================================================
class scram_test(HandlerCase):
handler = hash.scram
# TODO: need a bunch more reference vectors from some real
# SCRAM transactions.
known_correct_hashes = [
#
# taken from example in SCRAM specification (rfc 5802)
#
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'),
#
# custom
#
# same as 5802 example hash, but with sha-256 & sha-512 added.
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
# test unicode passwords & saslprep (all the passwords below
# should normalize to the same value: 'IX \xE0')
(u('IX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
(u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$'
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
(u('\u00ADIX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
]
known_malformed_hashes = [
# zero-padding in rounds
'$scram$04096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
# non-digit in rounds
'$scram$409A$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
# bad char in salt ---\/
'$scram$4096$QSXCR.Q6sek8bf9-$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
# bad char in digest ---\/
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX3-',
# missing sections
'$scram$4096$QSXCR.Q6sek8bf92',
'$scram$4096$QSXCR.Q6sek8bf92$',
# too many sections
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30$',
# missing separator
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY',
# too many chars in alg name
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'shaxxx-190=HZbuOlKbWl.eR8AfIposuKbhX30',
# missing sha-1 alg
'$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30',
# non-iana name
'$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30',
]
def setUp(self):
super(scram_test, self).setUp()
# some platforms lack stringprep (e.g. Jython, IronPython)
self.require_stringprep()
# silence norm_hash_name() warning
warnings.filterwarnings("ignore", r"norm_hash_name\(\): unknown hash")
def test_90_algs(self):
"""test parsing of 'algs' setting"""
defaults = dict(salt=b'A'*10, rounds=1000)
def parse(algs, **kwds):
for k in defaults:
kwds.setdefault(k, defaults[k])
return self.handler(algs=algs, **kwds).algs
# None -> default list
self.assertEqual(parse(None, use_defaults=True), hash.scram.default_algs)
self.assertRaises(TypeError, parse, None)
# strings should be parsed
self.assertEqual(parse("sha1"), ["sha-1"])
self.assertEqual(parse("sha1, sha256, md5"), ["md5","sha-1","sha-256"])
# lists should be normalized
self.assertEqual(parse(["sha-1","sha256"]), ["sha-1","sha-256"])
# sha-1 required
self.assertRaises(ValueError, parse, ["sha-256"])
self.assertRaises(ValueError, parse, algs=[], use_defaults=True)
# alg names must be < 10 chars
self.assertRaises(ValueError, parse, ["sha-1","shaxxx-190"])
# alg & checksum mutually exclusive.
self.assertRaises(RuntimeError, parse, ['sha-1'],
checksum={"sha-1": b"\x00"*20})
def test_90_checksums(self):
"""test internal parsing of 'checksum' keyword"""
# check non-bytes checksum values are rejected
self.assertRaises(TypeError, self.handler, use_defaults=True,
checksum={'sha-1': u('X')*20})
# check sha-1 is required
self.assertRaises(ValueError, self.handler, use_defaults=True,
checksum={'sha-256': b'X'*32})
# XXX: anything else that's not tested by the other code already?
def test_91_extract_digest_info(self):
"""test scram.extract_digest_info()"""
edi = self.handler.extract_digest_info
# return appropriate value or throw KeyError
h = "$scram$10$AAAAAA$sha-1=AQ,bbb=Ag,ccc=Aw"
s = b'\x00'*4
self.assertEqual(edi(h,"SHA1"), (s,10, b'\x01'))
self.assertEqual(edi(h,"bbb"), (s,10, b'\x02'))
self.assertEqual(edi(h,"ccc"), (s,10, b'\x03'))
self.assertRaises(KeyError, edi, h, "ddd")
# config strings should cause value error.
c = "$scram$10$....$sha-1,bbb,ccc"
self.assertRaises(ValueError, edi, c, "sha-1")
self.assertRaises(ValueError, edi, c, "bbb")
self.assertRaises(ValueError, edi, c, "ddd")
def test_92_extract_digest_algs(self):
"""test scram.extract_digest_algs()"""
eda = self.handler.extract_digest_algs
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), ["sha-1"])
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', format="hashlib"),
["sha1"])
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
["sha-1","sha-256","sha-512"])
def test_93_derive_digest(self):
"""test scram.derive_digest()"""
# NOTE: this just does a light test, since derive_digest
# is used by hash / verify, and is tested pretty well via those.
hash = self.handler.derive_digest
# check various encodings of password work.
s1 = b'\x01\x02\x03'
d1 = b'\xb2\xfb\xab\x82[tNuPnI\x8aZZ\x19\x87\xcen\xe9\xd3'
self.assertEqual(hash(u("\u2168"), s1, 1000, 'sha-1'), d1)
self.assertEqual(hash(b"\xe2\x85\xa8", s1, 1000, 'SHA-1'), d1)
self.assertEqual(hash(u("IX"), s1, 1000, 'sha1'), d1)
self.assertEqual(hash(b"IX", s1, 1000, 'SHA1'), d1)
# check algs
self.assertEqual(hash("IX", s1, 1000, 'md5'),
b'3\x19\x18\xc0\x1c/\xa8\xbf\xe4\xa3\xc2\x8eM\xe8od')
self.assertRaises(ValueError, hash, "IX", s1, 1000, 'sha-666')
# check rounds
self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1')
# unicode salts accepted as of passlib 1.7 (previous caused TypeError)
self.assertEqual(hash(u("IX"), s1.decode("latin-1"), 1000, 'sha1'), d1)
def test_94_saslprep(self):
"""test hash/verify use saslprep"""
# NOTE: this just does a light test that saslprep() is being
# called in various places, relying in saslpreps()'s tests
# to verify full normalization behavior.
# hash unnormalized
h = self.do_encrypt(u("I\u00ADX"))
self.assertTrue(self.do_verify(u("IX"), h))
self.assertTrue(self.do_verify(u("\u2168"), h))
# hash normalized
h = self.do_encrypt(u("\xF3"))
self.assertTrue(self.do_verify(u("o\u0301"), h))
self.assertTrue(self.do_verify(u("\u200Do\u0301"), h))
# throws error if forbidden char provided
self.assertRaises(ValueError, self.do_encrypt, u("\uFDD0"))
self.assertRaises(ValueError, self.do_verify, u("\uFDD0"), h)
def test_94_using_w_default_algs(self, param="default_algs"):
"""using() -- 'default_algs' parameter"""
# create subclass
handler = self.handler
orig = list(handler.default_algs) # in case it's modified in place
subcls = handler.using(**{param: "sha1,md5"})
# shouldn't have changed handler
self.assertEqual(handler.default_algs, orig)
# should have own set
self.assertEqual(subcls.default_algs, ["md5", "sha-1"])
# test hash output
h1 = subcls.hash("dummy")
self.assertEqual(handler.extract_digest_algs(h1), ["md5", "sha-1"])
def test_94_using_w_algs(self):
"""using() -- 'algs' parameter"""
self.test_94_using_w_default_algs(param="algs")
def test_94_needs_update_algs(self):
"""needs_update() -- algs setting"""
handler1 = self.handler.using(algs="sha1,md5")
# shouldn't need update, has same algs
h1 = handler1.hash("dummy")
self.assertFalse(handler1.needs_update(h1))
# *currently* shouldn't need update, has superset of algs required by handler2
# (may change this policy)
handler2 = handler1.using(algs="sha1")
self.assertFalse(handler2.needs_update(h1))
# should need update, doesn't have all algs required by handler3
handler3 = handler1.using(algs="sha1,sha256")
self.assertTrue(handler3.needs_update(h1))
def test_95_context_algs(self):
"""test handling of 'algs' in context object"""
handler = self.handler
from passlib.context import CryptContext
c1 = CryptContext(["scram"], scram__algs="sha1,md5")
h = c1.hash("dummy")
self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
self.assertFalse(c1.needs_update(h))
c2 = c1.copy(scram__algs="sha1")
self.assertFalse(c2.needs_update(h))
c2 = c1.copy(scram__algs="sha1,sha256")
self.assertTrue(c2.needs_update(h))
def test_96_full_verify(self):
"""test verify(full=True) flag"""
def vpart(s, h):
return self.handler.verify(s, h)
def vfull(s, h):
return self.handler.verify(s, h, full=True)
# reference
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
self.assertTrue(vfull('pencil', h))
self.assertFalse(vfull('tape', h))
# catch truncated digests.
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhV,' # -1 char
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
self.assertRaises(ValueError, vfull, 'pencil', h)
# catch padded digests.
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVYa,' # +1 char
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
self.assertRaises(ValueError, vfull, 'pencil', h)
# catch hash containing digests belonging to diff passwords.
# proper behavior for quick-verify (the default) is undefined,
# but full-verify should throw error.
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' # 'pencil'
'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc,' # 'tape'
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' # 'pencil'
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
self.assertTrue(vpart('tape', h))
self.assertFalse(vpart('pencil', h))
self.assertRaises(ValueError, vfull, 'pencil', h)
self.assertRaises(ValueError, vfull, 'tape', h)
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,111 @@
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
#=============================================================================
# imports
#=============================================================================
# core
import logging; log = logging.getLogger(__name__)
import warnings
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
# site
# pkg
from passlib import hash
from passlib.tests.utils import HandlerCase, TEST_MODE
from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8
# module
#=============================================================================
# scrypt hash
#=============================================================================
class _scrypt_test(HandlerCase):
handler = hash.scrypt
known_correct_hashes = [
#
# excepted from test vectors from scrypt whitepaper
# (http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b),
# and encoded using passlib's custom format
#
# salt=b""
("", "$scrypt$ln=4,r=1,p=1$$d9ZXYjhleyA7GcpCwYoEl/FrSETjB0ro39/6P+3iFEI"),
# salt=b"NaCl"
("password", "$scrypt$ln=10,r=8,p=16$TmFDbA$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWI"),
#
# custom
#
# simple test
("test", '$scrypt$ln=8,r=8,p=1$wlhLyXmP8b53bm1NKYVQqg$mTpvG8lzuuDk+DWz8HZIB6Vum6erDuUm0As5yU+VxWA'),
# different block value
("password", '$scrypt$ln=8,r=2,p=1$dO6d0xoDoLT2PofQGoNQag$g/Wf2A0vhHhaJM+addK61QPBthSmYB6uVTtQzh8CM3o'),
# different rounds
(UPASS_TABLE, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
# alt encoding
(PASS_TABLE_UTF8, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
# diff block & parallel counts as well
("nacl", '$scrypt$ln=1,r=4,p=2$yhnD+J+Tci4lZCwFgHCuVQ$fAsEWmxSHuC0cHKMwKVFPzrQukgvK09Sj+NueTSxKds')
]
if TEST_MODE("full"):
# add some hashes with larger rounds value.
known_correct_hashes.extend([
#
# from scrypt whitepaper
#
# salt=b"SodiumChloride"
("pleaseletmein", "$scrypt$ln=14,r=8,p=1$U29kaXVtQ2hsb3JpZGU"
"$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+qbVD9lRdofI"),
#
# openwall format (https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt)
#
("pleaseletmein",
"$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D"),
])
known_malformed_hashes = [
# missing 'p' value
'$scrypt$ln=10,r=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
# rounds too low
'$scrypt$ln=0,r=1,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
# invalid block size
'$scrypt$ln=10,r=A,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
# r*p too large
'$scrypt$ln=10,r=134217728,p=8$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
]
def setUpWarnings(self):
super(_scrypt_test, self).setUpWarnings()
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
def populate_settings(self, kwds):
# builtin is still just way too slow.
if self.backend == "builtin":
kwds.setdefault("rounds", 6)
super(_scrypt_test, self).populate_settings(kwds)
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
def random_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return self.randintgauss(4, 10, 6, 1)
# create test cases for specific backends
scrypt_stdlib_test = _scrypt_test.create_backend_case("stdlib")
scrypt_scrypt_test = _scrypt_test.create_backend_case("scrypt")
scrypt_builtin_test = _scrypt_test.create_backend_case("builtin")
#=============================================================================
# eof
#=============================================================================