@@ -164,10 +164,178 @@ impl Scrypt {
164
164
}
165
165
}
166
166
167
+ #[ pyo3:: pyclass( module = "cryptography.hazmat.primitives.kdf.argon2" ) ]
168
+ struct Argon2id {
169
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
170
+ salt : pyo3:: Py < pyo3:: types:: PyBytes > ,
171
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
172
+ length : usize ,
173
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
174
+ iterations : u32 ,
175
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
176
+ lanes : u32 ,
177
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
178
+ memory_cost : u32 ,
179
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
180
+ ad : Option < pyo3:: Py < pyo3:: types:: PyBytes > > ,
181
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
182
+ secret : Option < pyo3:: Py < pyo3:: types:: PyBytes > > ,
183
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
184
+ used : bool ,
185
+ }
186
+
187
+ #[ pyo3:: pymethods]
188
+ impl Argon2id {
189
+ #[ new]
190
+ #[ pyo3( signature = ( salt, length, iterations, lanes, memory_cost, ad=None , secret=None ) ) ]
191
+ #[ allow( clippy:: too_many_arguments) ]
192
+ fn new (
193
+ py : pyo3:: Python < ' _ > ,
194
+ salt : pyo3:: Py < pyo3:: types:: PyBytes > ,
195
+ length : usize ,
196
+ iterations : u32 ,
197
+ lanes : u32 ,
198
+ memory_cost : u32 ,
199
+ ad : Option < pyo3:: Py < pyo3:: types:: PyBytes > > ,
200
+ secret : Option < pyo3:: Py < pyo3:: types:: PyBytes > > ,
201
+ ) -> CryptographyResult < Self > {
202
+ cfg_if:: cfg_if! {
203
+ if #[ cfg( not( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ) ] {
204
+ _ = py;
205
+ _ = salt;
206
+ _ = length;
207
+ _ = iterations;
208
+ _ = lanes;
209
+ _ = memory_cost;
210
+ _ = ad;
211
+ _ = secret;
212
+
213
+ Err ( CryptographyError :: from(
214
+ exceptions:: UnsupportedAlgorithm :: new_err(
215
+ "This version of OpenSSL does not support argon2id"
216
+ ) ,
217
+ ) )
218
+ } else {
219
+ if cryptography_openssl:: fips:: is_enabled( ) {
220
+ return Err ( CryptographyError :: from(
221
+ exceptions:: UnsupportedAlgorithm :: new_err(
222
+ "This version of OpenSSL does not support argon2id"
223
+ ) ,
224
+ ) ) ;
225
+ }
226
+
227
+ if salt. as_bytes( py) . len( ) < 8 {
228
+ return Err ( CryptographyError :: from(
229
+ pyo3:: exceptions:: PyValueError :: new_err(
230
+ "salt must be at least 8 bytes"
231
+ ) ,
232
+ ) ) ;
233
+ }
234
+ if length < 4 {
235
+ return Err ( CryptographyError :: from(
236
+ pyo3:: exceptions:: PyValueError :: new_err(
237
+ "length must be greater than or equal to 4."
238
+ ) ,
239
+ ) ) ;
240
+ }
241
+ if iterations < 1 {
242
+ return Err ( CryptographyError :: from(
243
+ pyo3:: exceptions:: PyValueError :: new_err(
244
+ "iterations must be greater than or equal to 1."
245
+ ) ,
246
+ ) ) ;
247
+ }
248
+ if lanes < 1 {
249
+ return Err ( CryptographyError :: from(
250
+ pyo3:: exceptions:: PyValueError :: new_err(
251
+ "lanes must be greater than or equal to 1."
252
+ ) ,
253
+ ) ) ;
254
+ }
255
+
256
+ if memory_cost / 8 < lanes {
257
+ return Err ( CryptographyError :: from(
258
+ pyo3:: exceptions:: PyValueError :: new_err(
259
+ "memory_cost must be an integer >= 8 * lanes."
260
+ ) ,
261
+ ) ) ;
262
+ }
263
+
264
+
265
+ Ok ( Argon2id {
266
+ salt,
267
+ length,
268
+ iterations,
269
+ lanes,
270
+ memory_cost,
271
+ ad,
272
+ secret,
273
+ used: false ,
274
+ } )
275
+ }
276
+ }
277
+ }
278
+
279
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
280
+ fn derive < ' p > (
281
+ & mut self ,
282
+ py : pyo3:: Python < ' p > ,
283
+ key_material : CffiBuf < ' _ > ,
284
+ ) -> CryptographyResult < pyo3:: Bound < ' p , pyo3:: types:: PyBytes > > {
285
+ if self . used {
286
+ return Err ( exceptions:: already_finalized_error ( ) ) ;
287
+ }
288
+ self . used = true ;
289
+ Ok ( pyo3:: types:: PyBytes :: new_bound_with (
290
+ py,
291
+ self . length ,
292
+ |b| {
293
+ openssl:: kdf:: argon2id (
294
+ None ,
295
+ key_material. as_bytes ( ) ,
296
+ self . salt . as_bytes ( py) ,
297
+ self . ad . as_ref ( ) . map ( |ad| ad. as_bytes ( py) ) ,
298
+ self . secret . as_ref ( ) . map ( |secret| secret. as_bytes ( py) ) ,
299
+ self . iterations ,
300
+ self . lanes ,
301
+ self . memory_cost ,
302
+ b,
303
+ )
304
+ . map_err ( CryptographyError :: from) ?;
305
+ Ok ( ( ) )
306
+ } ,
307
+ ) ?)
308
+ }
309
+
310
+ #[ cfg( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER ) ]
311
+ fn verify (
312
+ & mut self ,
313
+ py : pyo3:: Python < ' _ > ,
314
+ key_material : CffiBuf < ' _ > ,
315
+ expected_key : CffiBuf < ' _ > ,
316
+ ) -> CryptographyResult < ( ) > {
317
+ let actual = self . derive ( py, key_material) ?;
318
+ let actual_bytes = actual. as_bytes ( ) ;
319
+ let expected_bytes = expected_key. as_bytes ( ) ;
320
+
321
+ if actual_bytes. len ( ) != expected_bytes. len ( )
322
+ || !openssl:: memcmp:: eq ( actual_bytes, expected_bytes)
323
+ {
324
+ return Err ( CryptographyError :: from ( exceptions:: InvalidKey :: new_err (
325
+ "Keys do not match." ,
326
+ ) ) ) ;
327
+ }
328
+
329
+ Ok ( ( ) )
330
+ }
331
+ }
332
+
167
333
#[ pyo3:: pymodule]
168
334
pub ( crate ) mod kdf {
169
335
#[ pymodule_export]
170
336
use super :: derive_pbkdf2_hmac;
171
337
#[ pymodule_export]
338
+ use super :: Argon2id ;
339
+ #[ pymodule_export]
172
340
use super :: Scrypt ;
173
341
}
0 commit comments