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

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

143 

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) 

148 

149 fraction = _measure() 

150 iterations = iterations / fraction 

151 

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 

160 

161 

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 

166 

167 :param hash_algorithm: 

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

169 

170 :param password: 

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

172 

173 :param salt: 

174 A cryptographic random byte string 

175 

176 :param iterations: 

177 The numbers of iterations to use when deriving the key 

178 

179 :param key_length: 

180 The length of the desired key in bytes 

181 

182 :return: 

183 The derived key as a byte string 

184 """ 

185 

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

193 

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

201 

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

209 

210 if iterations < 1: 

211 raise ValueError(pretty_message( 

212 ''' 

213 iterations must be greater than 0 - is %s 

214 ''', 

215 repr(iterations) 

216 )) 

217 

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

225 

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

233 

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

241 

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

249 

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

257 

258 algo = getattr(hashlib, hash_algorithm) 

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

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

261 output = algo(output).digest() 

262 

263 return output[:key_length]