Skip to content

Commit bcd0c31

Browse files
authored
migrate kbkdfhmac to rust (#13800)
* migrate kbkdfhmac to rust * implement derive_into for kbkdfhmac * code review * internal enum * use python int_to_bytes to handle arbitrary llen sizes properly * alternate solution * feedback * ellipsis
1 parent 311398c commit bcd0c31

File tree

8 files changed

+466
-103
lines changed

8 files changed

+466
-103
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Changelog
5555
:class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHMAC`,
5656
:class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id`,
5757
:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`,
58+
:class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFHMAC`,
5859
:class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt`, and
5960
:class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF` to allow
6061
deriving keys directly into pre-allocated buffers.

docs/hazmat/primitives/key-derivation-functions.rst

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,13 +1090,40 @@ KBKDF
10901090
:raises TypeError: This exception is raised if ``key_material`` is
10911091
not ``bytes``.
10921092
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1093-
:meth:`derive` or
1093+
:meth:`derive`,
1094+
:meth:`derive_into`, or
10941095
:meth:`verify` is
10951096
called more than
10961097
once.
10971098

10981099
Derives a new key from the input key material.
10991100

1101+
.. method:: derive_into(key_material, buffer)
1102+
1103+
.. versionadded:: 47.0.0
1104+
1105+
:param key_material: The input key material.
1106+
:type key_material: :term:`bytes-like`
1107+
:param buffer: A writable buffer to write the derived key into. The
1108+
buffer must be equal to the length supplied in the
1109+
constructor.
1110+
:type buffer: :term:`bytes-like`
1111+
:return int: the number of bytes written to the buffer.
1112+
:raises ValueError: This exception is raised if the buffer length does
1113+
not match the specified ``length``.
1114+
:raises TypeError: This exception is raised if ``key_material`` or
1115+
``buffer`` is not ``bytes``.
1116+
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1117+
:meth:`derive`,
1118+
:meth:`derive_into`, or
1119+
:meth:`verify` is
1120+
called more than
1121+
once.
1122+
1123+
Derives a new key from the input key material and writes it into
1124+
the provided buffer. This is useful when you want to avoid allocating
1125+
new memory for the derived key.
1126+
11001127
.. method:: verify(key_material, expected_key)
11011128

11021129
:param bytes key_material: The input key material. This is the same as
@@ -1108,7 +1135,8 @@ KBKDF
11081135
derived key does not match
11091136
the expected key.
11101137
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1111-
:meth:`derive` or
1138+
:meth:`derive`,
1139+
:meth:`derive_into`, or
11121140
:meth:`verify` is
11131141
called more than
11141142
once.

src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import typing
66

77
from cryptography.hazmat.primitives.hashes import HashAlgorithm
8+
from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, Mode
89
from cryptography.utils import Buffer
910

1011
class PBKDF2HMAC:
@@ -162,3 +163,23 @@ class ConcatKDFHMAC:
162163
def derive(self, key_material: Buffer) -> bytes: ...
163164
def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ...
164165
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...
166+
167+
class KBKDFHMAC:
168+
def __init__(
169+
self,
170+
algorithm: HashAlgorithm,
171+
mode: Mode,
172+
length: int,
173+
rlen: int,
174+
llen: int | None,
175+
location: CounterLocation,
176+
label: bytes | None,
177+
context: bytes | None,
178+
fixed: bytes | None,
179+
backend: typing.Any = None,
180+
*,
181+
break_location: int | None = None,
182+
) -> None: ...
183+
def derive(self, key_material: Buffer) -> bytes: ...
184+
def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ...
185+
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...

src/cryptography/hazmat/primitives/kdf/kbkdf.py

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,8 @@
1414
UnsupportedAlgorithm,
1515
_Reasons,
1616
)
17-
from cryptography.hazmat.primitives import (
18-
ciphers,
19-
cmac,
20-
constant_time,
21-
hashes,
22-
hmac,
23-
)
17+
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
18+
from cryptography.hazmat.primitives import ciphers, cmac, constant_time
2419
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
2520

2621

