X Tutup
Skip to content

Commit 3626a50

Browse files
committed
Issue python#19254: Provide an optimized Python implementation of PBKDF2_HMAC
1 parent a412f76 commit 3626a50

File tree

4 files changed

+86
-11
lines changed

4 files changed

+86
-11
lines changed

Doc/library/hashlib.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ slow and include a salt.
212212

213213
.. versionadded:: 3.4
214214

215-
.. note:: *pbkdf2_hmac* is only available with OpenSSL 1.0 and newer.
215+
.. note:: A fast implementation of *pbkdf2_hmac* is only available with
216+
OpenSSL 1.0 and newer. The Python implementation uses an inline
217+
version of :mod:`hmac` and is about three times slower. Contrary to
218+
OpenSSL's current code the length of the password has only a minimal
219+
impact on the runtime of the Python implementation.
216220

217221

218222
.. seealso::

Lib/hashlib.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
1+
#. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
22
# Licensed to PSF under a Contributor Agreement.
33
#
44

@@ -61,7 +61,7 @@
6161
algorithms_available = set(__always_supported)
6262

6363
__all__ = __always_supported + ('new', 'algorithms_guaranteed',
64-
'algorithms_available')
64+
'algorithms_available', 'pbkdf2_hmac')
6565

6666

6767
def __get_builtin_constructor(name):
@@ -147,13 +147,70 @@ def __hash_new(name, data=b''):
147147
new = __py_new
148148
__get_hash = __get_builtin_constructor
149149

150-
# PBKDF2 requires OpenSSL 1.0+ with HMAC and SHA
151150
try:
151+
# OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA
152152
from _hashlib import pbkdf2_hmac
153153
except ImportError:
154-
pass
155-
else:
156-
__all__ += ('pbkdf2_hmac',)
154+
_trans_5C = bytes((x ^ 0x5C) for x in range(256))
155+
_trans_36 = bytes((x ^ 0x36) for x in range(256))
156+
157+
def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None):
158+
"""Password based key derivation function 2 (PKCS #5 v2.0)
159+
160+
This Python implementations based on the hmac module about as fast
161+
as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster
162+
for long passwords.
163+
"""
164+
if not isinstance(hash_name, str):
165+
raise TypeError(hash_name)
166+
167+
if not isinstance(password, (bytes, bytearray)):
168+
password = bytes(memoryview(password))
169+
if not isinstance(salt, (bytes, bytearray)):
170+
salt = bytes(memoryview(salt))
171+
172+
# Fast inline HMAC implementation
173+
inner = new(hash_name)
174+
outer = new(hash_name)
175+
blocksize = getattr(inner, 'block_size', 64)
176+
if len(password) > blocksize:
177+
password = new(hash_name, password).digest()
178+
password = password + b'\x00' * (blocksize - len(password))
179+
inner.update(password.translate(_trans_36))
180+
outer.update(password.translate(_trans_5C))
181+
182+
def prf(msg, inner=inner, outer=outer):
183+
# PBKDF2_HMAC uses the password as key. We can re-use the same
184+
# digest objects and and just update copies to skip initialization.
185+
icpy = inner.copy()
186+
ocpy = outer.copy()
187+
icpy.update(msg)
188+
ocpy.update(icpy.digest())
189+
return ocpy.digest()
190+
191+
if iterations < 1:
192+
raise ValueError(iterations)
193+
if dklen is None:
194+
dklen = outer.digest_size
195+
if dklen < 1:
196+
raise ValueError(dklen)
197+
198+
dkey = b''
199+
loop = 1
200+
from_bytes = int.from_bytes
201+
while len(dkey) < dklen:
202+
prev = prf(salt + loop.to_bytes(4, 'big'))
203+
# endianess doesn't matter here as long to / from use the same
204+
rkey = int.from_bytes(prev, 'big')
205+
for i in range(iterations - 1):
206+
prev = prf(prev)
207+
# rkey = rkey ^ prev
208+
rkey ^= from_bytes(prev, 'big')
209+
loop += 1
210+
dkey += rkey.to_bytes(inner.digest_size, 'big')
211+
212+
return dkey[:dklen]
213+
157214

158215
for __func_name in __always_supported:
159216
# try them all, some may not work due to the OpenSSL

Lib/test/test_hashlib.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
import unittest
1919
import warnings
2020
from test import support
21-
from test.support import _4G, bigmemtest
21+
from test.support import _4G, bigmemtest, import_fresh_module
2222

2323
# Were we compiled --with-pydebug or with #define Py_DEBUG?
2424
COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
2525

26+
c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib'])
27+
py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib'])
2628

2729
def hexstr(s):
2830
assert isinstance(s, bytes), repr(s)
@@ -545,6 +547,10 @@ def hash_in_chunks(chunk_size, event):
545547

546548
self.assertEqual(expected_hash, hasher.hexdigest())
547549

550+
551+
class KDFTests:
552+
hashlibmod = None
553+
548554
pbkdf2_test_vectors = [
549555
(b'password', b'salt', 1, None),
550556
(b'password', b'salt', 2, None),
@@ -594,10 +600,8 @@ def hash_in_chunks(chunk_size, event):
594600
(bytes.fromhex('9d9e9c4cd21fe4be24d5b8244c759665'), None),],
595601
}
596602

597-
@unittest.skipUnless(hasattr(hashlib, 'pbkdf2_hmac'),
598-
'pbkdf2_hmac required for this test.')
599603
def test_pbkdf2_hmac(self):
600-
pbkdf2 = hashlib.pbkdf2_hmac
604+
pbkdf2 = self.hashlibmod.pbkdf2_hmac
601605

602606
for digest_name, results in self.pbkdf2_results.items():
603607
for i, vector in enumerate(self.pbkdf2_test_vectors):
@@ -628,5 +632,13 @@ def test_pbkdf2_hmac(self):
628632
pbkdf2('unknown', b'pass', b'salt', 1)
629633

630634

635+
class PyKDFTests(KDFTests, unittest.TestCase):
636+
hashlibmod = py_hashlib
637+
638+
639+
class CKDFTests(KDFTests, unittest.TestCase):
640+
hashlibmod = c_hashlib
641+
642+
631643
if __name__ == "__main__":
632644
unittest.main()

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Core and Builtins
5757
Library
5858
-------
5959

60+
- Issue #19254: Provide an optimized Python implementation of PBKDF2_HMAC.
61+
6062
- Issues #19201, #19222, #19223: Add "x" mode (exclusive creation) in opening
6163
file to bz2, gzip and lzma modules. Patches by Tim Heaney and Vajrasky Kok.
6264

0 commit comments

Comments
 (0)
X Tutup