1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import annotations
6
7import typing
8
9from cryptography import utils
10from cryptography.exceptions import (
11 AlreadyFinalized,
12 InvalidKey,
13 UnsupportedAlgorithm,
14 _Reasons,
15)
16from cryptography.hazmat.bindings._rust import openssl as rust_openssl
17from cryptography.hazmat.primitives import constant_time, hashes
18from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
19
20
21class PBKDF2HMAC(KeyDerivationFunction):
22 def __init__(
23 self,
24 algorithm: hashes.HashAlgorithm,
25 length: int,
26 salt: bytes,
27 iterations: int,
28 backend: typing.Any = None,
29 ):
30 from cryptography.hazmat.backends.openssl.backend import (
31 backend as ossl,
32 )
33
34 if not ossl.pbkdf2_hmac_supported(algorithm):
35 raise UnsupportedAlgorithm(
36 f"{algorithm.name} is not supported for PBKDF2.",
37 _Reasons.UNSUPPORTED_HASH,
38 )
39 self._used = False
40 self._algorithm = algorithm
41 self._length = length
42 utils._check_bytes("salt", salt)
43 self._salt = salt
44 self._iterations = iterations
45
46 def derive(self, key_material: utils.Buffer) -> bytes:
47 if self._used:
48 raise AlreadyFinalized("PBKDF2 instances can only be used once.")
49 self._used = True
50
51 return rust_openssl.kdf.derive_pbkdf2_hmac(
52 key_material,
53 self._algorithm,
54 self._salt,
55 self._iterations,
56 self._length,
57 )
58
59 def verify(self, key_material: bytes, expected_key: bytes) -> None:
60 derived_key = self.derive(key_material)
61 if not constant_time.bytes_eq(derived_key, expected_key):
62 raise InvalidKey("Keys do not match.")