Skip to content

Commit 9f68ef3

Browse files
authored
implement HKDF and HKDFExpand derive_into (#13643)
* implement HKDF and HKDFExpand derive_into * remove unused var
1 parent 8784aa4 commit 9f68ef3

File tree

6 files changed

+234
-62
lines changed

6 files changed

+234
-62
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ Changelog
3434
* Added :meth:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF.extract`
3535
to :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. The previous
3636
private implementation will be removed in 49.0.0.
37+
* Added ``derive_into`` methods to
38+
:class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF` and
39+
:class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand` to allow
40+
deriving keys directly into pre-allocated buffers.
3741

3842
.. _v46-0-2:
3943

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

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -665,14 +665,38 @@ HKDF
665665
:raises TypeError: This exception is raised if ``key_material`` is not
666666
``bytes``.
667667
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
668-
:meth:`derive` or
669-
:meth:`verify` is
668+
:meth:`derive`,
669+
:meth:`derive_into`,
670+
or :meth:`verify` is
670671
called more than
671672
once.
672673

673674
Derives a new key from the input key material by performing both the
674675
extract and expand operations.
675676

677+
.. method:: derive_into(key_material, buffer)
678+
679+
.. versionadded:: 47.0.0
680+
681+
:param key_material: The input key material.
682+
:type key_material: :term:`bytes-like`
683+
:param buffer: A writable buffer to write the derived key into.
684+
:return int: The number of bytes written to the buffer.
685+
:raises TypeError: This exception is raised if ``key_material`` is not
686+
``bytes``.
687+
:raises ValueError: This exception is raised if the buffer is too small
688+
for the derived key.
689+
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
690+
:meth:`derive`,
691+
:meth:`derive_into`,
692+
or :meth:`verify` is
693+
called more than
694+
once.
695+
696+
Derives a new key from the input key material by performing both the
697+
extract and expand operations, writing the result into the provided
698+
buffer.
699+
676700
.. method:: verify(key_material, expected_key)
677701

678702
:param bytes key_material: The input key material. This is the same as
@@ -684,8 +708,9 @@ HKDF
684708
derived key does not match
685709
the expected key.
686710
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
687-
:meth:`derive` or
688-
:meth:`verify` is
711+
:meth:`derive`,
712+
:meth:`derive_into`,
713+
or :meth:`verify` is
689714
called more than
690715
once.
691716

@@ -748,14 +773,36 @@ HKDF
748773
:raises TypeError: This exception is raised if ``key_material`` is not
749774
``bytes``.
750775
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
751-
:meth:`derive` or
752-
:meth:`verify` is
776+
:meth:`derive`,
777+
:meth:`derive_into`,
778+
or :meth:`verify` is
753779
called more than
754780
once.
755781

756782
Derives a new key from the input key material by only performing the
757783
expand operation.
758784

785+
.. method:: derive_into(key_material, buffer)
786+
787+
.. versionadded:: 47.0.0
788+
789+
:param bytes key_material: The input key material.
790+
:param buffer: A writable buffer to write the derived key into.
791+
:return int: The number of bytes written to the buffer.
792+
:raises TypeError: This exception is raised if ``key_material`` is not
793+
``bytes``.
794+
:raises ValueError: This exception is raised if the buffer is too small
795+
for the derived key.
796+
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
797+
:meth:`derive`,
798+
:meth:`derive_into`,
799+
or :meth:`verify` is
800+
called more than
801+
once.
802+
803+
Derives a new key from the input key material by only performing the
804+
expand operation, writing the result into the provided buffer.
805+
759806
.. method:: verify(key_material, expected_key)
760807

761808
:param bytes key_material: The input key material. This is the same as
@@ -767,8 +814,9 @@ HKDF
767814
derived key does not match
768815
the expected key.
769816
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
770-
:meth:`derive` or
771-
:meth:`verify` is
817+
:meth:`derive`,
818+
:meth:`derive_into`,
819+
or :meth:`verify` is
772820
called more than
773821
once.
774822
:raises TypeError: This is raised if the provided ``key_material`` is

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class HKDF:
6666
algorithm: HashAlgorithm, salt: bytes | None, key_material: Buffer
6767
) -> bytes: ...
6868
def derive(self, key_material: Buffer) -> bytes: ...
69+
def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ...
6970
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...
7071

7172
class HKDFExpand:
@@ -77,4 +78,5 @@ class HKDFExpand:
7778
backend: typing.Any = None,
7879
): ...
7980
def derive(self, key_material: Buffer) -> bytes: ...
81+
def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ...
8082
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...

src/rust/src/backend/hmac.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// for complete details.
44

55
use cryptography_crypto::constant_time;
6-
use pyo3::types::PyBytesMethods;
76

87
use crate::backend::hashes::message_digest_from_algorithm;
98
use crate::buf::CffiBuf;
@@ -92,14 +91,12 @@ impl Hmac {
9291
py: pyo3::Python<'p>,
9392
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
9493
let data = self.finalize_bytes()?;
95-
self.ctx = None;
9694
Ok(pyo3::types::PyBytes::new(py, &data))
9795
}
9896

99-
fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> {
100-
let actual_bound = self.finalize(py)?;
101-
let actual = actual_bound.as_bytes();
102-
if !constant_time::bytes_eq(actual, signature) {
97+
fn verify(&mut self, signature: &[u8]) -> CryptographyResult<()> {
98+
let actual = self.finalize_bytes()?;
99+
if !constant_time::bytes_eq(&actual, signature) {
103100
return Err(CryptographyError::from(
104101
exceptions::InvalidSignature::new_err("Signature did not match digest."),
105102
));

src/rust/src/backend/kdf.rs

Lines changed: 110 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use pyo3::types::{PyAnyMethods, PyBytesMethods};
1111

1212
use crate::backend::hashes;
1313
use crate::backend::hmac::Hmac;
14-
use crate::buf::CffiBuf;
14+
use crate::buf::{CffiBuf, CffiMutBuf};
1515
use crate::error::{CryptographyError, CryptographyResult};
1616
use crate::exceptions;
1717

@@ -549,6 +549,40 @@ fn hkdf_extract(
549549
hmac.finalize_bytes()
550550
}
551551

552+
impl Hkdf {
553+
fn derive_into_buffer(
554+
&mut self,
555+
py: pyo3::Python<'_>,
556+
key_material: &[u8],
557+
output: &mut [u8],
558+
) -> CryptographyResult<usize> {
559+
if self.used {
560+
return Err(exceptions::already_finalized_error());
561+
}
562+
self.used = true;
563+
564+
if output.len() != self.length {
565+
return Err(CryptographyError::from(
566+
pyo3::exceptions::PyValueError::new_err(format!(
567+
"buffer must be {} bytes",
568+
self.length
569+
)),
570+
));
571+
}
572+
573+
let buf = CffiBuf::from_bytes(py, key_material);
574+
let prk = hkdf_extract(py, &self.algorithm, self.salt.as_ref(), &buf)?;
575+
let mut hkdf_expand = HkdfExpand::new(
576+
py,
577+
self.algorithm.clone_ref(py),
578+
self.length,
579+
self.info.as_ref().map(|i| i.clone_ref(py)),
580+
None,
581+
)?;
582+
hkdf_expand.derive_into_buffer(py, &prk, output)
583+
}
584+
}
585+
552586
#[pyo3::pymethods]
553587
impl Hkdf {
554588
#[new]
@@ -610,26 +644,24 @@ impl Hkdf {
610644
Ok(pyo3::types::PyBytes::new(py, &prk))
611645
}
612646

647+
fn derive_into(
648+
&mut self,
649+
py: pyo3::Python<'_>,
650+
key_material: CffiBuf<'_>,
651+
mut buf: CffiMutBuf<'_>,
652+
) -> CryptographyResult<usize> {
653+
self.derive_into_buffer(py, key_material.as_bytes(), buf.as_mut_bytes())
654+
}
655+
613656
fn derive<'p>(
614657
&mut self,
615658
py: pyo3::Python<'p>,
616659
key_material: CffiBuf<'_>,
617660
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
618-
if self.used {
619-
return Err(exceptions::already_finalized_error());
620-
}
621-
self.used = true;
622-
623-
let prk = hkdf_extract(py, &self.algorithm, self.salt.as_ref(), &key_material)?;
624-
let mut hkdf_expand = HkdfExpand::new(
625-
py,
626-
self.algorithm.clone_ref(py),
627-
self.length,
628-
self.info.as_ref().map(|i| i.clone_ref(py)),
629-
None,
630-
)?;
631-
let cffi_buf = CffiBuf::from_bytes(py, &prk);
632-
hkdf_expand.derive(py, cffi_buf)
661+
Ok(pyo3::types::PyBytes::new_with(py, self.length, |output| {
662+
self.derive_into_buffer(py, key_material.as_bytes(), output)?;
663+
Ok(())
664+
})?)
633665
}
634666

635667
fn verify(
@@ -665,6 +697,58 @@ struct HkdfExpand {
665697
used: bool,
666698
}
667699

700+
impl HkdfExpand {
701+
fn derive_into_buffer(
702+
&mut self,
703+
py: pyo3::Python<'_>,
704+
key_material: &[u8],
705+
output: &mut [u8],
706+
) -> CryptographyResult<usize> {
707+
if self.used {
708+
return Err(exceptions::already_finalized_error());
709+
}
710+
self.used = true;
711+
712+
if output.len() != self.length {
713+
return Err(CryptographyError::from(
714+
pyo3::exceptions::PyValueError::new_err(format!(
715+
"buffer must be {} bytes",
716+
self.length
717+
)),
718+
));
719+
}
720+
721+
let algorithm_bound = self.algorithm.bind(py);
722+
let h_prime = Hmac::new_bytes(py, key_material, algorithm_bound)?;
723+
let digest_size = algorithm_bound
724+
.getattr(pyo3::intern!(py, "digest_size"))?
725+
.extract::<usize>()?;
726+
727+
let mut pos = 0usize;
728+
let mut counter = 0u8;
729+
730+
while pos < self.length {
731+
counter += 1;
732+
let mut h = h_prime.copy(py)?;
733+
734+
let start = pos.saturating_sub(digest_size);
735+
h.update_bytes(&output[start..pos])?;
736+
737+
h.update_bytes(self.info.as_bytes(py))?;
738+
h.update_bytes(&[counter])?;
739+
740+
let block = h.finalize(py)?;
741+
let block_bytes = block.as_bytes();
742+
743+
let copy_len = (self.length - pos).min(digest_size);
744+
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]);
745+
pos += copy_len;
746+
}
747+
748+
Ok(self.length)
749+
}
750+
}
751+
668752
#[pyo3::pymethods]
669753
impl HkdfExpand {
670754
#[new]
@@ -710,44 +794,22 @@ impl HkdfExpand {
710794
})
711795
}
712796

797+
fn derive_into(
798+
&mut self,
799+
py: pyo3::Python<'_>,
800+
key_material: CffiBuf<'_>,
801+
mut buf: CffiMutBuf<'_>,
802+
) -> CryptographyResult<usize> {
803+
self.derive_into_buffer(py, key_material.as_bytes(), buf.as_mut_bytes())
804+
}
805+
713806
fn derive<'p>(
714807
&mut self,
715808
py: pyo3::Python<'p>,
716809
key_material: CffiBuf<'_>,
717810
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
718-
if self.used {
719-
return Err(exceptions::already_finalized_error());
720-
}
721-
self.used = true;
722-
723-
let algorithm_bound = self.algorithm.bind(py);
724-
let h_prime = Hmac::new_bytes(py, key_material.as_bytes(), algorithm_bound)?;
725-
let digest_size = algorithm_bound
726-
.getattr(pyo3::intern!(py, "digest_size"))?
727-
.extract::<usize>()?;
728-
729811
Ok(pyo3::types::PyBytes::new_with(py, self.length, |output| {
730-
let mut pos = 0usize;
731-
let mut counter = 0u8;
732-
733-
while pos < self.length {
734-
counter += 1;
735-
let mut h = h_prime.copy(py)?;
736-
737-
let start = pos.saturating_sub(digest_size);
738-
h.update_bytes(&output[start..pos])?;
739-
740-
h.update_bytes(self.info.as_bytes(py))?;
741-
h.update_bytes(&[counter])?;
742-
743-
let block = h.finalize(py)?;
744-
let block_bytes = block.as_bytes();
745-
746-
let copy_len = (self.length - pos).min(digest_size);
747-
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]);
748-
pos += copy_len;
749-
}
750-
812+
self.derive_into_buffer(py, key_material.as_bytes(), output)?;
751813
Ok(())
752814
})?)
753815
}

0 commit comments

Comments
 (0)