-
Notifications
You must be signed in to change notification settings - Fork 1.7k
implement HKDF and HKDFExpand derive_into #13643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -646,6 +646,27 @@ HKDF | |
Derives a new key from the input key material by performing both the | ||
extract and expand operations. | ||
|
||
.. method:: derive_into(key_material, buffer) | ||
|
||
.. versionadded:: 47.0.0 | ||
|
||
:param key_material: The input key material. | ||
:type key_material: :term:`bytes-like` | ||
:param buffer: A writable buffer to write the derived key into. | ||
:return int: The number of bytes written to the buffer. | ||
:raises TypeError: This exception is raised if ``key_material`` is not | ||
``bytes``. | ||
:raises ValueError: This exception is raised if the buffer is too small | ||
for the derived key. | ||
:raises cryptography.exceptions.AlreadyFinalized: This is raised when | ||
:meth:`derive_into` | ||
is called more than | ||
once. | ||
|
||
Derives a new key from the input key material by performing both the | ||
extract and expand operations, writing the result into the provided | ||
buffer. | ||
|
||
.. method:: verify(key_material, expected_key) | ||
|
||
:param bytes key_material: The input key material. This is the same as | ||
|
@@ -729,6 +750,25 @@ HKDF | |
Derives a new key from the input key material by only performing the | ||
expand operation. | ||
|
||
.. method:: derive_into(key_material, buffer) | ||
|
||
.. versionadded:: 47.0.0 | ||
|
||
:param bytes key_material: The input key material. | ||
:param buffer: A writable buffer to write the derived key into. | ||
:return int: The number of bytes written to the buffer. | ||
:raises TypeError: This exception is raised if ``key_material`` is not | ||
``bytes``. | ||
:raises ValueError: This exception is raised if the buffer is too small | ||
for the derived key. | ||
:raises cryptography.exceptions.AlreadyFinalized: This is raised when | ||
:meth:`derive_into` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
is called more than | ||
once. | ||
|
||
Derives a new key from the input key material by only performing the | ||
expand operation, writing the result into the provided buffer. | ||
|
||
.. method:: verify(key_material, expected_key) | ||
|
||
:param bytes key_material: The input key material. This is the same as | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ use pyo3::types::{PyAnyMethods, PyBytesMethods}; | |
|
||
use crate::backend::hashes; | ||
use crate::backend::hmac::Hmac; | ||
use crate::buf::CffiBuf; | ||
use crate::buf::{CffiBuf, CffiMutBuf}; | ||
use crate::error::{CryptographyError, CryptographyResult}; | ||
use crate::exceptions; | ||
|
||
|
@@ -526,6 +526,40 @@ struct Hkdf { | |
used: bool, | ||
} | ||
|
||
impl Hkdf { | ||
fn derive_into_buffer( | ||
&mut self, | ||
py: pyo3::Python<'_>, | ||
key_material: &[u8], | ||
output: &mut [u8], | ||
) -> CryptographyResult<usize> { | ||
if self.used { | ||
return Err(exceptions::already_finalized_error()); | ||
} | ||
self.used = true; | ||
|
||
if output.len() != self.length { | ||
return Err(CryptographyError::from( | ||
pyo3::exceptions::PyValueError::new_err(format!( | ||
"buffer must be {} bytes", | ||
self.length | ||
)), | ||
)); | ||
} | ||
|
||
let prk = self._extract(py, key_material)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it undermind the point at all that we still end up with the prk in some buffer that the caller doesn't control? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sort of? It's not ideal. We could require that the buffer provided be >= underlying HMAC output size, but wow that sounds confusing. |
||
let mut hkdf_expand = HkdfExpand::new( | ||
py, | ||
self.algorithm.clone_ref(py), | ||
self.length, | ||
self.info.as_ref().map(|i| i.clone_ref(py)), | ||
None, | ||
)?; | ||
let prk_bytes = prk.as_bytes(); | ||
hkdf_expand.derive_into_buffer(py, prk_bytes, output) | ||
} | ||
} | ||
|
||
#[pyo3::pymethods] | ||
impl Hkdf { | ||
#[new] | ||
|
@@ -584,27 +618,24 @@ impl Hkdf { | |
hmac.finalize(py) | ||
} | ||
|
||
fn derive_into( | ||
&mut self, | ||
py: pyo3::Python<'_>, | ||
key_material: CffiBuf<'_>, | ||
mut buf: CffiMutBuf<'_>, | ||
) -> CryptographyResult<usize> { | ||
self.derive_into_buffer(py, key_material.as_bytes(), buf.as_mut_bytes()) | ||
} | ||
|
||
fn derive<'p>( | ||
&mut self, | ||
py: pyo3::Python<'p>, | ||
key_material: CffiBuf<'_>, | ||
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> { | ||
if self.used { | ||
return Err(exceptions::already_finalized_error()); | ||
} | ||
self.used = true; | ||
|
||
let prk = self._extract(py, key_material.as_bytes())?; | ||
let mut hkdf_expand = HkdfExpand::new( | ||
py, | ||
self.algorithm.clone_ref(py), | ||
self.length, | ||
self.info.as_ref().map(|i| i.clone_ref(py)), | ||
None, | ||
)?; | ||
let prk_bytes = prk.as_bytes(); | ||
let cffi_buf = CffiBuf::from_bytes(py, prk_bytes); | ||
hkdf_expand.derive(py, cffi_buf) | ||
Ok(pyo3::types::PyBytes::new_with(py, self.length, |output| { | ||
self.derive_into_buffer(py, key_material.as_bytes(), output)?; | ||
Ok(()) | ||
})?) | ||
} | ||
|
||
fn verify( | ||
|
@@ -640,6 +671,58 @@ struct HkdfExpand { | |
used: bool, | ||
} | ||
|
||
impl HkdfExpand { | ||
fn derive_into_buffer( | ||
&mut self, | ||
py: pyo3::Python<'_>, | ||
key_material: &[u8], | ||
output: &mut [u8], | ||
) -> CryptographyResult<usize> { | ||
if self.used { | ||
return Err(exceptions::already_finalized_error()); | ||
} | ||
self.used = true; | ||
|
||
if output.len() != self.length { | ||
return Err(CryptographyError::from( | ||
pyo3::exceptions::PyValueError::new_err(format!( | ||
"buffer must be {} bytes", | ||
self.length | ||
)), | ||
)); | ||
} | ||
|
||
let algorithm_bound = self.algorithm.bind(py); | ||
let h_prime = Hmac::new_bytes(py, key_material, algorithm_bound)?; | ||
let digest_size = algorithm_bound | ||
.getattr(pyo3::intern!(py, "digest_size"))? | ||
.extract::<usize>()?; | ||
|
||
let mut pos = 0usize; | ||
let mut counter = 0u8; | ||
|
||
while pos < self.length { | ||
counter += 1; | ||
let mut h = h_prime.copy(py)?; | ||
|
||
let start = pos.saturating_sub(digest_size); | ||
h.update_bytes(&output[start..pos])?; | ||
|
||
h.update_bytes(self.info.as_bytes(py))?; | ||
h.update_bytes(&[counter])?; | ||
|
||
let block = h.finalize(py)?; | ||
let block_bytes = block.as_bytes(); | ||
|
||
let copy_len = (self.length - pos).min(digest_size); | ||
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]); | ||
pos += copy_len; | ||
} | ||
|
||
Ok(self.length) | ||
} | ||
} | ||
|
||
#[pyo3::pymethods] | ||
impl HkdfExpand { | ||
#[new] | ||
|
@@ -685,44 +768,22 @@ impl HkdfExpand { | |
}) | ||
} | ||
|
||
fn derive_into( | ||
&mut self, | ||
py: pyo3::Python<'_>, | ||
key_material: CffiBuf<'_>, | ||
mut buf: CffiMutBuf<'_>, | ||
) -> CryptographyResult<usize> { | ||
self.derive_into_buffer(py, key_material.as_bytes(), buf.as_mut_bytes()) | ||
} | ||
|
||
fn derive<'p>( | ||
&mut self, | ||
py: pyo3::Python<'p>, | ||
key_material: CffiBuf<'_>, | ||
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> { | ||
if self.used { | ||
return Err(exceptions::already_finalized_error()); | ||
} | ||
self.used = true; | ||
|
||
let algorithm_bound = self.algorithm.bind(py); | ||
let h_prime = Hmac::new_bytes(py, key_material.as_bytes(), algorithm_bound)?; | ||
let digest_size = algorithm_bound | ||
.getattr(pyo3::intern!(py, "digest_size"))? | ||
.extract::<usize>()?; | ||
|
||
Ok(pyo3::types::PyBytes::new_with(py, self.length, |output| { | ||
let mut pos = 0usize; | ||
let mut counter = 0u8; | ||
|
||
while pos < self.length { | ||
counter += 1; | ||
let mut h = h_prime.copy(py)?; | ||
|
||
let start = pos.saturating_sub(digest_size); | ||
h.update_bytes(&output[start..pos])?; | ||
|
||
h.update_bytes(self.info.as_bytes(py))?; | ||
h.update_bytes(&[counter])?; | ||
|
||
let block = h.finalize(py)?; | ||
let block_bytes = block.as_bytes(); | ||
|
||
let copy_len = (self.length - pos).min(digest_size); | ||
output[pos..pos + copy_len].copy_from_slice(&block_bytes[..copy_len]); | ||
pos += copy_len; | ||
} | ||
|
||
self.derive_into_buffer(py, key_material.as_bytes(), output)?; | ||
Ok(()) | ||
})?) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, its really when any combination of the two methods is called more than once.