cpython: e73627483d2f (original) (raw)
Mercurial > cpython
changeset 86467:e73627483d2f
Issue #19254: Provide an optimized Python implementation of PBKDF2_HMAC [#19254]
Christian Heimes christian@cheimes.de | |
---|---|
date | Sat, 19 Oct 2013 14:12:02 +0200 |
parents | 47618b00405b |
children | b8987863f74d |
files | Doc/library/hashlib.rst Lib/hashlib.py Lib/test/test_hashlib.py Misc/NEWS |
diffstat | 4 files changed, 86 insertions(+), 11 deletions(-)[+] [-] Doc/library/hashlib.rst 6 Lib/hashlib.py 69 Lib/test/test_hashlib.py 20 Misc/NEWS 2 |
line wrap: on
line diff
--- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -212,7 +212,11 @@ slow and include a salt. .. versionadded:: 3.4
- .. note:: A fast implementation of pbkdf2_hmac is only available with
OpenSSL 1.0 and newer. The Python implementation uses an inline[](#l1.9)
version of :mod:`hmac` and is about three times slower. Contrary to[](#l1.10)
OpenSSL's current code the length of the password has only a minimal[](#l1.11)
impact on the runtime of the Python implementation.[](#l1.12)
--- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) +#. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
Licensed to PSF under a Contributor Agreement.
# @@ -61,7 +61,7 @@ algorithms_guaranteed = set(__always_sup algorithms_available = set(__always_supported) all = __always_supported + ('new', 'algorithms_guaranteed',
'algorithms_available')[](#l2.13)
'algorithms_available', 'pbkdf2_hmac')[](#l2.14)
def __get_builtin_constructor(name): @@ -147,13 +147,70 @@ except ImportError: new = __py_new __get_hash = __get_builtin_constructor -# PBKDF2 requires OpenSSL 1.0+ with HMAC and SHA try:
from _hashlib import pbkdf2_hmac OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA except ImportError:
- def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None):
"""Password based key derivation function 2 (PKCS #5 v2.0)[](#l2.34)
This Python implementations based on the hmac module about as fast[](#l2.36)
as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster[](#l2.37)
for long passwords.[](#l2.38)
"""[](#l2.39)
if not isinstance(hash_name, str):[](#l2.40)
raise TypeError(hash_name)[](#l2.41)
if not isinstance(password, (bytes, bytearray)):[](#l2.43)
password = bytes(memoryview(password))[](#l2.44)
if not isinstance(salt, (bytes, bytearray)):[](#l2.45)
salt = bytes(memoryview(salt))[](#l2.46)
# Fast inline HMAC implementation[](#l2.48)
inner = new(hash_name)[](#l2.49)
outer = new(hash_name)[](#l2.50)
blocksize = getattr(inner, 'block_size', 64)[](#l2.51)
if len(password) > blocksize:[](#l2.52)
password = new(hash_name, password).digest()[](#l2.53)
password = password + b'\x00' * (blocksize - len(password))[](#l2.54)
inner.update(password.translate(_trans_36))[](#l2.55)
outer.update(password.translate(_trans_5C))[](#l2.56)
def prf(msg, inner=inner, outer=outer):[](#l2.58)
# PBKDF2_HMAC uses the password as key. We can re-use the same[](#l2.59)
# digest objects and and just update copies to skip initialization.[](#l2.60)
icpy = inner.copy()[](#l2.61)
ocpy = outer.copy()[](#l2.62)
icpy.update(msg)[](#l2.63)
ocpy.update(icpy.digest())[](#l2.64)
return ocpy.digest()[](#l2.65)
if iterations < 1:[](#l2.67)
raise ValueError(iterations)[](#l2.68)
if dklen is None:[](#l2.69)
dklen = outer.digest_size[](#l2.70)
if dklen < 1:[](#l2.71)
raise ValueError(dklen)[](#l2.72)
dkey = b''[](#l2.74)
loop = 1[](#l2.75)
from_bytes = int.from_bytes[](#l2.76)
while len(dkey) < dklen:[](#l2.77)
prev = prf(salt + loop.to_bytes(4, 'big'))[](#l2.78)
# endianess doesn't matter here as long to / from use the same[](#l2.79)
rkey = int.from_bytes(prev, 'big')[](#l2.80)
for i in range(iterations - 1):[](#l2.81)
prev = prf(prev)[](#l2.82)
# rkey = rkey ^ prev[](#l2.83)
rkey ^= from_bytes(prev, 'big')[](#l2.84)
loop += 1[](#l2.85)
dkey += rkey.to_bytes(inner.digest_size, 'big')[](#l2.86)
return dkey[:dklen][](#l2.88)
+ for __func_name in __always_supported: # try them all, some may not work due to the OpenSSL
--- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -18,11 +18,13 @@ except ImportError: import unittest import warnings from test import support -from test.support import _4G, bigmemtest +from test.support import _4G, bigmemtest, import_fresh_module
Were we compiled --with-pydebug or with #define Py_DEBUG?
COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') +c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) +py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib']) def hexstr(s): assert isinstance(s, bytes), repr(s) @@ -545,6 +547,10 @@ class HashLibTestCase(unittest.TestCase) self.assertEqual(expected_hash, hasher.hexdigest()) + +class KDFTests:
+ pbkdf2_test_vectors = [ (b'password', b'salt', 1, None), (b'password', b'salt', 2, None), @@ -594,10 +600,8 @@ class HashLibTestCase(unittest.TestCase) (bytes.fromhex('9d9e9c4cd21fe4be24d5b8244c759665'), None),], }
- @unittest.skipUnless(hasattr(hashlib, 'pbkdf2_hmac'),
def test_pbkdf2_hmac(self):'pbkdf2_hmac required for this test.')[](#l3.34)
pbkdf2 = hashlib.pbkdf2_hmac[](#l3.36)
pbkdf2 = self.hashlibmod.pbkdf2_hmac[](#l3.37)
for digest_name, results in self.pbkdf2_results.items(): for i, vector in enumerate(self.pbkdf2_test_vectors): @@ -628,5 +632,13 @@ class HashLibTestCase(unittest.TestCase) pbkdf2('unknown', b'pass', b'salt', 1) +class PyKDFTests(KDFTests, unittest.TestCase):
+ + +class CKDFTests(KDFTests, unittest.TestCase):
+ + if name == "main": unittest.main()
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -57,6 +57,8 @@ Core and Builtins Library ------- +- Issue #19254: Provide an optimized Python implementation of PBKDF2_HMAC. +