Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Doc/library/crypt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ are available on all platforms):
.. data:: METHOD_SHA512

A Modular Crypt Format method with 16 character salt and 86 character
hash based on the SHA-512 hash function. This is the strongest method.
hash based on the SHA-512 hash function. This is the strongest method
supported on all UNIX-based operating systems.

.. data:: METHOD_SHA256

Expand All @@ -67,6 +68,14 @@ are available on all platforms):
The traditional method with a 2 character salt and 13 characters of
hash. This is the weakest method.

.. versionadded:: 3.10

.. data:: METHOD_YESCRYPT

Another Modular Crypt Format method with 24 character salt and 43
character hash based on the yescrypt hash function. This is the
strongest method supported on Linux distributions using libxcrypt.


Module Attributes
-----------------
Expand Down
13 changes: 12 additions & 1 deletion Lib/crypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ def mksalt(method=None, *, rounds=None):
else: # modular
s = f'${method.ident}$'

if method.ident and method.ident[0] == '2': # Blowfish variants
if method.ident and method.ident == 'y': # yescrypt
if rounds is not None:
if not 1 <= rounds <= 11:
raise ValueError('rounds out of the range 1 to 11')
else:
rounds = 5
s += 'j' + ('75', '85', '7T', '8T', '9T', 'AT', 'BT', 'CT', 'DT', 'ET', 'FT')[rounds - 1] + '$'
elif method.ident and method.ident[0] == '2': # Blowfish variants
if rounds is None:
log_rounds = 12
else:
Expand Down Expand Up @@ -102,6 +109,10 @@ def _add_method(name, *args, rounds=None):
return True
return False

# Supported by libxcrypt. Strongest hashing method currently supported.
_add_method('YESCRYPT', 'y', 24, 75, rounds=1)

# SHA-2 based methods.
_add_method('SHA512', '6', 16, 106)
_add_method('SHA256', '5', 16, 63)

Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_crypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,29 @@ def test_blowfish_rounds(self):
cr2 = crypt.crypt('mypassword', cr)
self.assertEqual(cr2, cr)

@unittest.skipUnless(
crypt and crypt.METHOD_YESCRYPT in crypt.methods, 'requires support of yescrypt'
)
def test_yescrypt_rounds(self):
for rounds in range(1, 11):
enc_rounds = 'j' + ('75', '85', '7T', '8T', '9T', 'AT', 'BT', 'CT', 'DT', 'ET', 'FT')[rounds - 1]
salt = crypt.mksalt(crypt.METHOD_YESCRYPT, rounds=rounds)
self.assertIn('$%s$' % enc_rounds, salt)
self.assertEqual(len(salt) - crypt.METHOD_YESCRYPT.salt_chars, 7)
cr = crypt.crypt('mypassword', salt)
self.assertTrue(cr)
cr2 = crypt.crypt('mypassword', cr)
self.assertEqual(cr2, cr)

def test_invalid_rounds(self):
if crypt.METHOD_YESCRYPT in crypt.methods:
with self.assertRaises(TypeError):
crypt.mksalt(crypt.METHOD_YESCRYPT, rounds='4096')
with self.assertRaises(TypeError):
crypt.mksalt(crypt.METHOD_YESCRYPT, rounds=4096.0)
for rounds in (0, -1, 1000, 1<<999):
with self.assertRaises(ValueError):
crypt.mksalt(crypt.METHOD_YESCRYPT, rounds=rounds)
for method in (crypt.METHOD_SHA256, crypt.METHOD_SHA512,
crypt.METHOD_BLOWFISH):
with self.assertRaises(TypeError):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for the yescrypt hashing method in the crypt module.