diff --git a/Doc/library/crypt.rst b/Doc/library/crypt.rst index d25c626a175854a..50d949244f64253 100644 --- a/Doc/library/crypt.rst +++ b/Doc/library/crypt.rst @@ -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 @@ -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 ----------------- diff --git a/Lib/crypt.py b/Lib/crypt.py index 33dbc46bb3e96be..98aa43c5ce32068 100644 --- a/Lib/crypt.py +++ b/Lib/crypt.py @@ -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: @@ -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) diff --git a/Lib/test/test_crypt.py b/Lib/test/test_crypt.py index 5dc83b4ecbfa008..7d22c87496180dd 100644 --- a/Lib/test/test_crypt.py +++ b/Lib/test/test_crypt.py @@ -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): diff --git a/Misc/NEWS.d/next/Library/2021-06-04-11-19-28.bpo-44309.PBcd1f.rst b/Misc/NEWS.d/next/Library/2021-06-04-11-19-28.bpo-44309.PBcd1f.rst new file mode 100644 index 000000000000000..0056ee81e23ea88 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-06-04-11-19-28.bpo-44309.PBcd1f.rst @@ -0,0 +1 @@ +Add support for the yescrypt hashing method in the crypt module. \ No newline at end of file