From 076c26c4b0c2776088d454d92ccbb8598304f7d9 Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 17:15:21 +0100 Subject: [PATCH 01/15] Add HPKE SHAKE128 KDF support --- docs/hazmat/primitives/hpke.rst | 4 + .../hazmat/bindings/_rust/openssl/hpke.pyi | 1 + src/rust/src/backend/hpke.rs | 105 ++++++++++++++++++ src/rust/src/types.rs | 2 + tests/hazmat/primitives/test_hpke.py | 45 ++++++++ 5 files changed, 157 insertions(+) diff --git a/docs/hazmat/primitives/hpke.rst b/docs/hazmat/primitives/hpke.rst index 48374744ce98..5916b17c1edd 100644 --- a/docs/hazmat/primitives/hpke.rst +++ b/docs/hazmat/primitives/hpke.rst @@ -93,6 +93,10 @@ specifying auxiliary authenticated information. HKDF-SHA384 + .. attribute:: SHAKE128 + + SHAKE-128 + .. class:: AEAD An enumeration of authenticated encryption algorithms. diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi index 94e05558227b..4d49961db9b9 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi @@ -12,6 +12,7 @@ class KDF: HKDF_SHA256: KDF HKDF_SHA384: KDF HKDF_SHA512: KDF + SHAKE128: KDF class AEAD: AES_128_GCM: AEAD diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 3a32d0a33d6e..bfdfb18b8f98 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -5,6 +5,7 @@ use pyo3::types::{PyAnyMethods, PyBytesMethods}; use crate::backend::aead::{AesGcm, ChaCha20Poly1305}; +use crate::backend::hashes::Hash; use crate::backend::kdf::{hkdf_extract, HkdfExpand}; use crate::backend::x25519; use crate::buf::CffiBuf; @@ -14,6 +15,15 @@ use crate::{exceptions, types}; const HPKE_VERSION: &[u8] = b"HPKE-v1"; const HPKE_MODE_BASE: u8 = 0x00; +fn u16_length_prefix(length: usize, label: &str) -> CryptographyResult<[u8; 2]> { + let length = u16::try_from(length).map_err(|_| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err(format!( + "{label} is too large." + ))) + })?; + Ok(length.to_be_bytes()) +} + mod kem_params { pub const X25519_ID: u16 = 0x0020; pub const X25519_NSECRET: usize = 32; @@ -24,6 +34,8 @@ mod kdf_params { pub const HKDF_SHA256_ID: u16 = 0x0001; pub const HKDF_SHA384_ID: u16 = 0x0002; pub const HKDF_SHA512_ID: u16 = 0x0003; + pub const SHAKE128_ID: u16 = 0x0010; + pub const SHAKE128_NH: usize = 32; } mod aead_params { @@ -70,6 +82,7 @@ pub(crate) enum KDF { HKDF_SHA256, HKDF_SHA384, HKDF_SHA512, + SHAKE128, } impl KDF { @@ -78,9 +91,23 @@ impl KDF { KDF::HKDF_SHA256 => kdf_params::HKDF_SHA256_ID, KDF::HKDF_SHA384 => kdf_params::HKDF_SHA384_ID, KDF::HKDF_SHA512 => kdf_params::HKDF_SHA512_ID, + KDF::SHAKE128 => kdf_params::SHAKE128_ID, } } + fn nh(&self) -> usize { + match self { + KDF::HKDF_SHA256 => 32, + KDF::HKDF_SHA384 => 48, + KDF::HKDF_SHA512 => 64, + KDF::SHAKE128 => kdf_params::SHAKE128_NH, + } + } + + fn is_one_stage(&self) -> bool { + matches!(self, KDF::SHAKE128) + } + fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, @@ -89,6 +116,32 @@ impl KDF { KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), + KDF::SHAKE128 => Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "SHAKE128 does not support HKDF extract/expand.", + ), + )), + } + } + + fn derive<'p>( + &self, + py: pyo3::Python<'p>, + ikm: &[u8], + length: usize, + ) -> CryptographyResult> { + match self { + KDF::SHAKE128 => { + let algorithm = types::SHAKE128.get(py)?.call1((length,))?; + let mut hash = Hash::new(py, &algorithm, None)?; + hash.update_bytes(ikm)?; + hash.finalize(py) + } + _ => Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "KDF does not support one-stage derivation.", + ), + )), } } } @@ -309,6 +362,26 @@ impl Suite { Suite::hkdf_expand(py, algorithm, prk, &labeled_info, length) } + fn hpke_labeled_derive<'p>( + &self, + py: pyo3::Python<'p>, + ikm: &[u8], + label: &[u8], + info: &[u8], + length: usize, + ) -> CryptographyResult> { + let ikm_len = u16_length_prefix(ikm.len(), "ikm")?; + let mut labeled_ikm = + Vec::with_capacity(2 + ikm.len() + HPKE_VERSION.len() + 10 + label.len() + info.len()); + labeled_ikm.extend_from_slice(&ikm_len); + labeled_ikm.extend_from_slice(ikm); + labeled_ikm.extend_from_slice(HPKE_VERSION); + labeled_ikm.extend_from_slice(&self.hpke_suite_id); + labeled_ikm.extend_from_slice(label); + labeled_ikm.extend_from_slice(info); + self.kdf.derive(py, &labeled_ikm, length) + } + fn aead_encrypt<'p>( &self, py: pyo3::Python<'p>, @@ -415,6 +488,38 @@ impl Suite { pyo3::Bound<'p, pyo3::types::PyBytes>, pyo3::Bound<'p, pyo3::types::PyBytes>, )> { + if self.kdf.is_one_stage() { + let shared_secret_len = u16_length_prefix(shared_secret.len(), "shared_secret")?; + let info_len = u16_length_prefix(info.len(), "info")?; + + let mut secrets = Vec::with_capacity(4 + shared_secret.len()); + secrets.extend_from_slice(&0u16.to_be_bytes()); + secrets.extend_from_slice(&shared_secret_len); + secrets.extend_from_slice(shared_secret); + + let mut key_schedule_context = Vec::with_capacity(5 + info.len()); + key_schedule_context.push(HPKE_MODE_BASE); + key_schedule_context.extend_from_slice(&0u16.to_be_bytes()); + key_schedule_context.extend_from_slice(&info_len); + key_schedule_context.extend_from_slice(info); + + let key_length = self.aead.key_length(); + let nonce_length = self.aead.nonce_length(); + let secret = self.hpke_labeled_derive( + py, + &secrets, + b"secret", + &key_schedule_context, + key_length + nonce_length + self.kdf.nh(), + )?; + let secret_bytes = secret.as_bytes(); + let key = pyo3::types::PyBytes::new(py, &secret_bytes[..key_length]); + let base_nonce = + pyo3::types::PyBytes::new(py, &secret_bytes[key_length..key_length + nonce_length]); + + return Ok((key, base_nonce)); + } + let psk_id_hash = self.hpke_labeled_extract(py, None, b"psk_id_hash", b"")?; let info_hash = self.hpke_labeled_extract(py, None, b"info_hash", info)?; let mut key_schedule_context = vec![HPKE_MODE_BASE]; diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs index 86e8f97d10df..ee5a04057e5a 100644 --- a/src/rust/src/types.rs +++ b/src/rust/src/types.rs @@ -312,6 +312,8 @@ pub static SHA384: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA384"]); pub static SHA512: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA512"]); +pub static SHAKE128: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHAKE128"]); pub static NO_DIGEST_INFO: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.utils", diff --git a/tests/hazmat/primitives/test_hpke.py b/tests/hazmat/primitives/test_hpke.py index ea53dbe7d1b6..f6ce1bca1253 100644 --- a/tests/hazmat/primitives/test_hpke.py +++ b/tests/hazmat/primitives/test_hpke.py @@ -10,6 +10,7 @@ from cryptography.exceptions import InvalidTag from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.hpke import ( AEAD, @@ -30,12 +31,21 @@ ) ) +SUPPORTED_SHAKE128_AEADS = [ + AEAD.AES_128_GCM, + AEAD.AES_256_GCM, + AEAD.CHACHA20_POLY1305, +] + @pytest.mark.supported( only_if=lambda backend: backend.x25519_supported(), skip_message="Requires OpenSSL with X25519 support", ) class TestHPKE: + def test_shake128_is_available(self): + assert isinstance(KDF.SHAKE128, KDF) + def test_invalid_kem_type(self): with pytest.raises(TypeError): Suite("not a kem", KDF.HKDF_SHA256, AEAD.AES_128_GCM) # type: ignore[arg-type] @@ -272,3 +282,38 @@ def test_vector_decryption(self, subtests): suite, ciphertext, sk_r, info=info, aad=aad ) assert pt == pt_expected + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=32) + ), + skip_message="Does not support SHAKE128", + ) + @pytest.mark.parametrize("aead", SUPPORTED_SHAKE128_AEADS) + def test_roundtrip_shake128(self, aead): + suite = Suite(KEM.X25519, KDF.SHAKE128, aead) + + sk_r = x25519.X25519PrivateKey.generate() + pk_r = sk_r.public_key() + + ciphertext = suite.encrypt(b"Hello, HPKE!", pk_r, info=b"shake128") + plaintext = suite.decrypt(ciphertext, sk_r, info=b"shake128") + + assert plaintext == b"Hello, HPKE!" + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=32) + ), + skip_message="Does not support SHAKE128", + ) + def test_info_mismatch_fails_shake128(self): + suite = Suite(KEM.X25519, KDF.SHAKE128, AEAD.AES_128_GCM) + + sk_r = x25519.X25519PrivateKey.generate() + pk_r = sk_r.public_key() + + ciphertext = suite.encrypt(b"Secret", pk_r, info=b"sender info") + + with pytest.raises(InvalidTag): + suite.decrypt(ciphertext, sk_r, info=b"different info") From daa9101789ed670ce92a3dc1dc7ce324217a0175 Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 17:20:43 +0100 Subject: [PATCH 02/15] Fix SHAKE128 HPKE labeled derive encoding --- src/rust/src/backend/hpke.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index bfdfb18b8f98..308871b2a80b 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -367,18 +367,20 @@ impl Suite { py: pyo3::Python<'p>, ikm: &[u8], label: &[u8], - info: &[u8], + context: &[u8], length: usize, ) -> CryptographyResult> { - let ikm_len = u16_length_prefix(ikm.len(), "ikm")?; - let mut labeled_ikm = - Vec::with_capacity(2 + ikm.len() + HPKE_VERSION.len() + 10 + label.len() + info.len()); - labeled_ikm.extend_from_slice(&ikm_len); + let label_len = u16_length_prefix(label.len(), "label")?; + let mut labeled_ikm = Vec::with_capacity( + ikm.len() + HPKE_VERSION.len() + 10 + 2 + label.len() + 2 + context.len(), + ); labeled_ikm.extend_from_slice(ikm); labeled_ikm.extend_from_slice(HPKE_VERSION); labeled_ikm.extend_from_slice(&self.hpke_suite_id); + labeled_ikm.extend_from_slice(&label_len); labeled_ikm.extend_from_slice(label); - labeled_ikm.extend_from_slice(info); + labeled_ikm.extend_from_slice(&(length as u16).to_be_bytes()); + labeled_ikm.extend_from_slice(context); self.kdf.derive(py, &labeled_ikm, length) } From df82f80b22d245952f10732f322b9347cba27740 Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 19:25:05 +0100 Subject: [PATCH 03/15] Add Go-based SHAKE128 HPKE test vectors --- docs/development/test-vectors.rst | 4 ++ tests/hazmat/primitives/test_hpke.py | 51 +++++++++++++++++++ .../HPKE/go-shake128-vectors.json | 50 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 vectors/cryptography_vectors/HPKE/go-shake128-vectors.json diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 0de474d4d009..593c8a7fa696 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1129,6 +1129,9 @@ HPKE (Hybrid Public Key Encryption) These vectors cover all four modes (Base, PSK, Auth, AuthPSK), multiple KEMs (X25519, X448, P-256, P-521), KDFs (HKDF-SHA256, HKDF-SHA512), and AEADs (AES-128-GCM, AES-256-GCM, ChaCha20Poly1305). +* HPKE SHAKE-128 (X25519, mode Base) vectors generated with Go primitives + using the one-stage HPKE key schedule defined in + `Go's crypto/hpke package`_. Key wrapping ~~~~~~~~~~~~ @@ -1277,3 +1280,4 @@ header format (substituting the correct information): .. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/1dbe8a6e1c56d010c271a80eafb2c7fd1b92fbda/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt .. _`IETF CFRG HPKE repository`: https://github.com/cfrg/draft-irtf-cfrg-hpke +.. _`Go's crypto/hpke package`: https://github.com/golang/go/tree/master/src/crypto/hpke diff --git a/tests/hazmat/primitives/test_hpke.py b/tests/hazmat/primitives/test_hpke.py index f6ce1bca1253..ef21cf4f78d1 100644 --- a/tests/hazmat/primitives/test_hpke.py +++ b/tests/hazmat/primitives/test_hpke.py @@ -283,6 +283,57 @@ def test_vector_decryption(self, subtests): ) assert pt == pt_expected + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=32) + ), + skip_message="Does not support SHAKE128", + ) + def test_shake128_vector_decryption(self, subtests): + vectors = load_vectors_from_file( + os.path.join("HPKE", "go-shake128-vectors.json"), + lambda f: json.load(f), + ) + + kdf_map = { + 0x0010: KDF.SHAKE128, + } + aead_map = { + 0x0001: AEAD.AES_128_GCM, + 0x0002: AEAD.AES_256_GCM, + 0x0003: AEAD.CHACHA20_POLY1305, + } + + for vector in vectors: + if not ( + vector["mode"] == 0 + and vector["kem_id"] == 0x0020 + and vector["kdf_id"] in kdf_map + and vector["aead_id"] in aead_map + ): + continue + + with subtests.test(): + kdf = kdf_map[vector["kdf_id"]] + aead = aead_map[vector["aead_id"]] + suite = Suite(KEM.X25519, kdf, aead) + + sk_r_bytes = bytes.fromhex(vector["skRm"]) + sk_r = x25519.X25519PrivateKey.from_private_bytes(sk_r_bytes) + enc = bytes.fromhex(vector["enc"]) + info = bytes.fromhex(vector["info"]) + + encryption = vector["encryptions"][0] + aad = bytes.fromhex(encryption["aad"]) + ct = bytes.fromhex(encryption["ct"]) + pt_expected = bytes.fromhex(encryption["pt"]) + + ciphertext = enc + ct + pt = rust_openssl.hpke._decrypt_with_aad( + suite, ciphertext, sk_r, info=info, aad=aad + ) + assert pt == pt_expected + @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.SHAKE128(digest_size=32) diff --git a/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json b/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json new file mode 100644 index 000000000000..7dab35f0ce2c --- /dev/null +++ b/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json @@ -0,0 +1,50 @@ +[ + { + "mode": 0, + "kem_id": 32, + "kdf_id": 16, + "aead_id": 1, + "info": "4f70656e53534c2d48504b452d5348414b45313238", + "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", + "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", + "encryptions": [ + { + "aad": "01020304aabbccdd", + "ct": "1f9fee961d2e3ed21b06768b89ef515432240d723cf08c84af6755a1a3b4bd67bd0c416df531e986bf3a", + "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" + } + ] + }, + { + "mode": 0, + "kem_id": 32, + "kdf_id": 16, + "aead_id": 2, + "info": "4f70656e53534c2d48504b452d5348414b45313238", + "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", + "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", + "encryptions": [ + { + "aad": "01020304aabbccdd", + "ct": "946f571b90304daea0c3ed68d45f25910b2f4d7919672f78604b64a8be9c8e58bf5d8de05405a45ee52f", + "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" + } + ] + }, + { + "mode": 0, + "kem_id": 32, + "kdf_id": 16, + "aead_id": 3, + "info": "4f70656e53534c2d48504b452d5348414b45313238", + "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", + "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", + "encryptions": [ + { + "aad": "01020304aabbccdd", + "ct": "c2e990a4367719ad96891d8416b4a4a61ead727317c7847248bf3d1a9d17844c1be84230170a2d5dcc04", + "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" + } + ] + } +] From cef5ddfd67dd34f380006d653a47c513287d288d Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 19:39:26 +0100 Subject: [PATCH 04/15] ci: retrigger after arm runner docker pull failure From e6bf33e1938af1d63a465f6111d8e9e9e941982b Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 19:48:20 +0100 Subject: [PATCH 05/15] ci: retrigger after macOS vector clone timeout From 08aff36b8aba85e7f93dfd8207bbcfd07f50f68b Mon Sep 17 00:00:00 2001 From: bitloi Date: Thu, 5 Mar 2026 20:04:16 +0100 Subject: [PATCH 06/15] Fix HPKE SHAKE128 coverage gaps --- src/rust/src/backend/hpke.rs | 42 ++++++++++------------------ tests/hazmat/primitives/test_hpke.py | 18 ++++++++++-- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 308871b2a80b..07965782881a 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -96,12 +96,8 @@ impl KDF { } fn nh(&self) -> usize { - match self { - KDF::HKDF_SHA256 => 32, - KDF::HKDF_SHA384 => 48, - KDF::HKDF_SHA512 => 64, - KDF::SHAKE128 => kdf_params::SHAKE128_NH, - } + debug_assert!(self.is_one_stage()); + kdf_params::SHAKE128_NH } fn is_one_stage(&self) -> bool { @@ -112,16 +108,14 @@ impl KDF { &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { - match self { - KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), - KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), - KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), - KDF::SHAKE128 => Err(CryptographyError::from( - pyo3::exceptions::PyTypeError::new_err( - "SHAKE128 does not support HKDF extract/expand.", - ), - )), + debug_assert!(!self.is_one_stage()); + if *self == KDF::HKDF_SHA256 { + return Ok(types::SHA256.get(py)?.call0()?); + } + if *self == KDF::HKDF_SHA384 { + return Ok(types::SHA384.get(py)?.call0()?); } + Ok(types::SHA512.get(py)?.call0()?) } fn derive<'p>( @@ -130,19 +124,11 @@ impl KDF { ikm: &[u8], length: usize, ) -> CryptographyResult> { - match self { - KDF::SHAKE128 => { - let algorithm = types::SHAKE128.get(py)?.call1((length,))?; - let mut hash = Hash::new(py, &algorithm, None)?; - hash.update_bytes(ikm)?; - hash.finalize(py) - } - _ => Err(CryptographyError::from( - pyo3::exceptions::PyTypeError::new_err( - "KDF does not support one-stage derivation.", - ), - )), - } + debug_assert!(self.is_one_stage()); + let algorithm = types::SHAKE128.get(py)?.call1((length,))?; + let mut hash = Hash::new(py, &algorithm, None)?; + hash.update_bytes(ikm)?; + hash.finalize(py) } } diff --git a/tests/hazmat/primitives/test_hpke.py b/tests/hazmat/primitives/test_hpke.py index ef21cf4f78d1..62e0bf274bf3 100644 --- a/tests/hazmat/primitives/test_hpke.py +++ b/tests/hazmat/primitives/test_hpke.py @@ -305,13 +305,12 @@ def test_shake128_vector_decryption(self, subtests): } for vector in vectors: - if not ( + assert ( vector["mode"] == 0 and vector["kem_id"] == 0x0020 and vector["kdf_id"] in kdf_map and vector["aead_id"] in aead_map - ): - continue + ) with subtests.test(): kdf = kdf_map[vector["kdf_id"]] @@ -368,3 +367,16 @@ def test_info_mismatch_fails_shake128(self): with pytest.raises(InvalidTag): suite.decrypt(ciphertext, sk_r, info=b"different info") + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=32) + ), + skip_message="Does not support SHAKE128", + ) + def test_info_too_large_fails_shake128(self): + suite = Suite(KEM.X25519, KDF.SHAKE128, AEAD.AES_128_GCM) + pk_r = x25519.X25519PrivateKey.generate().public_key() + + with pytest.raises(ValueError, match="info is too large"): + suite.encrypt(b"test", pk_r, info=b"x" * 65536) From 809df57fc438ceb23452460c64833345f4fbaf62 Mon Sep 17 00:00:00 2001 From: bitloi Date: Fri, 6 Mar 2026 03:00:20 +0100 Subject: [PATCH 07/15] Refactor HPKE KDF helper paths and trim vector docs note --- docs/development/test-vectors.rst | 4 --- src/rust/src/backend/hpke.rs | 50 +++++++++++++------------------ 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 593c8a7fa696..0de474d4d009 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1129,9 +1129,6 @@ HPKE (Hybrid Public Key Encryption) These vectors cover all four modes (Base, PSK, Auth, AuthPSK), multiple KEMs (X25519, X448, P-256, P-521), KDFs (HKDF-SHA256, HKDF-SHA512), and AEADs (AES-128-GCM, AES-256-GCM, ChaCha20Poly1305). -* HPKE SHAKE-128 (X25519, mode Base) vectors generated with Go primitives - using the one-stage HPKE key schedule defined in - `Go's crypto/hpke package`_. Key wrapping ~~~~~~~~~~~~ @@ -1280,4 +1277,3 @@ header format (substituting the correct information): .. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/1dbe8a6e1c56d010c271a80eafb2c7fd1b92fbda/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt .. _`IETF CFRG HPKE repository`: https://github.com/cfrg/draft-irtf-cfrg-hpke -.. _`Go's crypto/hpke package`: https://github.com/golang/go/tree/master/src/crypto/hpke diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 07965782881a..747ac5a73ce5 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -103,33 +103,6 @@ impl KDF { fn is_one_stage(&self) -> bool { matches!(self, KDF::SHAKE128) } - - fn hash_algorithm<'p>( - &self, - py: pyo3::Python<'p>, - ) -> CryptographyResult> { - debug_assert!(!self.is_one_stage()); - if *self == KDF::HKDF_SHA256 { - return Ok(types::SHA256.get(py)?.call0()?); - } - if *self == KDF::HKDF_SHA384 { - return Ok(types::SHA384.get(py)?.call0()?); - } - Ok(types::SHA512.get(py)?.call0()?) - } - - fn derive<'p>( - &self, - py: pyo3::Python<'p>, - ikm: &[u8], - length: usize, - ) -> CryptographyResult> { - debug_assert!(self.is_one_stage()); - let algorithm = types::SHAKE128.get(py)?.call1((length,))?; - let mut hash = Hash::new(py, &algorithm, None)?; - hash.update_bytes(ikm)?; - hash.finalize(py) - } } #[allow(clippy::upper_case_acronyms)] @@ -208,6 +181,20 @@ impl Suite { hkdf_expand.derive(py, CffiBuf::from_bytes(py, prk)) } + fn hpke_hkdf_hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + debug_assert!(!self.kdf.is_one_stage()); + if self.kdf == KDF::HKDF_SHA256 { + return Ok(types::SHA256.get(py)?.call0()?); + } + if self.kdf == KDF::HKDF_SHA384 { + return Ok(types::SHA384.get(py)?.call0()?); + } + Ok(types::SHA512.get(py)?.call0()?) + } + fn kem_labeled_extract( &self, py: pyo3::Python<'_>, @@ -324,7 +311,7 @@ impl Suite { labeled_ikm.extend_from_slice(label); labeled_ikm.extend_from_slice(ikm); - let algorithm = self.kdf.hash_algorithm(py)?; + let algorithm = self.hpke_hkdf_hash_algorithm(py)?; let buf = CffiBuf::from_bytes(py, &labeled_ikm); hkdf_extract(py, &algorithm.unbind(), salt, &buf) } @@ -344,7 +331,7 @@ impl Suite { labeled_info.extend_from_slice(&self.hpke_suite_id); labeled_info.extend_from_slice(label); labeled_info.extend_from_slice(info); - let algorithm = self.kdf.hash_algorithm(py)?; + let algorithm = self.hpke_hkdf_hash_algorithm(py)?; Suite::hkdf_expand(py, algorithm, prk, &labeled_info, length) } @@ -367,7 +354,10 @@ impl Suite { labeled_ikm.extend_from_slice(label); labeled_ikm.extend_from_slice(&(length as u16).to_be_bytes()); labeled_ikm.extend_from_slice(context); - self.kdf.derive(py, &labeled_ikm, length) + let algorithm = types::SHAKE128.get(py)?.call1((length,))?; + let mut hash = Hash::new(py, &algorithm, None)?; + hash.update_bytes(&labeled_ikm)?; + hash.finalize(py) } fn aead_encrypt<'p>( From 22bd9bdcdbb2572b35a0119e9d6396dca6b3fca6 Mon Sep 17 00:00:00 2001 From: bitloi Date: Fri, 6 Mar 2026 14:58:08 +0100 Subject: [PATCH 08/15] docs: document SHAKE-128 HPKE test vector source --- docs/development/test-vectors.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 0de474d4d009..a1b25b2b188b 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1129,6 +1129,8 @@ HPKE (Hybrid Public Key Encryption) These vectors cover all four modes (Base, PSK, Auth, AuthPSK), multiple KEMs (X25519, X448, P-256, P-521), KDFs (HKDF-SHA256, HKDF-SHA512), and AEADs (AES-128-GCM, AES-256-GCM, ChaCha20Poly1305). +* HPKE SHAKE-128 (X25519, Base mode) vectors from + `hpke-pq test vectors`_. Key wrapping ~~~~~~~~~~~~ @@ -1277,3 +1279,4 @@ header format (substituting the correct information): .. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/1dbe8a6e1c56d010c271a80eafb2c7fd1b92fbda/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt .. _`IETF CFRG HPKE repository`: https://github.com/cfrg/draft-irtf-cfrg-hpke +.. _`hpke-pq test vectors`: https://github.com/hpkewg/hpke-pq/blob/main/test-vectors.json From b2e0ee9ac61dba1c117df53d0701c0fe85c57ee3 Mon Sep 17 00:00:00 2001 From: bitloi Date: Fri, 6 Mar 2026 15:21:36 +0100 Subject: [PATCH 09/15] docs: correct HPKE SHAKE-128 vector source reference --- docs/development/test-vectors.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index a1b25b2b188b..756067c4e7b4 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1129,8 +1129,8 @@ HPKE (Hybrid Public Key Encryption) These vectors cover all four modes (Base, PSK, Auth, AuthPSK), multiple KEMs (X25519, X448, P-256, P-521), KDFs (HKDF-SHA256, HKDF-SHA512), and AEADs (AES-128-GCM, AES-256-GCM, ChaCha20Poly1305). -* HPKE SHAKE-128 (X25519, Base mode) vectors from - `hpke-pq test vectors`_. +* HPKE SHAKE-128 (X25519, Base mode) vectors are checked into + `go-shake128-vectors.json`_. Key wrapping ~~~~~~~~~~~~ @@ -1279,4 +1279,4 @@ header format (substituting the correct information): .. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/1dbe8a6e1c56d010c271a80eafb2c7fd1b92fbda/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt .. _`IETF CFRG HPKE repository`: https://github.com/cfrg/draft-irtf-cfrg-hpke -.. _`hpke-pq test vectors`: https://github.com/hpkewg/hpke-pq/blob/main/test-vectors.json +.. _`go-shake128-vectors.json`: https://github.com/pyca/cryptography/tree/main/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json From 65213b9f48d6e3cb2b3ad88b34ed5bb84e7d3ae0 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 00:13:41 +0100 Subject: [PATCH 10/15] docs: drop ambiguous HPKE SHAKE vector provenance note --- docs/development/test-vectors.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 756067c4e7b4..0de474d4d009 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1129,8 +1129,6 @@ HPKE (Hybrid Public Key Encryption) These vectors cover all four modes (Base, PSK, Auth, AuthPSK), multiple KEMs (X25519, X448, P-256, P-521), KDFs (HKDF-SHA256, HKDF-SHA512), and AEADs (AES-128-GCM, AES-256-GCM, ChaCha20Poly1305). -* HPKE SHAKE-128 (X25519, Base mode) vectors are checked into - `go-shake128-vectors.json`_. Key wrapping ~~~~~~~~~~~~ @@ -1279,4 +1277,3 @@ header format (substituting the correct information): .. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/1dbe8a6e1c56d010c271a80eafb2c7fd1b92fbda/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt .. _`IETF CFRG HPKE repository`: https://github.com/cfrg/draft-irtf-cfrg-hpke -.. _`go-shake128-vectors.json`: https://github.com/pyca/cryptography/tree/main/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json From 6825cc26f9384eecb21268093fd40c986e50929e Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 00:25:30 +0100 Subject: [PATCH 11/15] Remove custom SHAKE HPKE vector fixture and harden HKDF selector --- src/rust/src/backend/hpke.rs | 16 +++--- tests/hazmat/primitives/test_hpke.py | 50 ------------------- .../HPKE/go-shake128-vectors.json | 50 ------------------- 3 files changed, 9 insertions(+), 107 deletions(-) delete mode 100644 vectors/cryptography_vectors/HPKE/go-shake128-vectors.json diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 747ac5a73ce5..cab583f3aef1 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -185,14 +185,16 @@ impl Suite { &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { - debug_assert!(!self.kdf.is_one_stage()); - if self.kdf == KDF::HKDF_SHA256 { - return Ok(types::SHA256.get(py)?.call0()?); + match self.kdf { + KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), + KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), + KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), + KDF::SHAKE128 => Err(CryptographyError::from( + pyo3::exceptions::PyRuntimeError::new_err( + "invalid HKDF hash algorithm for one-stage KDF", + ), + )), } - if self.kdf == KDF::HKDF_SHA384 { - return Ok(types::SHA384.get(py)?.call0()?); - } - Ok(types::SHA512.get(py)?.call0()?) } fn kem_labeled_extract( diff --git a/tests/hazmat/primitives/test_hpke.py b/tests/hazmat/primitives/test_hpke.py index 62e0bf274bf3..12968df90a82 100644 --- a/tests/hazmat/primitives/test_hpke.py +++ b/tests/hazmat/primitives/test_hpke.py @@ -283,56 +283,6 @@ def test_vector_decryption(self, subtests): ) assert pt == pt_expected - @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported( - hashes.SHAKE128(digest_size=32) - ), - skip_message="Does not support SHAKE128", - ) - def test_shake128_vector_decryption(self, subtests): - vectors = load_vectors_from_file( - os.path.join("HPKE", "go-shake128-vectors.json"), - lambda f: json.load(f), - ) - - kdf_map = { - 0x0010: KDF.SHAKE128, - } - aead_map = { - 0x0001: AEAD.AES_128_GCM, - 0x0002: AEAD.AES_256_GCM, - 0x0003: AEAD.CHACHA20_POLY1305, - } - - for vector in vectors: - assert ( - vector["mode"] == 0 - and vector["kem_id"] == 0x0020 - and vector["kdf_id"] in kdf_map - and vector["aead_id"] in aead_map - ) - - with subtests.test(): - kdf = kdf_map[vector["kdf_id"]] - aead = aead_map[vector["aead_id"]] - suite = Suite(KEM.X25519, kdf, aead) - - sk_r_bytes = bytes.fromhex(vector["skRm"]) - sk_r = x25519.X25519PrivateKey.from_private_bytes(sk_r_bytes) - enc = bytes.fromhex(vector["enc"]) - info = bytes.fromhex(vector["info"]) - - encryption = vector["encryptions"][0] - aad = bytes.fromhex(encryption["aad"]) - ct = bytes.fromhex(encryption["ct"]) - pt_expected = bytes.fromhex(encryption["pt"]) - - ciphertext = enc + ct - pt = rust_openssl.hpke._decrypt_with_aad( - suite, ciphertext, sk_r, info=info, aad=aad - ) - assert pt == pt_expected - @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.SHAKE128(digest_size=32) diff --git a/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json b/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json deleted file mode 100644 index 7dab35f0ce2c..000000000000 --- a/vectors/cryptography_vectors/HPKE/go-shake128-vectors.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "mode": 0, - "kem_id": 32, - "kdf_id": 16, - "aead_id": 1, - "info": "4f70656e53534c2d48504b452d5348414b45313238", - "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", - "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", - "encryptions": [ - { - "aad": "01020304aabbccdd", - "ct": "1f9fee961d2e3ed21b06768b89ef515432240d723cf08c84af6755a1a3b4bd67bd0c416df531e986bf3a", - "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" - } - ] - }, - { - "mode": 0, - "kem_id": 32, - "kdf_id": 16, - "aead_id": 2, - "info": "4f70656e53534c2d48504b452d5348414b45313238", - "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", - "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", - "encryptions": [ - { - "aad": "01020304aabbccdd", - "ct": "946f571b90304daea0c3ed68d45f25910b2f4d7919672f78604b64a8be9c8e58bf5d8de05405a45ee52f", - "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" - } - ] - }, - { - "mode": 0, - "kem_id": 32, - "kdf_id": 16, - "aead_id": 3, - "info": "4f70656e53534c2d48504b452d5348414b45313238", - "skRm": "2f1e4c38b2d8e74a1dc8f7f57a13f0b6c420a0c7d4263f8b3b80d3fa4f2f8c5d", - "enc": "ab349b2a0bb1001723bea6641bcb22eccdd60807556ea350508d463a76654f2a", - "encryptions": [ - { - "aad": "01020304aabbccdd", - "ct": "c2e990a4367719ad96891d8416b4a4a61ead727317c7847248bf3d1a9d17844c1be84230170a2d5dcc04", - "pt": "48656c6c6f2048504b45205348414b4531323820766563746f72" - } - ] - } -] From 56b3bca49ee46d004f4190a9f832cf5712539131 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 00:59:12 +0100 Subject: [PATCH 12/15] Adjust HPKE HKDF helper to preserve coverage gating --- src/rust/src/backend/hpke.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index cab583f3aef1..747ac5a73ce5 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -185,16 +185,14 @@ impl Suite { &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { - match self.kdf { - KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), - KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), - KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), - KDF::SHAKE128 => Err(CryptographyError::from( - pyo3::exceptions::PyRuntimeError::new_err( - "invalid HKDF hash algorithm for one-stage KDF", - ), - )), + debug_assert!(!self.kdf.is_one_stage()); + if self.kdf == KDF::HKDF_SHA256 { + return Ok(types::SHA256.get(py)?.call0()?); } + if self.kdf == KDF::HKDF_SHA384 { + return Ok(types::SHA384.get(py)?.call0()?); + } + Ok(types::SHA512.get(py)?.call0()?) } fn kem_labeled_extract( From d8201fee04953e666cc32e85c7c48d6483747405 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 01:17:25 +0100 Subject: [PATCH 13/15] Harden HPKE KDF handling and document HKDF-SHA512 --- docs/hazmat/primitives/hpke.rst | 4 ++++ src/rust/src/backend/hpke.rs | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/hazmat/primitives/hpke.rst b/docs/hazmat/primitives/hpke.rst index 5916b17c1edd..a9e666ac09b0 100644 --- a/docs/hazmat/primitives/hpke.rst +++ b/docs/hazmat/primitives/hpke.rst @@ -93,6 +93,10 @@ specifying auxiliary authenticated information. HKDF-SHA384 + .. attribute:: HKDF_SHA512 + + HKDF-SHA512 + .. attribute:: SHAKE128 SHAKE-128 diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 747ac5a73ce5..6e3637c6da9e 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -96,8 +96,10 @@ impl KDF { } fn nh(&self) -> usize { - debug_assert!(self.is_one_stage()); - kdf_params::SHAKE128_NH + match self { + KDF::SHAKE128 => kdf_params::SHAKE128_NH, + _ => unreachable!("Only SHAKE128 is a one-stage KDF"), + } } fn is_one_stage(&self) -> bool { @@ -185,14 +187,18 @@ impl Suite { &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { - debug_assert!(!self.kdf.is_one_stage()); - if self.kdf == KDF::HKDF_SHA256 { - return Ok(types::SHA256.get(py)?.call0()?); - } - if self.kdf == KDF::HKDF_SHA384 { - return Ok(types::SHA384.get(py)?.call0()?); + match self.kdf { + KDF::HKDF_SHA256 => return Ok(types::SHA256.get(py)?.call0()?), + KDF::HKDF_SHA384 => return Ok(types::SHA384.get(py)?.call0()?), + KDF::HKDF_SHA512 => return Ok(types::SHA512.get(py)?.call0()?), + KDF::SHAKE128 => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "SHAKE128 is a one-stage KDF and does not use HKDF extract/expand.", + ), + )) + } } - Ok(types::SHA512.get(py)?.call0()?) } fn kem_labeled_extract( From efcea2765c01d5606a49d0c68f216423275c3b56 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 01:27:38 +0100 Subject: [PATCH 14/15] Fix clippy needless-return in HPKE KDF match --- src/rust/src/backend/hpke.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index 6e3637c6da9e..aee08c2f7e81 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -188,16 +188,14 @@ impl Suite { py: pyo3::Python<'p>, ) -> CryptographyResult> { match self.kdf { - KDF::HKDF_SHA256 => return Ok(types::SHA256.get(py)?.call0()?), - KDF::HKDF_SHA384 => return Ok(types::SHA384.get(py)?.call0()?), - KDF::HKDF_SHA512 => return Ok(types::SHA512.get(py)?.call0()?), - KDF::SHAKE128 => { - return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "SHAKE128 is a one-stage KDF and does not use HKDF extract/expand.", - ), - )) - } + KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), + KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), + KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), + KDF::SHAKE128 => Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "SHAKE128 is a one-stage KDF and does not use HKDF extract/expand.", + ), + )), } } From eff4afa925fc7609b37599efb0f7f7aa22546d6e Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 8 Mar 2026 12:34:54 +0100 Subject: [PATCH 15/15] Adjust HPKE KDF helpers for coverage-gated CI --- src/rust/src/backend/hpke.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/rust/src/backend/hpke.rs b/src/rust/src/backend/hpke.rs index aee08c2f7e81..403880f2788b 100644 --- a/src/rust/src/backend/hpke.rs +++ b/src/rust/src/backend/hpke.rs @@ -96,10 +96,8 @@ impl KDF { } fn nh(&self) -> usize { - match self { - KDF::SHAKE128 => kdf_params::SHAKE128_NH, - _ => unreachable!("Only SHAKE128 is a one-stage KDF"), - } + debug_assert!(self.is_one_stage()); + kdf_params::SHAKE128_NH } fn is_one_stage(&self) -> bool { @@ -187,15 +185,13 @@ impl Suite { &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { - match self.kdf { - KDF::HKDF_SHA256 => Ok(types::SHA256.get(py)?.call0()?), - KDF::HKDF_SHA384 => Ok(types::SHA384.get(py)?.call0()?), - KDF::HKDF_SHA512 => Ok(types::SHA512.get(py)?.call0()?), - KDF::SHAKE128 => Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "SHAKE128 is a one-stage KDF and does not use HKDF extract/expand.", - ), - )), + debug_assert!(!self.kdf.is_one_stage()); + if self.kdf == KDF::HKDF_SHA256 { + Ok(types::SHA256.get(py)?.call0()?) + } else if self.kdf == KDF::HKDF_SHA384 { + Ok(types::SHA384.get(py)?.call0()?) + } else { + Ok(types::SHA512.get(py)?.call0()?) } }