@@ -178,62 +173,8 @@ def _generate_fixed_input(self) -> bytes:
178173
return b"".join([self._label, b"\x00", self._context, l_val])
179174

180175

181-
class KBKDFHMAC(KeyDerivationFunction):
182-
def __init__(
183-
self,
184-
algorithm: hashes.HashAlgorithm,
185-
mode: Mode,
186-
length: int,
187-
rlen: int,
188-
llen: int | None,
189-
location: CounterLocation,
190-
label: bytes | None,
191-
context: bytes | None,
192-
fixed: bytes | None,
193-
backend: typing.Any = None,
194-
*,
195-
break_location: int | None = None,
196-
):
197-
if not isinstance(algorithm, hashes.HashAlgorithm):
198-
raise UnsupportedAlgorithm(
199-
"Algorithm supplied is not a supported hash algorithm.",
200-
_Reasons.UNSUPPORTED_HASH,
201-
)
202-
203-
from cryptography.hazmat.backends.openssl.backend import (
204-
backend as ossl,
205-
)
206-
207-
if not ossl.hmac_supported(algorithm):
208-
raise UnsupportedAlgorithm(
209-
"Algorithm supplied is not a supported hmac algorithm.",
210-
_Reasons.UNSUPPORTED_HASH,
211-
)
212-
213-
self._algorithm = algorithm
214-
215-
self._deriver = _KBKDFDeriver(
216-
self._prf,
217-
mode,
218-
length,
219-
rlen,
220-
llen,
221-
location,
222-
break_location,
223-
label,
224-
context,
225-
fixed,
226-
)
227-
228-
def _prf(self, key_material: bytes) -> hmac.HMAC:
229-
return hmac.HMAC(key_material, self._algorithm)
230-
231-
def derive(self, key_material: utils.Buffer) -> bytes:
232-
return self._deriver.derive(key_material, self._algorithm.digest_size)
233-
234-
def verify(self, key_material: bytes, expected_key: bytes) -> None:
235-
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
236-
raise InvalidKey
176+
KBKDFHMAC = rust_openssl.kdf.KBKDFHMAC
177+
KeyDerivationFunction.register(KBKDFHMAC)
237178

238179

239180
class KBKDFCMAC(KeyDerivationFunction):

src/rust/src/asn1.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,31 @@ pub(crate) fn py_uint_to_big_endian_bytes<'p>(
7171
py: pyo3::Python<'p>,
7272
v: pyo3::Bound<'p, pyo3::types::PyInt>,
7373
) -> pyo3::PyResult<PyBackedBytes> {
74-
if v.lt(0)? {
75-
return Err(pyo3::exceptions::PyValueError::new_err(
76-
"Negative integers are not supported",
77-
));
78-
}
79-
8074
// Round the length up so that we prefix an extra \x00. This ensures that
8175
// integers that'd have the high bit set in their first octet are not
8276
// encoded as negative in DER.
83-
let n = v
77+
let length = v
8478
.call_method0(pyo3::intern!(py, "bit_length"))?
8579
.extract::<usize>()?
8680
/ 8
8781
+ 1;
88-
Ok(v.call_method1(pyo3::intern!(py, "to_bytes"), (n, "big"))?
89-
.extract()?)
82+
py_uint_to_be_bytes_with_length(py, v, length)
83+
}
84+
85+
pub(crate) fn py_uint_to_be_bytes_with_length<'p>(
86+
py: pyo3::Python<'p>,
87+
v: pyo3::Bound<'p, pyo3::types::PyInt>,
88+
length: usize,
89+
) -> pyo3::PyResult<PyBackedBytes> {
90+
if v.lt(0)? {
91+
return Err(pyo3::exceptions::PyValueError::new_err(
92+
"Negative integers are not supported",
93+
));
94+
}
95+
Ok(
96+
v.call_method1(pyo3::intern!(py, "to_bytes"), (length, "big"))?
97+
.extract()?,
98+
)
9099
}
91100

92101
pub(crate) fn encode_der_data<'p>(

0 commit comments

Comments
 (0)