Skip to content

Commit 71d5299

Browse files
authored
gh-94199: Remove hashlib.pbkdf2_hmac() Python implementation (GH-94200)
Remove the pure Python implementation of hashlib.pbkdf2_hmac(), deprecated in Python 3.10. Python 3.10 and newer requires OpenSSL 1.1.1 or newer (PEP 644), this OpenSSL version provides a C implementation of pbkdf2_hmac() which is faster.
1 parent 5c5fc9d commit 71d5299

File tree

5 files changed

+22
-87
lines changed

5 files changed

+22
-87
lines changed

Doc/library/hashlib.rst

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -300,23 +300,17 @@ include a `salt <https://en.wikipedia.org/wiki/Salt_%28cryptography%29>`_.
300300

301301
>>> from hashlib import pbkdf2_hmac
302302
>>> our_app_iters = 500_000 # Application specific, read above.
303-
>>> dk = pbkdf2_hmac('sha256', b'password', b'bad salt'*2, our_app_iters)
303+
>>> dk = pbkdf2_hmac('sha256', b'password', b'bad salt' * 2, our_app_iters)
304304
>>> dk.hex()
305305
'15530bba69924174860db778f2c6f8104d3aaf9d26241840c8c4a641c8d000a9'
306306

307-
.. versionadded:: 3.4
308-
309-
.. note::
307+
Function only available when Python is compiled with OpenSSL.
310308

311-
A fast implementation of *pbkdf2_hmac* is available with OpenSSL. The
312-
Python implementation uses an inline version of :mod:`hmac`. It is about
313-
three times slower and doesn't release the GIL.
314-
315-
.. deprecated:: 3.10
309+
.. versionadded:: 3.4
316310

317-
Slow Python implementation of *pbkdf2_hmac* is deprecated. In the
318-
future the function will only be available when Python is compiled
319-
with OpenSSL.
311+
.. versionchanged:: 3.12
312+
Function now only available when Python is built with OpenSSL. The slow
313+
pure Python implementation has been removed.
320314

321315
.. function:: scrypt(password, *, salt, n, r, p, maxmem=0, dklen=64)
322316

Doc/whatsnew/3.12.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ Removed
273273
use :func:`locale.format_string` instead.
274274
(Contributed by Victor Stinner in :gh:`94226`.)
275275

276+
* :mod:`hashlib`: Remove the pure Python implementation of
277+
:func:`hashlib.pbkdf2_hmac()`, deprecated in Python 3.10. Python 3.10 and
278+
newer requires OpenSSL 1.1.1 (:pep:`644`): this OpenSSL version provides
279+
a C implementation of :func:`~hashlib.pbkdf2_hmac()` which is faster.
280+
(Contributed by Victor Stinner in :gh:`94199`.)
281+
276282

277283
Porting to Python 3.12
278284
======================

Lib/hashlib.py

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
algorithms_available = set(__always_supported)
6666

6767
__all__ = __always_supported + ('new', 'algorithms_guaranteed',
68-
'algorithms_available', 'pbkdf2_hmac', 'file_digest')
68+
'algorithms_available', 'file_digest')
6969

7070

7171
__builtin_constructor_cache = {}
@@ -180,72 +180,10 @@ def __hash_new(name, data=b'', **kwargs):
180180
try:
181181
# OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA
182182
from _hashlib import pbkdf2_hmac
183+
__all__ += ('pbkdf2_hmac',)
183184
except ImportError:
184-
from warnings import warn as _warn
185-
_trans_5C = bytes((x ^ 0x5C) for x in range(256))
186-
_trans_36 = bytes((x ^ 0x36) for x in range(256))
187-
188-
def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None):
189-
"""Password based key derivation function 2 (PKCS #5 v2.0)
190-
191-
This Python implementations based on the hmac module about as fast
192-
as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster
193-
for long passwords.
194-
"""
195-
_warn(
196-
"Python implementation of pbkdf2_hmac() is deprecated.",
197-
category=DeprecationWarning,
198-
stacklevel=2
199-
)
200-
if not isinstance(hash_name, str):
201-
raise TypeError(hash_name)
202-
203-
if not isinstance(password, (bytes, bytearray)):
204-
password = bytes(memoryview(password))
205-
if not isinstance(salt, (bytes, bytearray)):
206-
salt = bytes(memoryview(salt))
207-
208-
# Fast inline HMAC implementation
209-
inner = new(hash_name)
210-
outer = new(hash_name)
211-
blocksize = getattr(inner, 'block_size', 64)
212-
if len(password) > blocksize:
213-
password = new(hash_name, password).digest()
214-
password = password + b'\x00' * (blocksize - len(password))
215-
inner.update(password.translate(_trans_36))
216-
outer.update(password.translate(_trans_5C))
217-
218-
def prf(msg, inner=inner, outer=outer):
219-
# PBKDF2_HMAC uses the password as key. We can re-use the same
220-
# digest objects and just update copies to skip initialization.
221-
icpy = inner.copy()
222-
ocpy = outer.copy()
223-
icpy.update(msg)
224-
ocpy.update(icpy.digest())
225-
return ocpy.digest()
226-
227-
if iterations < 1:
228-
raise ValueError(iterations)
229-
if dklen is None:
230-
dklen = outer.digest_size
231-
if dklen < 1:
232-
raise ValueError(dklen)
233-
234-
dkey = b''
235-
loop = 1
236-
from_bytes = int.from_bytes
237-
while len(dkey) < dklen:
238-
prev = prf(salt + loop.to_bytes(4))
239-
# endianness doesn't matter here as long to / from use the same
240-
rkey = from_bytes(prev)
241-
for i in range(iterations - 1):
242-
prev = prf(prev)
243-
# rkey = rkey ^ prev
244-
rkey ^= from_bytes(prev)
245-
loop += 1
246-
dkey += rkey.to_bytes(inner.digest_size)
247-
248-
return dkey[:dklen]
185+
pass
186+
249187

250188
try:
251189
# OpenSSL's scrypt requires OpenSSL 1.1+

Lib/test/test_hashlib.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,15 +1096,7 @@ def _test_pbkdf2_hmac(self, pbkdf2, supported):
10961096
iterations=1, dklen=None)
10971097
self.assertEqual(out, self.pbkdf2_results['sha1'][0][0])
10981098

1099-
@unittest.skipIf(builtin_hashlib is None, "test requires builtin_hashlib")
1100-
def test_pbkdf2_hmac_py(self):
1101-
with warnings_helper.check_warnings():
1102-
self._test_pbkdf2_hmac(
1103-
builtin_hashlib.pbkdf2_hmac, builtin_hashes
1104-
)
1105-
1106-
@unittest.skipUnless(hasattr(openssl_hashlib, 'pbkdf2_hmac'),
1107-
' test requires OpenSSL > 1.0')
1099+
@unittest.skipIf(openssl_hashlib is None, "requires OpenSSL bindings")
11081100
def test_pbkdf2_hmac_c(self):
11091101
self._test_pbkdf2_hmac(openssl_hashlib.pbkdf2_hmac, openssl_md_meth_names)
11101102

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:mod:`hashlib`: Remove the pure Python implementation of
2+
:func:`hashlib.pbkdf2_hmac()`, deprecated in Python 3.10. Python 3.10 and
3+
newer requires OpenSSL 1.1.1 (:pep:`644`): this OpenSSL version provides
4+
a C implementation of :func:`~hashlib.pbkdf2_hmac()` which is faster. Patch
5+
by Victor Stinner.

0 commit comments

Comments
 (0)