From f3a53fb5794bc33a467eab728e33e9f23bcd05dc Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 28 Jan 2026 15:57:52 -0800 Subject: [PATCH 1/2] Zeroize both seed values (z,d) per spec FIPS 203 section 3.3 "Destruction of intermediate values" specifies that both parts of the seed (z, d) should be treated in the same manner at the decapsulation key itself, which is to zeroize. --- ml-kem/src/kem.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index 2644d92..e3138d0 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -57,7 +57,9 @@ where { fn drop(&mut self) { self.dk_pke.zeroize(); - self.d.zeroize(); + if let Some(d) = self.d.as_mut() { + d.zeroize(); + } self.z.zeroize(); } } From 30faa183312a92fae5fab6e864f2e2f41f3f09d1 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 28 Jan 2026 16:12:57 -0800 Subject: [PATCH 2/2] Validate expanded decapsulation key hash FIPS 203 section 7.2 requires verifying the provided encapsulation key hash against the encapsulation key included in the serialized format. --- ml-kem/src/kem.rs | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index e3138d0..4c0bbc5 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -5,6 +5,7 @@ pub use ::kem::{ Decapsulate, Decapsulator, Encapsulate, Generate, InvalidKey, Key, KeyExport, KeyInit, KeySizeUser, TryKeyInit, }; +use sha3::Digest; use crate::{ B32, Encoded, EncodedSizeUser, KemCore, Seed, @@ -57,9 +58,7 @@ where { fn drop(&mut self) { self.dk_pke.zeroize(); - if let Some(d) = self.d.as_mut() { - d.zeroize(); - } + self.d.zeroize(); self.z.zeroize(); } } @@ -171,8 +170,10 @@ where let (dk_pke, ek_pke, h, z) = P::split_dk(enc); let ek_pke = EncryptionKey::from_bytes(ek_pke)?; - // XXX(RLB): The encoding here is redundant, since `h` can be computed from `ek_pke`. - // Should we verify that the provided `h` value is valid? + let test = sha3::Sha3_256::digest(ek_pke.to_bytes()); + if test.as_slice() != h.as_slice() { + return Err(InvalidKey); + } Ok(Self { dk_pke: DecryptionKey::from_bytes(dk_pke), @@ -375,6 +376,7 @@ mod test { use super::*; use crate::{MlKem512Params, MlKem768Params, MlKem1024Params}; use ::kem::{Decapsulate, Encapsulate, Generate}; + use array::typenum::Unsigned; use getrandom::SysRng; use rand_core::UnwrapErr; @@ -423,6 +425,30 @@ mod test { expanded_key_test::(); } + fn invalid_hash_expanded_key_test

() + where + P: KemParams, + { + let mut rng = UnwrapErr(SysRng); + let dk_original = DecapsulationKey::

::generate_from_rng(&mut rng); + + let mut dk_encoded = dk_original.to_encoded_bytes(); + // Corrupt the hash value + let hash_offset = P::NttVectorSize::USIZE + P::EncryptionKeySize::USIZE; + dk_encoded[hash_offset] ^= 0xFF; + + let dk_decoded: Result, InvalidKey> = + DecapsulationKey::from_encoded_bytes(&dk_encoded); + assert!(dk_decoded.is_err()); + } + + #[test] + fn invalid_hash_expanded_key() { + invalid_hash_expanded_key_test::(); + invalid_hash_expanded_key_test::(); + invalid_hash_expanded_key_test::(); + } + fn seed_test

() where P: KemParams,