From baeb12fd1882d42cff230cd471a267b086de1b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 13 Feb 2025 18:11:42 +0100 Subject: [PATCH] add an additional MLKEM implementation, and select with a feature --- Cargo.toml | 1 + src/core/kem.rs | 157 ++--------------------------------- src/core/kem/mlkem.rs | 183 +++++++++++++++++++++++++++++++++++++++++ src/core/mod.rs | 18 ++-- src/core/primitives.rs | 13 +-- src/traits.rs | 13 +-- 6 files changed, 216 insertions(+), 169 deletions(-) create mode 100644 src/core/kem/mlkem.rs diff --git a/Cargo.toml b/Cargo.toml index cfe82ff8..65ad7ac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ required-features = ["test-utils"] [features] test-utils = [] +mlkem768 = [] [dependencies] cosmian_crypto_core = { version = "10.0.0", default-features = false, features = ["ser", "sha3", "aes", "curve25519"] } diff --git a/src/core/kem.rs b/src/core/kem.rs index be5e1e73..16cc8bef 100644 --- a/src/core/kem.rs +++ b/src/core/kem.rs @@ -1,154 +1,7 @@ -use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; -use ml_kem::{ - array::Array, - kem::{Decapsulate, Encapsulate}, - EncodedSizeUser, KemCore, -}; -use zeroize::Zeroize; +pub mod mlkem; -use crate::traits::Kem; -use crate::{core::SHARED_SECRET_LENGTH, Error}; +#[cfg(not(feature = "mlkem768"))] +pub use mlkem::MlKem512 as MlKem; -#[derive(Debug, PartialEq, Clone)] -pub struct EncapsulationKey512(Box<::EncapsulationKey>); - -impl Serializable for EncapsulationKey512 { - type Error = Error; - - fn length(&self) -> usize { - 800 - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut bytes = self.0.as_bytes(); - let n = ser.write_array(&bytes)?; - bytes.zeroize(); - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let mut bytes = Array::from(de.read_array::<800>()?); - let ek = <::EncapsulationKey>::from_bytes(&bytes); - bytes.zeroize(); - Ok(Self(Box::new(ek))) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct DecapsulationKey512(Box<::DecapsulationKey>); - -impl DecapsulationKey512 { - pub fn ek(&self) -> EncapsulationKey512 { - EncapsulationKey512(Box::new(self.0.encapsulation_key().clone())) - } -} - -impl Serializable for DecapsulationKey512 { - type Error = Error; - - fn length(&self) -> usize { - 1632 - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut bytes = self.0.as_bytes(); - let n = ser.write_array(&bytes)?; - bytes.zeroize(); - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let mut bytes = Array::from(de.read_array::<1632>()?); - let dk = <::DecapsulationKey>::from_bytes(&bytes); - bytes.zeroize(); - Ok(Self(Box::new(dk))) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct Encapsulation512(Box::CiphertextSize>>); - -impl Serializable for Encapsulation512 { - type Error = Error; - - fn length(&self) -> usize { - 768 - } - - fn write(&self, ser: &mut Serializer) -> Result { - Ok(ser.write_array(&self.0)?) - } - - fn read(de: &mut Deserializer) -> Result { - Ok(Self(Box::new(Array::< - u8, - ::CiphertextSize, - >::from(de.read_array::<768>()?)))) - } -} - -pub struct MlKem512; - -impl Kem for MlKem512 { - type EncapsulationKey = EncapsulationKey512; - type DecapsulationKey = DecapsulationKey512; - type SessionKey = Secret; - - type Encapsulation = Encapsulation512; - - type Error = Error; - - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error> { - let (dk, ek) = ::generate(rng); - Ok(( - DecapsulationKey512(Box::new(dk)), - EncapsulationKey512(Box::new(ek)), - )) - } - - fn enc( - ek: &Self::EncapsulationKey, - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error> { - let (enc, mut ss) = - ek.0.encapsulate(rng) - .map_err(|e| Error::Kem(format!("{:?}", e)))?; - let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok((ss, Encapsulation512(Box::new(enc)))) - } - - fn dec( - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result { - let mut ss = - dk.0.decapsulate(&enc.0) - .map_err(|e| Self::Error::Kem(format!("{e:?}")))?; - let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok(ss) - } -} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - - #[test] - fn test_mlkem() { - let mut rng = CsRng::from_entropy(); - let (dk, ek) = MlKem512::keygen(&mut rng).unwrap(); - test_serialization(&dk).unwrap(); - test_serialization(&ek).unwrap(); - let (ss1, enc) = MlKem512::enc(&ek, &mut rng).unwrap(); - test_serialization(&enc).unwrap(); - let ss2 = MlKem512::dec(&dk, &enc).unwrap(); - assert_eq!(ss1, ss2); - } -} +#[cfg(feature = "mlkem768")] +pub use mlkem::MlKem768 as MlKem; diff --git a/src/core/kem/mlkem.rs b/src/core/kem/mlkem.rs new file mode 100644 index 00000000..e3722163 --- /dev/null +++ b/src/core/kem/mlkem.rs @@ -0,0 +1,183 @@ +use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; +use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; +use ml_kem::{ + array::Array, + kem::{Decapsulate, Encapsulate}, + EncodedSizeUser, KemCore, +}; +use zeroize::Zeroize; + +use crate::traits::Kem; +use crate::{core::SHARED_SECRET_LENGTH, Error}; + +macro_rules! make_mlkem { + ($base: ident, $ek: ident, $ek_len: literal, $dk: ident, $dk_len: literal, $enc: ident, $enc_len:literal) => { + #[derive(Debug, PartialEq, Clone)] + pub struct $ek(Box<::EncapsulationKey>); + + impl Serializable for $ek { + type Error = Error; + + fn length(&self) -> usize { + $ek_len + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut bytes = self.0.as_bytes(); + let n = ser.write_array(&bytes)?; + bytes.zeroize(); + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let mut bytes = Array::from(de.read_array::<$ek_len>()?); + let ek = <::EncapsulationKey>::from_bytes(&bytes); + bytes.zeroize(); + Ok(Self(Box::new(ek))) + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct $dk(Box<::DecapsulationKey>); + + #[allow(dead_code)] + impl $dk { + pub fn ek(&self) -> $ek { + $ek(Box::new(self.0.encapsulation_key().clone())) + } + } + + impl Serializable for $dk { + type Error = Error; + + fn length(&self) -> usize { + $dk_len + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut bytes = self.0.as_bytes(); + let n = ser.write_array(&bytes)?; + bytes.zeroize(); + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let mut bytes = Array::from(de.read_array::<$dk_len>()?); + let dk = <::DecapsulationKey>::from_bytes(&bytes); + bytes.zeroize(); + Ok(Self(Box::new(dk))) + } + } + + #[derive(Debug, PartialEq, Eq, Clone, Hash)] + pub struct $enc(Box::CiphertextSize>>); + + impl Serializable for $enc { + type Error = Error; + + fn length(&self) -> usize { + $enc_len + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write_array(&self.0)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(Box::new(Array::< + u8, + ::CiphertextSize, + >::from(de.read_array::<$enc_len>()?)))) + } + } + + pub struct $base; + + impl Kem for $base { + type EncapsulationKey = $ek; + type DecapsulationKey = $dk; + type SessionKey = Secret; + + type Encapsulation = $enc; + + type Error = Error; + + fn keygen( + rng: &mut impl CryptoRngCore, + ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error> { + let (dk, ek) = ::generate(rng); + Ok(($dk(Box::new(dk)), $ek(Box::new(ek)))) + } + + fn enc( + ek: &Self::EncapsulationKey, + rng: &mut impl CryptoRngCore, + ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error> { + let (enc, mut ss) = + ek.0.encapsulate(rng) + .map_err(|e| Error::Kem(format!("{:?}", e)))?; + let ss = Secret::from_unprotected_bytes(ss.as_mut()); + Ok((ss, $enc(Box::new(enc)))) + } + + fn dec( + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result { + let mut ss = + dk.0.decapsulate(&enc.0) + .map_err(|e| Self::Error::Kem(format!("{e:?}")))?; + let ss = Secret::from_unprotected_bytes(ss.as_mut()); + Ok(ss) + } + } + }; +} + +make_mlkem!( + MlKem512, + EncapsulationKey512, + 800, + DecapsulationKey512, + 1632, + Encapsulation512, + 768 +); + +make_mlkem!( + MlKem768, + EncapsulationKey768, + 1184, + DecapsulationKey768, + 2400, + Encapsulation768, + 1088 +); + +#[cfg(test)] +mod tests { + use cosmian_crypto_core::{ + bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, + }; + + use super::*; + + macro_rules! test_mlkem { + ($base:ident, $test_name:ident) => { + #[test] + fn $test_name() { + let mut rng = CsRng::from_entropy(); + let (dk, ek) = $base::keygen(&mut rng).unwrap(); + test_serialization(&dk).unwrap(); + test_serialization(&ek).unwrap(); + let (ss1, enc) = $base::enc(&ek, &mut rng).unwrap(); + test_serialization(&enc).unwrap(); + let ss2 = $base::dec(&dk, &enc).unwrap(); + assert_eq!(ss1, ss2); + } + }; + } + + test_mlkem!(MlKem512, test_mlkem512); + test_mlkem!(MlKem768, test_mlkem768); +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 9e853918..3574833e 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,6 +6,7 @@ use std::{ }; use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, SymmetricKey}; +use kem::MlKem; use crate::{ abe_policy::{AccessStructure, Right}, @@ -23,7 +24,7 @@ mod tests; pub mod primitives; -use self::{kem::DecapsulationKey512, kem::MlKem512, nike::R25519}; +use self::nike::R25519; use nike::{EcPoint, Scalar}; /// The length of the secret encapsulated by Covercrypt. @@ -57,8 +58,13 @@ pub const MIN_TRACING_LEVEL: usize = 1; /// Subkeys can be hybridized, in which case they also hold a PQ-KEM secret key. #[derive(Clone, Debug, PartialEq)] enum RightSecretKey { - Hybridized { sk: Scalar, dk: DecapsulationKey512 }, - Classic { sk: Scalar }, + Hybridized { + sk: Scalar, + dk: ::DecapsulationKey, + }, + Classic { + sk: Scalar, + }, } impl RightSecretKey { @@ -67,7 +73,7 @@ impl RightSecretKey { fn random(rng: &mut impl CryptoRngCore, hybridize: bool) -> Result { let sk = Scalar::new(rng); if hybridize { - let (dk, _) = MlKem512::keygen(rng)?; + let (dk, _) = MlKem::keygen(rng)?; Ok(Self::Hybridized { sk, dk }) } else { Ok(Self::Classic { sk }) @@ -108,7 +114,7 @@ impl RightSecretKey { enum RightPublicKey { Hybridized { H: EcPoint, - ek: kem::EncapsulationKey512, + ek: ::EncapsulationKey, }, Classic { H: EcPoint, @@ -467,7 +473,7 @@ impl UserSecretKey { #[derive(Debug, Clone, PartialEq)] enum Encapsulations { - HEncs(Vec<(kem::Encapsulation512, [u8; SHARED_SECRET_LENGTH])>), + HEncs(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), CEncs(Vec<[u8; SHARED_SECRET_LENGTH]>), } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 7a63c445..51504c8a 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -16,7 +16,7 @@ use zeroize::Zeroize; use crate::{ abe_policy::{AccessStructure, AttributeStatus, EncryptionHint, Right}, core::{ - kem, kem::MlKem512, nike::R25519, EcPoint, Encapsulations, KmacSignature, MasterPublicKey, + kem, kem::MlKem, nike::R25519, EcPoint, Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, Scalar, TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, @@ -206,7 +206,7 @@ fn h_encaps( .map(|subkey| match subkey { RightPublicKey::Hybridized { H, ek } => { let K1 = R25519::session_key(&r, H)?; - let (K2, E) = MlKem512::enc(ek, rng)?; + let (K2, E) = MlKem::enc(ek, rng)?; Ok((K1, K2, E)) } RightPublicKey::Classic { .. } => { @@ -348,7 +348,10 @@ fn h_decaps( A: &EcPoint, c: &[EcPoint], tag: &[u8; TAG_LENGTH], - encs: &[(kem::Encapsulation512, [u8; SHARED_SECRET_LENGTH])], + encs: &[( + ::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], ) -> Result>, Error> { let T = { let mut hasher = Sha3::v256(); @@ -377,7 +380,7 @@ fn h_decaps( for secret in usk.secrets.bfs() { if let RightSecretKey::Hybridized { sk, dk } = secret { let mut K1 = R25519::session_key(sk, A)?; - let K2 = MlKem512::dec(dk, E)?; + let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(&K1, Some(&K2), &T), F); let (tag_ij, ss) = J_hash(&S_ij, &U); if tag == &tag_ij { @@ -552,7 +555,7 @@ pub fn full_decaps( if *is_activated { if let RightSecretKey::Hybridized { sk, dk } = secret { let mut K1 = R25519::session_key(sk, &A)?; - let K2 = MlKem512::dec(dk, E)?; + let K2 = MlKem::dec(dk, E)?; try_decaps(right, &mut K1, Some(K2), F)?; } } diff --git a/src/traits.rs b/src/traits.rs index cfcd6e0f..c999e080 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -70,8 +70,9 @@ pub trait PkeAc> { ptx: &[u8], ) -> Result; - /// Attempts decrypting the given ciphertext with the given key. Returns the plaintext upon - /// success, or `None` if this key was not authorized to decrypt this ciphertext. + /// Attempts decrypting the given ciphertext with the given key. Returns the + /// plaintext upon success, or `None` if this key was not authorized to + /// decrypt this ciphertext. fn decrypt( &self, usk: &Self::DecryptionKey, @@ -91,15 +92,15 @@ pub trait Kem { rng: &mut impl CryptoRngCore, ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error>; - /// Generates an encapsulation of a random session key, and returns both the key and its - /// encapsulation. + /// Generates an encapsulation of a random session key, and returns both the + /// key and its encapsulation. fn enc( ek: &Self::EncapsulationKey, rng: &mut impl CryptoRngCore, ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error>; - /// Attempts opening the given encapsulation. Upon failure to decapsulate, returns a random - /// session key. + /// Attempts opening the given encapsulation. Upon failure to decapsulate, + /// returns a random session key. fn dec( dk: &Self::DecapsulationKey, enc: &Self::Encapsulation,