Skip to content

Commit 00a78c1

Browse files
committed
Convert KDFs to Rust
1 parent b5c25a9 commit 00a78c1

File tree

7 files changed

+99
-69
lines changed

7 files changed

+99
-69
lines changed

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
XTS,
8686
Mode,
8787
)
88-
from cryptography.hazmat.primitives.kdf import scrypt
8988
from cryptography.hazmat.primitives.serialization import ssh
9089
from cryptography.hazmat.primitives.serialization.pkcs12 import (
9190
PBES,
@@ -365,30 +364,6 @@ def create_symmetric_decryption_ctx(
365364
def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
366365
return self.hmac_supported(algorithm)
367366

368-
def derive_pbkdf2_hmac(
369-
self,
370-
algorithm: hashes.HashAlgorithm,
371-
length: int,
372-
salt: bytes,
373-
iterations: int,
374-
key_material: bytes,
375-
) -> bytes:
376-
buf = self._ffi.new("unsigned char[]", length)
377-
evp_md = self._evp_md_non_null_from_algorithm(algorithm)
378-
key_material_ptr = self._ffi.from_buffer(key_material)
379-
res = self._lib.PKCS5_PBKDF2_HMAC(
380-
key_material_ptr,
381-
len(key_material),
382-
salt,
383-
len(salt),
384-
iterations,
385-
evp_md,
386-
length,
387-
buf,
388-
)
389-
self.openssl_assert(res == 1)
390-
return self._ffi.buffer(buf)[:]
391-
392367
def _consume_errors(self) -> typing.List[rust_openssl.OpenSSLError]:
393368
return rust_openssl.capture_error_stack()
394369

@@ -1703,41 +1678,6 @@ def ed448_load_private_bytes(self, data: bytes) -> ed448.Ed448PrivateKey:
17031678
def ed448_generate_key(self) -> ed448.Ed448PrivateKey:
17041679
return rust_openssl.ed448.generate_key()
17051680

1706-
def derive_scrypt(
1707-
self,
1708-
key_material: bytes,
1709-
salt: bytes,
1710-
length: int,
1711-
n: int,
1712-
r: int,
1713-
p: int,
1714-
) -> bytes:
1715-
buf = self._ffi.new("unsigned char[]", length)
1716-
key_material_ptr = self._ffi.from_buffer(key_material)
1717-
res = self._lib.EVP_PBE_scrypt(
1718-
key_material_ptr,
1719-
len(key_material),
1720-
salt,
1721-
len(salt),
1722-
n,
1723-
r,
1724-
p,
1725-
scrypt._MEM_LIMIT,
1726-
buf,
1727-
length,
1728-
)
1729-
if res != 1:
1730-
errors = self._consume_errors()
1731-
# memory required formula explained here:
1732-
# https://blog.filippo.io/the-scrypt-parameters/
1733-
min_memory = 128 * n * r // (1024**2)
1734-
raise MemoryError(
1735-
"Not enough memory to derive key. These parameters require"
1736-
" {} MB of memory.".format(min_memory),
1737-
errors,
1738-
)
1739-
return self._ffi.buffer(buf)[:]
1740-
17411681
def aead_cipher_supported(self, cipher) -> bool:
17421682
cipher_name = aead._aead_cipher_name(cipher)
17431683
if self._fips_enabled and cipher_name not in self._fips_aead:

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from cryptography.hazmat.bindings._rust.openssl import (
1010
ed25519,
1111
hashes,
1212
hmac,
13+
kdf,
1314
x448,
1415
x25519,
1516
)
@@ -20,6 +21,7 @@ __all__ = [
2021
"dh",
2122
"hashes",
2223
"hmac",
24+
"kdf",
2325
"ed448",
2426
"ed25519",
2527
"x448",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
5+
from cryptography.hazmat.primitives.hashes import HashAlgorithm
6+
7+
def derive_pbkdf2_hmac(
8+
key_material: bytes,
9+
algorithm: HashAlgorithm,
10+
salt: bytes,
11+
iterations: int,
12+
length: int,
13+
) -> bytes: ...
14+
def derive_scrypt(
15+
key_material: bytes,
16+
salt: bytes,
17+
n: int,
18+
r: int,
19+
p: int,
20+
max_mem: int,
21+
length: int,
22+
) -> bytes: ...

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
UnsupportedAlgorithm,
1414
_Reasons,
1515
)
16+
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
1617
from cryptography.hazmat.primitives import constant_time, hashes
1718
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
1819

@@ -49,15 +50,12 @@ def derive(self, key_material: bytes) -> bytes:
4950
raise AlreadyFinalized("PBKDF2 instances can only be used once.")
5051
self._used = True
5152

52-
utils._check_byteslike("key_material", key_material)
53-
from cryptography.hazmat.backends.openssl.backend import backend
54-
55-
return backend.derive_pbkdf2_hmac(
53+
return rust_openssl.kdf.derive_pbkdf2_hmac(
54+
key_material,
5655
self._algorithm,
57-
self._length,
5856
self._salt,
5957
self._iterations,
60-
key_material,
58+
self._length,
6159
)
6260

6361
def verify(self, key_material: bytes, expected_key: bytes) -> None:

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
InvalidKey,
1414
UnsupportedAlgorithm,
1515
)
16+
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
1617
from cryptography.hazmat.primitives import constant_time
1718
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
1819

@@ -62,10 +63,15 @@ def derive(self, key_material: bytes) -> bytes:
6263
self._used = True
6364

6465
utils._check_byteslike("key_material", key_material)
65-
from cryptography.hazmat.backends.openssl.backend import backend
6666

67-
return backend.derive_scrypt(
68-
key_material, self._salt, self._length, self._n, self._r, self._p
67+
return rust_openssl.kdf.derive_scrypt(
68+
key_material,
69+
self._salt,
70+
self._n,
71+
self._r,
72+
self._p,
73+
_MEM_LIMIT,
74+
self._length,
6975
)
7076

7177
def verify(self, key_material: bytes, expected_key: bytes) -> None:

src/rust/src/backend/kdf.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
5+
use crate::backend::hashes;
6+
use crate::buf::CffiBuf;
7+
use crate::error::CryptographyResult;
8+
9+
#[pyo3::prelude::pyfunction]
10+
fn derive_pbkdf2_hmac<'p>(
11+
py: pyo3::Python<'p>,
12+
key_material: CffiBuf<'_>,
13+
algorithm: &pyo3::PyAny,
14+
salt: &[u8],
15+
iterations: usize,
16+
length: usize,
17+
) -> CryptographyResult<&'p pyo3::types::PyBytes> {
18+
let md = hashes::message_digest_from_algorithm(py, algorithm)?;
19+
20+
Ok(pyo3::types::PyBytes::new_with(py, length, |b| {
21+
openssl::pkcs5::pbkdf2_hmac(key_material.as_bytes(), salt, iterations, md, b).unwrap();
22+
Ok(())
23+
})?)
24+
}
25+
26+
#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))]
27+
#[pyo3::prelude::pyfunction]
28+
#[allow(clippy::too_many_arguments)]
29+
fn derive_scrypt<'p>(
30+
py: pyo3::Python<'p>,
31+
key_material: CffiBuf<'_>,
32+
salt: &[u8],
33+
n: u64,
34+
r: u64,
35+
p: u64,
36+
max_mem: u64,
37+
length: usize,
38+
) -> CryptographyResult<&'p pyo3::types::PyBytes> {
39+
Ok(pyo3::types::PyBytes::new_with(py, length, |b| {
40+
openssl::pkcs5::scrypt(key_material.as_bytes(), salt, n, r, p, max_mem, b).map_err(|_| {
41+
// memory required formula explained here:
42+
// https://blog.filippo.io/the-scrypt-parameters/
43+
let min_memory = 128 * n * r / (1024 * 1024);
44+
pyo3::exceptions::PyMemoryError::new_err(format!(
45+
"Not enough memory to derive key. These parameters require {}MB of memory.",
46+
min_memory
47+
))
48+
})
49+
})?)
50+
}
51+
52+
pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> {
53+
let m = pyo3::prelude::PyModule::new(py, "kdf")?;
54+
55+
m.add_wrapped(pyo3::wrap_pyfunction!(derive_pbkdf2_hmac))?;
56+
#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))]
57+
m.add_wrapped(pyo3::wrap_pyfunction!(derive_scrypt))?;
58+
59+
Ok(m)
60+
}

src/rust/src/backend/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub(crate) mod ed25519;
99
pub(crate) mod ed448;
1010
pub(crate) mod hashes;
1111
pub(crate) mod hmac;
12+
pub(crate) mod kdf;
1213
pub(crate) mod utils;
1314
#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))]
1415
pub(crate) mod x25519;
@@ -30,6 +31,7 @@ pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<
3031

3132
module.add_submodule(hashes::create_module(module.py())?)?;
3233
module.add_submodule(hmac::create_module(module.py())?)?;
34+
module.add_submodule(kdf::create_module(module.py())?)?;
3335

3436
Ok(())
3537
}

0 commit comments

Comments
 (0)