mirror of
https://github.com/python/cpython.git
synced 2025-11-01 22:21:35 +00:00
gh-90733: improve hashlib.scrypt interface (#136100)
* add `scrypt` to `hashlib.__all__` * improve `hashlib.scrypt` exception messages
This commit is contained in:
parent
75e2c5dd34
commit
a68ddea3bf
5 changed files with 120 additions and 88 deletions
|
|
@ -1184,12 +1184,6 @@ class KDFTests(unittest.TestCase):
|
|||
(b'pass\0word', b'sa\0lt', 4096, 16),
|
||||
]
|
||||
|
||||
scrypt_test_vectors = [
|
||||
(b'', b'', 16, 1, 1, unhexlify('77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906')),
|
||||
(b'password', b'NaCl', 1024, 8, 16, unhexlify('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')),
|
||||
(b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, unhexlify('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')),
|
||||
]
|
||||
|
||||
pbkdf2_results = {
|
||||
"sha1": [
|
||||
# official test vectors from RFC 6070
|
||||
|
|
@ -1281,46 +1275,6 @@ def _test_pbkdf2_hmac(self, pbkdf2, supported):
|
|||
def test_pbkdf2_hmac_c(self):
|
||||
self._test_pbkdf2_hmac(openssl_hashlib.pbkdf2_hmac, openssl_md_meth_names)
|
||||
|
||||
@unittest.skipUnless(hasattr(hashlib, 'scrypt'),
|
||||
' test requires OpenSSL > 1.1')
|
||||
@unittest.skipIf(get_fips_mode(), reason="scrypt is blocked in FIPS mode")
|
||||
def test_scrypt(self):
|
||||
for password, salt, n, r, p, expected in self.scrypt_test_vectors:
|
||||
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# this values should work
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1)
|
||||
# password and salt must be bytes-like
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt('password', salt=b'salt', n=2, r=8, p=1)
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', salt='salt', n=2, r=8, p=1)
|
||||
# require keyword args
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', b'salt')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', 2, 8, 1, salt=b'salt')
|
||||
for n in [-1, 0, 1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=n, r=8, p=1)
|
||||
for r in [-1, 0, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=r, p=1)
|
||||
for p in [-1, 0, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=p)
|
||||
for maxmem in [-1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
|
||||
maxmem=maxmem)
|
||||
for dklen in [-1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
|
||||
dklen=dklen)
|
||||
|
||||
def test_normalized_name(self):
|
||||
self.assertNotIn("blake2b512", hashlib.algorithms_available)
|
||||
self.assertNotIn("sha3-512", hashlib.algorithms_available)
|
||||
|
|
@ -1362,5 +1316,76 @@ def readable(self):
|
|||
hashlib.file_digest(NonBlocking(), hashlib.sha256)
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(hashlib, 'scrypt'), 'requires OpenSSL 1.1+')
|
||||
@unittest.skipIf(get_fips_mode(), reason="scrypt is blocked in FIPS mode")
|
||||
class TestScrypt(unittest.TestCase):
|
||||
|
||||
scrypt_test_vectors = [
|
||||
(b'', b'', 16, 1, 1, unhexlify('77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906')),
|
||||
(b'password', b'NaCl', 1024, 8, 16, unhexlify('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')),
|
||||
(b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, unhexlify('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')),
|
||||
]
|
||||
|
||||
def test_scrypt(self):
|
||||
for password, salt, n, r, p, expected in self.scrypt_test_vectors:
|
||||
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# these parameters must be valid
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1)
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1, maxmem=0)
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1, dklen=1)
|
||||
|
||||
def test_scrypt_types(self):
|
||||
# password and salt must be bytes-like
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt('password', salt=b'salt', n=2, r=8, p=1)
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', salt='salt', n=2, r=8, p=1)
|
||||
# require keyword args
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', b'salt')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', 2, 8, 1, salt=b'salt')
|
||||
|
||||
def test_scrypt_validate(self):
|
||||
def scrypt(password=b"password", /, **kwargs):
|
||||
# overwrite well-defined parameters with bad ones
|
||||
kwargs = dict(salt=b'salt', n=2, r=8, p=1) | kwargs
|
||||
return hashlib.scrypt(password, **kwargs)
|
||||
|
||||
for param_name in ('n', 'r', 'p', 'maxmem', 'dklen'):
|
||||
param = {param_name: None}
|
||||
with self.subTest(**param):
|
||||
self.assertRaises(TypeError, scrypt, **param)
|
||||
|
||||
self.assertRaises(ValueError, scrypt, n=0)
|
||||
self.assertRaises(ValueError, scrypt, n=-1)
|
||||
self.assertRaises(ValueError, scrypt, n=1)
|
||||
|
||||
self.assertRaises(ValueError, scrypt, r=0)
|
||||
self.assertRaises(ValueError, scrypt, r=-1)
|
||||
|
||||
self.assertRaises(ValueError, scrypt, p=-1)
|
||||
self.assertRaises(ValueError, scrypt, p=0)
|
||||
|
||||
self.assertRaises(ValueError, scrypt, maxmem=-1)
|
||||
# OpenSSL hard limit for 'maxmem' is an 'uint64_t' but for now,
|
||||
# we do not use the 'uint64' Clinic converter but the 'long' one.
|
||||
self.assertRaises(OverflowError, scrypt, maxmem=(1 << 64))
|
||||
# Historically, Python allowed 'maxmem' to be at most INT_MAX,
|
||||
# which is at most 2**32-1 (on Windows, sizeof(long) == 4, so
|
||||
# an OverflowError will be raised instead of a ValueError).
|
||||
numeric_exc_types = (OverflowError, ValueError)
|
||||
self.assertRaises(numeric_exc_types, scrypt, maxmem=(1 << 32))
|
||||
|
||||
self.assertRaises(ValueError, scrypt, dklen=-1)
|
||||
self.assertRaises(ValueError, scrypt, dklen=0)
|
||||
MAX_DKLEN = ((1 << 32) - 1) * 32 # see RFC 7914
|
||||
self.assertRaises(numeric_exc_types, scrypt, dklen=MAX_DKLEN + 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue