|
1 | | -# Copyright (C) 2005-2010 Gregory P. Smith ([email protected]) |
| 1 | +#. Copyright (C) 2005-2010 Gregory P. Smith ([email protected]) |
2 | 2 | # Licensed to PSF under a Contributor Agreement. |
3 | 3 | # |
4 | 4 |
|
|
61 | 61 | algorithms_available = set(__always_supported) |
62 | 62 |
|
63 | 63 | __all__ = __always_supported + ('new', 'algorithms_guaranteed', |
64 | | - 'algorithms_available') |
| 64 | + 'algorithms_available', 'pbkdf2_hmac') |
65 | 65 |
|
66 | 66 |
|
67 | 67 | def __get_builtin_constructor(name): |
@@ -147,13 +147,70 @@ def __hash_new(name, data=b''): |
147 | 147 | new = __py_new |
148 | 148 | __get_hash = __get_builtin_constructor |
149 | 149 |
|
150 | | -# PBKDF2 requires OpenSSL 1.0+ with HMAC and SHA |
151 | 150 | try: |
| 151 | + # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA |
152 | 152 | from _hashlib import pbkdf2_hmac |
153 | 153 | 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 | + |
157 | 214 |
|
158 | 215 | for __func_name in __always_supported: |
159 | 216 | # try them all, some may not work due to the OpenSSL |
|
0 commit comments