Skip to content

Commit d41476b

Browse files
alexclaude
andauthored
Add HKDF_SHA512 support to the HPKE implementation (#14395)
Extend the HPKE KDF enum with HKDF_SHA512 (ID 0x0003) alongside the existing HKDF_SHA256. The KDF type owns its id() and hash_algorithm() methods, and hkdf_expand takes the hash as an argument so both KEM (SHA256) and HPKE (suite KDF) callers go through the same code path. KEM operations (DHKEM(X25519, HKDF-SHA256)) continue to use SHA-256 per RFC 9180. https://claude.ai/code/session_013bNoRc1A2vwBFC6NZkxCtS Co-authored-by: Claude <noreply@anthropic.com>
1 parent a6cabfa commit d41476b

4 files changed

Lines changed: 43 additions & 12 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class KEM:
1010

1111
class KDF:
1212
HKDF_SHA256: KDF
13+
HKDF_SHA512: KDF
1314

1415
class AEAD:
1516
AES_128_GCM: AEAD

src/rust/src/backend/hpke.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod kem_params {
2222

2323
mod kdf_params {
2424
pub const HKDF_SHA256_ID: u16 = 0x0001;
25+
pub const HKDF_SHA512_ID: u16 = 0x0003;
2526
}
2627

2728
mod aead_params {
@@ -61,6 +62,26 @@ pub(crate) enum KEM {
6162
#[derive(Clone, PartialEq, Eq, Hash)]
6263
pub(crate) enum KDF {
6364
HKDF_SHA256,
65+
HKDF_SHA512,
66+
}
67+
68+
impl KDF {
69+
fn id(&self) -> u16 {
70+
match self {
71+
KDF::HKDF_SHA256 => kdf_params::HKDF_SHA256_ID,
72+
KDF::HKDF_SHA512 => kdf_params::HKDF_SHA512_ID,
73+
}
74+
}
75+
76+
fn hash_algorithm<'p>(
77+
&self,
78+
py: pyo3::Python<'p>,
79+
) -> CryptographyResult<pyo3::Bound<'p, pyo3::PyAny>> {
80+
match self {
81+
KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?),
82+
KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?),
83+
}
84+
}
6485
}
6586

6687
#[allow(clippy::upper_case_acronyms)]
@@ -113,18 +134,17 @@ pub(crate) struct Suite {
113134
aead: AEAD,
114135
kem_suite_id: [u8; 5],
115136
hpke_suite_id: [u8; 10],
137+
kdf: KDF,
116138
}
117139

