Skip to content

Commit

Permalink
HPKE: two distinct KEM are actually used, one per recipient, one for …
Browse files Browse the repository at this point in the history
…the whole Hybrid KEM
  • Loading branch information
commial committed Aug 13, 2024
1 parent 461d072 commit f2aec20
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 28 deletions.
42 changes: 22 additions & 20 deletions mla/src/crypto/hpke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ type HpkeAead = HPKEAesGcm256;
const HPKE_MODE_BASE: u8 = 0;

/// Custom KEM ID, not in the RFC 9180
/// Hybrid : DHKEM(X25519, HKDF-SHA256) + MLKEM
/// Hybrid : DHKEM(X25519, HKDF-SHA256) + MLKEM, wrapping a shared secret to support multi-recipient
const HYBRID_KEM_ID: u16 = 0x1020;
/// Custom KEM ID, not in the RFC 9180
/// Hybrid Recipient : DHKEM(X25519, HKDF-SHA256) + MLKEM, used internally in the Hybrid KEM to wrap the per-recipient shared secret
const HYBRID_KEM_RECIPIENT_ID: u16 = 0x1120;

/// Return the suite_id for the Hybrid KEM (RFC 9180 §5.1)
/// suite_id = concat(
Expand All @@ -121,9 +124,7 @@ fn build_suite_id(kem_id: u16) -> [u8; 10] {
out
}

