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

90 statements  

1# coding: utf-8 

2from __future__ import unicode_literals, division, absolute_import, print_function 

3 

4import sys 

5import hashlib 

6from datetime import datetime 

7 

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 

13 

14 

15_backend = backend() 

16 

17 

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 

25 

26 

27__all__ = [ 

28 'pbkdf1', 

29 'pbkdf2', 

30 'pbkdf2_iteration_calculator', 

31 'pkcs12_kdf', 

32] 

33 

34 

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) 

41 

42 def _get_elapsed(start): 

43 length = _get_start() - start 

44 return int(length / 1000.0) 

45 

46else: 

47 def _get_start(): 

48 return datetime.now() 

49 

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)) 

55 

56 

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. 

62 

63 :param hash_algorithm: 

64 The string name of the hash algorithm to use: "md5", "sha1", "sha224", 

65 "sha256", "sha384", "sha512" 

66 

67 :param key_length: 

68 The length of the desired key in bytes 

69 

70 :param target_ms: 

71 The number of milliseconds the derivation should take 

72 

73 :param quiet: 

74 If no output should be printed as attempts are made 

75 

76 :return: 

77 An integer number of iterations of PBKDF2 using the specified hash 

78 that will take at least target_ms 

79 """ 

80 

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 )) 

89 

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 )) 

97 

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 )) 

105 

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 )) 

113 

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 )) 

121 

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 )) 

129 

130 iterations = 10000 

131 password = 'this is a test'.encode('utf-8') 

132 salt = rand_bytes(key_length) 

133 

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 

141 

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) 

146 

147 fraction = _measure() 

148 iterations = iterations / fraction 

149 

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 

158 

159 

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 

164 

165 :param hash_algorithm: 

166 The string name of the hash algorithm to use: "md2", "md5", "sha1" 

167 

168 :param password: 

169 A byte string of the password to use an input to the KDF 

170 

171 :param salt: 

172 A cryptographic random byte string 

173 

174 :param iterations: 

175 The numbers of iterations to use when deriving the key 

176 

177 :param key_length: 

178 The length of the desired key in bytes 

179 

180 :return: 

181 The derived key as a byte string 

182 """ 

183 

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 )) 

191 

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 )) 

199 

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 )) 

207 

208 if iterations < 1: 

209 raise ValueError(pretty_message( 

210 ''' 

211 iterations must be greater than 0 - is %s 

212 ''', 

213 repr(iterations) 

214 )) 

215 

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 )) 

223 

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 )) 

231 

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 )) 

239 

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 )) 

247 

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 )) 

255 

256 algo = getattr(hashlib, hash_algorithm) 

257 output = algo(password + salt).digest() 

258 for _ in range(2, iterations + 1): 

259 output = algo(output).digest() 

260 

261 return output[:key_length]