Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/oscrypto/kdf.py: 21%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# coding: utf-8
2from __future__ import unicode_literals, division, absolute_import, print_function
4import sys
5import hashlib
6from datetime import datetime
8from . import backend
9from .util import rand_bytes
10from ._types import type_name, byte_cls, int_types
11from ._errors import pretty_message
12from ._ffi import new, deref
15_backend = backend()
18if _backend == 'mac':
19 from ._mac.util import pbkdf2, pkcs12_kdf
20elif _backend == 'win' or _backend == 'winlegacy':
21 from ._win.util import pbkdf2, pkcs12_kdf
22 from ._win._kernel32 import kernel32, handle_error
23else:
24 from ._openssl.util import pbkdf2, pkcs12_kdf
27__all__ = [
28 'pbkdf1',
29 'pbkdf2',
30 'pbkdf2_iteration_calculator',
31 'pkcs12_kdf',
32]
35if sys.platform == 'win32':
36 def _get_start():
37 number = new(kernel32, 'LARGE_INTEGER *')
38 res = kernel32.QueryPerformanceCounter(number)
39 handle_error(res)
40 return deref(number)
42 def _get_elapsed(start):
43 length = _get_start() - start
44 return int(length / 1000.0)
46else:
47 def _get_start():
48 return datetime.now()
50 def _get_elapsed(start):
51 length = datetime.now() - start
52 seconds = length.seconds + (length.days * 24 * 3600)
53 milliseconds = (length.microseconds / 10 ** 3)
54 return int(milliseconds + (seconds * 10 ** 3))
57def pbkdf2_iteration_calculator(hash_algorithm, key_length, target_ms=100, quiet=False):
58 """
59 Runs pbkdf2() twice to determine the approximate number of iterations to
60 use to hit a desired time per run. Use this on a production machine to
61 dynamically adjust the number of iterations as high as you can.
63 :param hash_algorithm:
64 The string name of the hash algorithm to use: "md5", "sha1", "sha224",
65 "sha256", "sha384", "sha512"
67 :param key_length:
68 The length of the desired key in bytes
70 :param target_ms:
71 The number of milliseconds the derivation should take
73 :param quiet:
74 If no output should be printed as attempts are made
76 :return:
77 An integer number of iterations of PBKDF2 using the specified hash
78 that will take at least target_ms
79 """
81 if hash_algorithm not in set(['sha1', 'sha224', 'sha256', 'sha384', 'sha512']):
82 raise ValueError(pretty_message(
83 '''
84 hash_algorithm must be one of "sha1", "sha224", "sha256", "sha384",
85 "sha512", not %s
86 ''',
87 repr(hash_algorithm)
88 ))
90 if not isinstance(key_length, int_types):
91 raise TypeError(pretty_message(
92 '''
93 key_length must be an integer, not %s
94 ''',
95 type_name(key_length)
96 ))
98 if key_length < 1:
99 raise ValueError(pretty_message(
100 '''
101 key_length must be greater than 0 - is %s
102 ''',
103 repr(key_length)
104 ))
106 if not isinstance(target_ms, int_types):
107 raise TypeError(pretty_message(
108 '''
109 target_ms must be an integer, not %s
110 ''',
111 type_name(target_ms)
112 ))
114 if target_ms < 1:
115 raise ValueError(pretty_message(
116 '''
117 target_ms must be greater than 0 - is %s
118 ''',
119 repr(target_ms)
120 ))
122 if pbkdf2.pure_python:
123 raise OSError(pretty_message(
124 '''
125 Only a very slow, pure-python version of PBKDF2 is available,
126 making this function useless
127 '''
128 ))
130 iterations = 10000
131 password = 'this is a test'.encode('utf-8')
132 salt = rand_bytes(key_length)
134 def _measure():
135 start = _get_start()
136 pbkdf2(hash_algorithm, password, salt, iterations, key_length)
137 observed_ms = _get_elapsed(start)
138 if not quiet:
139 print('%s iterations in %sms' % (iterations, observed_ms))
140 return 1.0 / target_ms * observed_ms
142 # Measure the initial guess, then estimate how many iterations it would
143 # take to reach 1/2 of the target ms and try it to get a good final number
144 fraction = _measure()
145 iterations = int(iterations / fraction / 2.0)
147 fraction = _measure()
148 iterations = iterations / fraction
150 # < 20,000 round to 1000
151 # 20,000-100,000 round to 5,000
152 # > 100,000 round to 10,000
153 round_factor = -3 if iterations < 100000 else -4
154 result = int(round(iterations, round_factor))
155 if result > 20000:
156 result = (result // 5000) * 5000
157 return result
160def pbkdf1(hash_algorithm, password, salt, iterations, key_length):
161 """
162 An implementation of PBKDF1 - should only be used for interop with legacy
163 systems, not new architectures
165 :param hash_algorithm:
166 The string name of the hash algorithm to use: "md2", "md5", "sha1"
168 :param password:
169 A byte string of the password to use an input to the KDF
171 :param salt:
172 A cryptographic random byte string
174 :param iterations:
175 The numbers of iterations to use when deriving the key
177 :param key_length:
178 The length of the desired key in bytes
180 :return:
181 The derived key as a byte string
182 """
184 if not isinstance(password, byte_cls):
185 raise TypeError(pretty_message(
186 '''
187 password must be a byte string, not %s
188 ''',
189 (type_name(password))
190 ))
192 if not isinstance(salt, byte_cls):
193 raise TypeError(pretty_message(
194 '''
195 salt must be a byte string, not %s
196 ''',
197 (type_name(salt))
198 ))
200 if not isinstance(iterations, int_types):
201 raise TypeError(pretty_message(
202 '''
203 iterations must be an integer, not %s
204 ''',
205 (type_name(iterations))
206 ))
208 if iterations < 1:
209 raise ValueError(pretty_message(
210 '''
211 iterations must be greater than 0 - is %s
212 ''',
213 repr(iterations)
214 ))
216 if not isinstance(key_length, int_types):
217 raise TypeError(pretty_message(
218 '''
219 key_length must be an integer, not %s
220 ''',
221 (type_name(key_length))
222 ))
224 if key_length < 1:
225 raise ValueError(pretty_message(
226 '''
227 key_length must be greater than 0 - is %s
228 ''',
229 repr(key_length)
230 ))
232 if hash_algorithm not in set(['md2', 'md5', 'sha1']):
233 raise ValueError(pretty_message(
234 '''
235 hash_algorithm must be one of "md2", "md5", "sha1", not %s
236 ''',
237 repr(hash_algorithm)
238 ))
240 if key_length > 16 and hash_algorithm in set(['md2', 'md5']):
241 raise ValueError(pretty_message(
242 '''
243 key_length can not be longer than 16 for %s - is %s
244 ''',
245 (hash_algorithm, repr(key_length))
246 ))
248 if key_length > 20 and hash_algorithm == 'sha1':
249 raise ValueError(pretty_message(
250 '''
251 key_length can not be longer than 20 for sha1 - is %s
252 ''',
253 repr(key_length)
254 ))
256 algo = getattr(hashlib, hash_algorithm)
257 output = algo(password + salt).digest()
258 for _ in range(2, iterations + 1):
259 output = algo(output).digest()
261 return output[:key_length]