/// Key schedule for the sender (RFC 9180 §5.1), internal version
///
/// This version is kept for testing purpose (against RFC 9180 test vectors)
/// Key schedule (RFC 9180 §5.1), mode Base
///
/// Parameters are:
/// - `shared_secret`: the shared secret from the Hybrid KEM
Expand All @@ -134,7 +135,7 @@ fn build_suite_id(kem_id: u16) -> [u8; 10] {
/// - Kem: `kem_id`
/// - Kdf: HKDF-SHA512
/// - Aead: AES-GCM-256
fn key_schedule_s_internal(
fn key_schedule_base(
shared_secret: &[u8],
info: &[u8],
kem_id: u16,
Expand Down Expand Up @@ -164,19 +165,20 @@ fn key_schedule_s_internal(
Ok((key, base_nonce))
}

/// Key schedule for the sender (RFC 9180 §5.1)
///
/// Parameters are:
/// - `shared_secret`: the shared secret from the Hybrid KEM
/// - mode: set to Base (no PSK nor sender key)
/// - info: set to "MLA Encrypt Layer"
/// - psk: no PSK, because the mode used is "Base"
/// - algorithms:
/// - Kem: HYBRID_KEM_ID (custom value)
/// - Kdf: HKDF-SHA512
/// - Aead: AES-GCM-256
pub(crate) fn key_schedule_s(shared_secret: &[u8], info: &[u8]) -> Result<(Key, Nonce), Error> {
key_schedule_s_internal(shared_secret, info, HYBRID_KEM_ID)
/// Key schedule (RFC 9180 §5.1), mode Base, for the custom multi-recipient Hybrid KEM
pub(crate) fn key_schedule_base_hybrid_kem(
shared_secret: &[u8],
info: &[u8],
) -> Result<(Key, Nonce), Error> {
key_schedule_base(shared_secret, info, HYBRID_KEM_ID)
}

/// Key schedule (RFC 9180 §5.1), mode Base, for the custom per-recipient Hybrid KEM
pub(crate) fn key_schedule_base_hybrid_kem_recipient(
shared_secret: &[u8],
info: &[u8],
) -> Result<(Key, Nonce), Error> {
key_schedule_base(shared_secret, info, HYBRID_KEM_RECIPIENT_ID)
}

/// Compute the nonce for a given sequence number (RFC 9180 §5.2)
Expand Down Expand Up @@ -323,9 +325,9 @@ mod tests {
/// Use A.6 for HKDF-SHA512 and AES-256-GCM
/// In MLA, we rather use a custom Kem ID (Hybrid KEM), but this method does the main job
#[test]
fn test_key_schedule_s_internal() {
fn test_key_schedule_base() {
let (key, nonce) =
key_schedule_s_internal(&RFC_A6_SHARED_SECRET, &RFC_A6_INFO, RFC_A6_KEM_ID).unwrap();
key_schedule_base(&RFC_A6_SHARED_SECRET, &RFC_A6_INFO, RFC_A6_KEM_ID).unwrap();
assert_eq!(key, RFC_A6_KEY);
assert_eq!(nonce, RFC_A6_BASE_NONCE);
}
Expand Down
14 changes: 9 additions & 5 deletions mla/src/crypto/hybrid.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::crypto::aesgcm::{ConstantTimeEq, Key, KEY_SIZE, TAG_LENGTH};
use crate::crypto::hpke::{dhkem_decap, dhkem_encap, key_schedule_s, DHKEMCiphertext};
use crate::crypto::hpke::{
dhkem_decap, dhkem_encap, key_schedule_base_hybrid_kem_recipient, DHKEMCiphertext,
};
use crate::errors::ConfigError;
use crate::layers::encrypt::get_crypto_rng;
use hkdf::Hkdf;
Expand Down Expand Up @@ -187,8 +189,9 @@ impl Decapsulate<HybridMultiRecipientEncapsulatedKey, HybridKemSharedSecret> for
&recipient.ct_ml,
);

let (unwrap_key, unwrap_nonce) = key_schedule_s(&ss_recipient, HPKE_INFO_RECIPIENT)
.or(Err(ConfigError::KeyWrappingComputationError))?;
let (unwrap_key, unwrap_nonce) =
key_schedule_base_hybrid_kem_recipient(&ss_recipient, HPKE_INFO_RECIPIENT)
.or(Err(ConfigError::KeyWrappingComputationError))?;

// Unwrap the candidate shared secret and check it using AES-GCM tag validation
let mut cipher = crate::crypto::aesgcm::AesGcm256::new(
Expand Down Expand Up @@ -255,8 +258,9 @@ impl Encapsulate<HybridMultiRecipientEncapsulatedKey, HybridKemSharedSecret>
let ss_recipient = combine(&ss_ecc.0, ss_ml, &ct_ecc.to_bytes(), ct_ml);

// Wrap the final shared secret
let (wrap_key, wrap_nonce) = key_schedule_s(&ss_recipient, HPKE_INFO_RECIPIENT)
.or(Err(ConfigError::KeyWrappingComputationError))?;
let (wrap_key, wrap_nonce) =
key_schedule_base_hybrid_kem_recipient(&ss_recipient, HPKE_INFO_RECIPIENT)
.or(Err(ConfigError::KeyWrappingComputationError))?;
let mut cipher = crate::crypto::aesgcm::AesGcm256::new(
&wrap_key,
&wrap_nonce,
Expand Down
6 changes: 3 additions & 3 deletions mla/src/layers/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::crypto::aesgcm::{
AesGcm256, ConstantTimeEq, Key, Nonce, Tag, KEY_COMMITMENT_SIZE, TAG_LENGTH,
};

use crate::crypto::hpke::{compute_nonce, key_schedule_s};
use crate::crypto::hpke::{compute_nonce, key_schedule_base_hybrid_kem};
use crate::crypto::hybrid::{
HybridKemSharedSecret, HybridMultiRecipientEncapsulatedKey, HybridMultiRecipientsPublicKeys,
HybridPrivateKey, HybridPublicKey,
Expand Down Expand Up @@ -154,7 +154,7 @@ pub(crate) struct InternalEncryptionConfig {

impl InternalEncryptionConfig {
fn from(shared_secret: HybridKemSharedSecret) -> Result<Self, Error> {
let (key, nonce) = key_schedule_s(&shared_secret.0, HPKE_INFO_LAYER)?;
let (key, nonce) = key_schedule_base_hybrid_kem(&shared_secret.0, HPKE_INFO_LAYER)?;

Ok(Self { key, nonce })
}
Expand Down Expand Up @@ -245,7 +245,7 @@ impl EncryptionReaderConfig {
}
for private_key in &self.private_keys {
if let Ok(ss_hybrid) = private_key.decapsulate(&config.multi_recipient) {
let (key, nonce) = key_schedule_s(&ss_hybrid.0, HPKE_INFO_LAYER)
let (key, nonce) = key_schedule_base_hybrid_kem(&ss_hybrid.0, HPKE_INFO_LAYER)
.or(Err(ConfigError::KeyWrappingComputationError))?;
self.encrypt_parameters = Some((key, nonce));
break;
Expand Down

0 comments on commit f2aec20

Please sign in to comment.