118140
impl Suite {
119141
fn hkdf_expand<'p>(
120-
&self,
121142
py: pyo3::Python<'p>,
143+
algorithm: pyo3::Bound<'_, pyo3::PyAny>,
122144
prk: &[u8],
123145
info: &[u8],
124146
length: usize,
125147
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
126-
let algorithm = types::SHA256.get(py)?.call0()?;
127-
128148
let mut hkdf_expand = HkdfExpand::new(
129149
py,
130150
algorithm.unbind(),
@@ -167,7 +187,9 @@ impl Suite {
167187
labeled_info.extend_from_slice(&self.kem_suite_id);
168188
labeled_info.extend_from_slice(label);
169189
labeled_info.extend_from_slice(info);
170-
self.hkdf_expand(py, prk, &labeled_info, length)
190+
191+
let algorithm = types::SHA256.get(py)?.call0()?;
192+
Suite::hkdf_expand(py, algorithm, prk, &labeled_info, length)
171193
}
172194

173195
fn extract_and_expand<'p>(
@@ -249,7 +271,7 @@ impl Suite {
249271
labeled_ikm.extend_from_slice(label);
250272
labeled_ikm.extend_from_slice(ikm);
251273

252-
let algorithm = types::SHA256.get(py)?.call0()?;
274+
let algorithm = self.kdf.hash_algorithm(py)?;
253275
let buf = CffiBuf::from_bytes(py, &labeled_ikm);
254276
hkdf_extract(py, &algorithm.unbind(), salt, &buf)
255277
}
@@ -269,7 +291,8 @@ impl Suite {
269291
labeled_info.extend_from_slice(&self.hpke_suite_id);
270292
labeled_info.extend_from_slice(label);
271293
labeled_info.extend_from_slice(info);
272-
self.hkdf_expand(py, prk, &labeled_info, length)
294+
let algorithm = self.kdf.hash_algorithm(py)?;
295+
Suite::hkdf_expand(py, algorithm, prk, &labeled_info, length)
273296
}
274297

275298
fn aead_encrypt<'p>(
@@ -408,7 +431,7 @@ impl Suite {
408431
#[pyo3::pymethods]
409432
impl Suite {
410433
#[new]
411-
fn new(_kem: KEM, _kdf: KDF, aead: AEAD) -> CryptographyResult<Suite> {
434+
fn new(_kem: KEM, kdf: KDF, aead: AEAD) -> CryptographyResult<Suite> {
412435
// Build suite IDs
413436
let mut kem_suite_id = [0u8; 5];
414437
kem_suite_id[..3].copy_from_slice(b"KEM");
@@ -417,13 +440,14 @@ impl Suite {
417440
let mut hpke_suite_id = [0u8; 10];
418441
hpke_suite_id[..4].copy_from_slice(b"HPKE");
419442
hpke_suite_id[4..6].copy_from_slice(&kem_params::X25519_ID.to_be_bytes());
420-
hpke_suite_id[6..8].copy_from_slice(&kdf_params::HKDF_SHA256_ID.to_be_bytes());
443+
hpke_suite_id[6..8].copy_from_slice(&kdf.id().to_be_bytes());
421444
hpke_suite_id[8..10].copy_from_slice(&aead.id().to_be_bytes());
422445

423446
Ok(Suite {
424447
aead,
425448
kem_suite_id,
426449
hpke_suite_id,
450+
kdf,
427451
})
428452
}
429453

src/rust/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ pub static SHA1: LazyPyImport =
308308
LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA1"]);
309309
pub static SHA256: LazyPyImport =
310310
LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]);
311+
pub static SHA512: LazyPyImport =
312+
LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA512"]);
311313

312314
pub static NO_DIGEST_INFO: LazyPyImport = LazyPyImport::new(
313315
"cryptography.hazmat.primitives.asymmetric.utils",

tests/hazmat/primitives/test_hpke.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
SUPPORTED_SUITES = [
2525
(KEM.X25519, KDF.HKDF_SHA256, AEAD.AES_128_GCM),
2626
(KEM.X25519, KDF.HKDF_SHA256, AEAD.CHACHA20_POLY1305),
27+
(KEM.X25519, KDF.HKDF_SHA512, AEAD.AES_128_GCM),
2728
]
2829

2930

@@ -225,25 +226,28 @@ def test_vector_decryption(self, subtests):
225226
lambda f: json.load(f),
226227
)
227228

229+
kdf_map = {
230+
0x0001: KDF.HKDF_SHA256,
231+
0x0003: KDF.HKDF_SHA512,
232+
}
228233
aead_map = {
229234
0x0001: AEAD.AES_128_GCM,
230235
0x0003: AEAD.CHACHA20_POLY1305,
231236
}
232237

233238
for vector in vectors:
234-
# Support: mode 0 (Base), X25519, HKDF-SHA256,
235-
# AES-128-GCM or ChaCha20Poly1305
236239
if not (
237240
vector["mode"] == 0
238241
and vector["kem_id"] == 0x0020
239-
and vector["kdf_id"] == 0x0001
242+
and vector["kdf_id"] in kdf_map
240243
and vector["aead_id"] in aead_map
241244
):
242245
continue
243246

244247
with subtests.test():
248+
kdf = kdf_map[vector["kdf_id"]]
245249
aead = aead_map[vector["aead_id"]]
246-
suite = Suite(KEM.X25519, KDF.HKDF_SHA256, aead)
250+
suite = Suite(KEM.X25519, kdf, aead)
247251

248252
sk_r_bytes = bytes.fromhex(vector["skRm"])
249253
sk_r = x25519.X25519PrivateKey.from_private_bytes(sk_r_bytes)

0 commit comments

Comments
 (0)