Skip to content

Commit 36aece8

Browse files
authored
implement derive_into for x963kdf (#13795)
1 parent 8b5ff0e commit 36aece8

File tree

5 files changed

+122
-33
lines changed

5 files changed

+122
-33
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ Changelog
4949
:class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`,
5050
:class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand`,
5151
:class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id`,
52-
:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, and
53-
:class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` to allow
52+
:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`,
53+
:class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt`, and
54+
:class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF` to allow
5455
deriving keys directly into pre-allocated buffers.
5556
* Added ``encrypt_into`` methods to
5657
:class:`~cryptography.hazmat.primitives.ciphers.aead.AESCCM`,

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,13 +1269,40 @@ X963KDF
12691269
:raises TypeError: This exception is raised if ``key_material`` is
12701270
not ``bytes``.
12711271
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1272-
:meth:`derive` or
1272+
:meth:`derive`,
1273+
:meth:`derive_into`, or
12731274
:meth:`verify` is
12741275
called more than
12751276
once.
12761277

12771278
Derives a new key from the input key material.
12781279

1280+
.. method:: derive_into(key_material, buffer)
1281+
1282+
.. versionadded:: 47.0.0
1283+
1284+
:param key_material: The input key material.
1285+
:type key_material: :term:`bytes-like`
1286+
:param buffer: A writable buffer to write the derived key into. The
1287+
buffer must be equal to the length supplied in the
1288+
constructor.
1289+
:type buffer: :term:`bytes-like`
1290+
:return int: the number of bytes written to the buffer.
1291+
:raises ValueError: This exception is raised if the buffer length does
1292+
not match the specified ``length``.
1293+
:raises TypeError: This exception is raised if ``key_material`` or
1294+
``buffer`` is not ``bytes``.
1295+
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1296+
:meth:`derive`,
1297+
:meth:`derive_into`, or
1298+
:meth:`verify` is
1299+
called more than
1300+
once.
1301+
1302+
Derives a new key from the input key material and writes it into
1303+
the provided buffer. This is useful when you want to avoid allocating
1304+
new memory for the derived key.
1305+
12791306
.. method:: verify(key_material, expected_key)
12801307

12811308
:param bytes key_material: The input key material. This is the same as
@@ -1287,7 +1314,8 @@ X963KDF
12871314
derived key does not match
12881315
the expected key.
12891316
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
1290-
:meth:`derive` or
1317+
:meth:`derive`,
1318+
:meth:`derive_into`, or
12911319
:meth:`verify` is
12921320
called more than
12931321
once.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class X963KDF:
9393
backend: typing.Any = None,
9494
) -> None: ...
9595
def derive(self, key_material: Buffer) -> bytes: ...
96+
def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ...
9697
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...
9798

9899
class ConcatKDFHash:

src/rust/src/backend/kdf.rs

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,55 @@ struct X963Kdf {
944944
used: bool,
945945
}
946946

947+
impl X963Kdf {
948+
fn derive_into_buffer(
949+
&mut self,
950+
py: pyo3::Python<'_>,
951+
key_material: &[u8],
952+
output: &mut [u8],
953+
) -> CryptographyResult<usize> {
954+
if self.used {
955+
return Err(exceptions::already_finalized_error());
956+
}
957+
self.used = true;
958+
959+
if output.len() != self.length {
960+
return Err(CryptographyError::from(
961+
pyo3::exceptions::PyValueError::new_err(format!(
962+
"buffer must be {} bytes",
963+
self.length
964+
)),
965+
));
966+
}
967+
968+
let algorithm_bound = self.algorithm.bind(py);
969+
let digest_size = algorithm_bound
970+
.getattr(pyo3::intern!(py, "digest_size"))?
971+
.extract::<usize>()?;
972+
973+
let mut pos = 0usize;
974+
let mut counter = 1u32;
975+
976+
while pos < self.length {
977+
let mut hash_obj = hashes::Hash::new(py, algorithm_bound, None)?;
978+
hash_obj.update_bytes(key_material)?;
979+
hash_obj.update_bytes(&counter.to_be_bytes())?;
980+
if let Some(ref sharedinfo) = self.sharedinfo {
981+
hash_obj.update_bytes(sharedinfo.as_bytes(py))?;
982+
}
983+
let block = hash_obj.finalize(py)?;
984+
let block_bytes = block.as_bytes();
985+
986+
let copy_len = (self.length - pos).min(digest_size);
987+
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]);
988+
pos += copy_len;
989+
counter += 1;
990+
}
991+
992+
Ok(self.length)
993+
}
994+
}
995+
947996
#[pyo3::pymethods]
948997
impl X963Kdf {
949998
#[new]
@@ -980,41 +1029,22 @@ impl X963Kdf {
9801029
})
9811030
}
9821031

1032+
fn derive_into(
1033+
&mut self,
1034+
py: pyo3::Python<'_>,
1035+
key_material: CffiBuf<'_>,
1036+
mut buf: CffiMutBuf<'_>,
1037+
) -> CryptographyResult<usize> {
1038+
self.derive_into_buffer(py, key_material.as_bytes(), buf.as_mut_bytes())
1039+
}
1040+
9831041
fn derive<'p>(
9841042
&mut self,
9851043
py: pyo3::Python<'p>,
9861044
key_material: CffiBuf<'_>,
9871045
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
988-
if self.used {
989-
return Err(exceptions::already_finalized_error());
990-
}
991-
self.used = true;
992-
993-
let algorithm_bound = self.algorithm.bind(py);
994-
let digest_size = algorithm_bound
995-
.getattr(pyo3::intern!(py, "digest_size"))?
996-
.extract::<usize>()?;
997-
9981046
Ok(pyo3::types::PyBytes::new_with(py, self.length, |output| {
999-
let mut pos = 0usize;
1000-
let mut counter = 1u32;
1001-
1002-
while pos < self.length {
1003-
let mut hash_obj = hashes::Hash::new(py, algorithm_bound, None)?;
1004-
hash_obj.update_bytes(key_material.as_bytes())?;
1005-
hash_obj.update_bytes(&counter.to_be_bytes())?;
1006-
if let Some(ref sharedinfo) = self.sharedinfo {
1007-
hash_obj.update_bytes(sharedinfo.as_bytes(py))?;
1008-
}
1009-
let block = hash_obj.finalize(py)?;
1010-
let block_bytes = block.as_bytes();
1011-
1012-
let copy_len = (self.length - pos).min(digest_size);
1013-
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]);
1014-
pos += copy_len;
1015-
counter += 1;
1016-
}
1017-
1047+
self.derive_into_buffer(py, key_material.as_bytes(), output)?;
10181048
Ok(())
10191049
})?)
10201050
}

tests/hazmat/primitives/test_x963kdf.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,32 @@ def test_unicode_typeerror(self, backend):
110110
)
111111

112112
xkdf.verify(b"foo", "bar") # type: ignore[arg-type]
113+
114+
def test_derive_into(self, backend):
115+
key = binascii.unhexlify(
116+
b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08"
117+
)
118+
xkdf = X963KDF(hashes.SHA256(), 16, None, backend)
119+
buf = bytearray(16)
120+
n = xkdf.derive_into(key, buf)
121+
assert n == 16
122+
# Verify the output matches what derive would produce
123+
xkdf2 = X963KDF(hashes.SHA256(), 16, None, backend)
124+
expected = xkdf2.derive(key)
125+
assert buf == expected
126+
127+
@pytest.mark.parametrize(
128+
("buflen", "outlen"), [(15, 16), (17, 16), (8, 16), (32, 16)]
129+
)
130+
def test_derive_into_buffer_incorrect_size(self, buflen, outlen, backend):
131+
xkdf = X963KDF(hashes.SHA256(), outlen, None, backend)
132+
buf = bytearray(buflen)
133+
with pytest.raises(ValueError, match="buffer must be"):
134+
xkdf.derive_into(b"key", buf)
135+
136+
def test_derive_into_already_finalized(self, backend):
137+
xkdf = X963KDF(hashes.SHA256(), 16, None, backend)
138+
buf = bytearray(16)
139+
xkdf.derive_into(b"key", buf)
140+
with pytest.raises(AlreadyFinalized):
141+
xkdf.derive_into(b"key", buf)

0 commit comments

Comments
 (0)