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 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 observed_ms < 1:
139 observed_ms = 1
140 if not quiet:
141 print('%s iterations in %sms' % (iterations, observed_ms))
142 return 1.0 / target_ms * observed_ms
144 # Measure the initial guess, then estimate how many iterations it would
145 # take to reach 1/2 of the target ms and try it to get a good final number
146 fraction = _measure()
147 iterations = int(iterations / fraction / 2.0)
149 fraction = _measure()
150 iterations = iterations / fraction
152 # < 20,000 round to 1000
153 # 20,000-100,000 round to 5,000
154 # > 100,000 round to 10,000
155 round_factor = -3 if iterations < 100000 else -4
156 result = int(round(iterations, round_factor))
157 if result > 20000:
158 result = (result // 5000) * 5000
159 return result
162def pbkdf1(hash_algorithm, password, salt, iterations, key_length):
163 """
164 An implementation of PBKDF1 - should only be used for interop with legacy
165 systems, not new architectures
167 :param hash_algorithm:
168 The string name of the hash algorithm to use: "md2", "md5", "sha1"
170 :param password:
171 A byte string of the password to use an input to the KDF
173 :param salt:
174 A cryptographic random byte string
176 :param iterations:
177 The numbers of iterations to use when deriving the key
179 :param key_length:
180 The length of the desired key in bytes
182 :return:
183 The derived key as a byte string
184 """
186 if not isinstance(password, byte_cls):
187 raise TypeError(pretty_message(
188 '''
189 password must be a byte string, not %s
190 ''',
191 (type_name(password))
192 ))
194 if not isinstance(salt, byte_cls):
195 raise TypeError(pretty_message(
196 '''
197 salt must be a byte string, not %s
198 ''',
199 (type_name(salt))
200 ))
202 if not isinstance(iterations, int_types):
203 raise TypeError(pretty_message(
204 '''
205 iterations must be an integer, not %s
206 ''',
207 (type_name(iterations))
208 ))
210 if iterations < 1:
211 raise ValueError(pretty_message(
212 '''
213 iterations must be greater than 0 - is %s
214 ''',
215 repr(iterations)
216 ))
218 if not isinstance(key_length, int_types):
219 raise TypeError(pretty_message(
220 '''
221 key_length must be an integer, not %s
222 ''',
223 (type_name(key_length))
224 ))
226 if key_length < 1:
227 raise ValueError(pretty_message(
228 '''
229 key_length must be greater than 0 - is %s
230 ''',
231 repr(key_length)
232 ))
234 if hash_algorithm not in set(['md2', 'md5', 'sha1']):
235 raise ValueError(pretty_message(
236 '''
237 hash_algorithm must be one of "md2", "md5", "sha1", not %s
238 ''',
239 repr(hash_algorithm)
240 ))
242 if key_length > 16 and hash_algorithm in set(['md2', 'md5']):
243 raise ValueError(pretty_message(
244 '''
245 key_length can not be longer than 16 for %s - is %s
246 ''',
247 (hash_algorithm, repr(key_length))
248 ))
250 if key_length > 20 and hash_algorithm == 'sha1':
251 raise ValueError(pretty_message(
252 '''
253 key_length can not be longer than 20 for sha1 - is %s
254 ''',
255 repr(key_length)
256 ))
258 algo = getattr(hashlib, hash_algorithm)
259 output = algo(password + salt).digest()
260 for _ in range(2, iterations + 1):
261 output = algo(output).digest()
263 return output[:key_length]