Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces Hybrid key #213

Merged
merged 4 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mla/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bitflags = { version = "2.1", default-features = false, features = ["serde"]}
byteorder = { version = "1.3", default-features = false, features = ["std"] }
serde = { version = "1", default-features = false, features = ["derive"] }
bincode = { version = "1.3", default-features = false}
serde-big-array = { version = "0.5.1", default-features = false }
# Crypto needs
# Version fixed due to avoid conflict dependencies with `aes`, `aes-ctr` and `ghash`
generic-array = { version = "0.14", default-features = false}
Expand All @@ -32,6 +33,9 @@ x25519-dalek = { version = "2", default-features = false, features = ["zeroize",
hkdf = { version = "0", default-features = false}
sha2 = { version = "0", default-features = false}
zeroize = { version = "1", default-features = false}
# Post-quantum
ml-kem = { version = "0.1.1", default-features = false }
kem = {version = "0.3.0-pre.0", default-features = false }


[dev-dependencies]
Expand Down
135 changes: 8 additions & 127 deletions mla/src/crypto/ecc.rs
Original file line number Diff line number Diff line change
@@ -1,117 +1,31 @@
use crate::crypto::aesgcm;
use crate::crypto::aesgcm::{ConstantTimeEq, KEY_SIZE, TAG_LENGTH};
use crate::crypto::aesgcm::Key;
use crate::errors::Error;
use hkdf::Hkdf;
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::Zeroize;

pub const PUBLIC_KEY_SIZE: usize = 32;
/// The ciphertext in DH-KEM / ECIES is a public key
pub(crate) type DHKEMCipherText = [u8; PUBLIC_KEY_SIZE];
const DERIVE_KEY_INFO: &[u8; 14] = b"KEY DERIVATION";
const ECIES_NONCE: &[u8; 12] = b"ECIES NONCE0";
const ECIES_ASSOCIATED_DATA: &[u8; 0] = b"";

// TODO: consider DH-KEM
// Implementation inspired from XSTREAM/x25519hkdf.rs
// /!\ in XSTREAM/x25519hkdf.rs, the arguments of Hkdf::new seem inverted
fn derive_key(private_key: &StaticSecret, public_key: &PublicKey) -> Result<[u8; KEY_SIZE], Error> {
pub(crate) fn derive_key(private_key: &StaticSecret, public_key: &PublicKey) -> Result<Key, Error> {
let mut shared_secret = private_key.diffie_hellman(public_key);
let hkdf: Hkdf<Sha256> = Hkdf::new(None, shared_secret.as_bytes());
let mut output = [0u8; KEY_SIZE];
let mut output = Key::default();
hkdf.expand(DERIVE_KEY_INFO, &mut output)?;
shared_secret.zeroize();
Ok(output)
}

#[derive(Serialize, Deserialize)]
struct KeyAndTag {
key: [u8; KEY_SIZE],
tag: [u8; TAG_LENGTH],
}

#[derive(Serialize, Deserialize)]
pub struct MultiRecipientPersistent {
/// Ephemeral public key
public: [u8; 32],
/// Key wrapping for each recipient
encrypted_keys: Vec<KeyAndTag>,
}

impl MultiRecipientPersistent {
pub fn count_keys(&self) -> usize {
self.encrypted_keys.len()
}
}

/// Perform ECIES with several recipients, to share a common `key`, and return a
/// serializable structure (Key-wrapping made thanks to AesGcm256)
pub(crate) fn store_key_for_multi_recipients<T>(
recipients: &[PublicKey],
key: &[u8; KEY_SIZE],
csprng: &mut T,
) -> Result<MultiRecipientPersistent, Error>
where
T: RngCore + CryptoRng,
{
// A `StaticSecret` is used instead of an `EphemeralSecret` to allow for
// multiple diffie-hellman computation
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
let ephemeral = StaticSecret::from(bytes);

let public = PublicKey::from(&ephemeral);
let mut encrypted_keys = Vec::new();
for recipient in recipients.iter() {
// Perform an ECIES to obtain the common key
let dh_key = derive_key(&ephemeral, recipient)?;

// Encrypt the final shared key with it
// As the key is completely random and use only once, no need for a
// random NONCE
let mut cipher = aesgcm::AesGcm256::new(&dh_key, ECIES_NONCE, ECIES_ASSOCIATED_DATA)?;
let mut encrypted_key = [0u8; KEY_SIZE];
encrypted_key.copy_from_slice(key);
cipher.encrypt(&mut encrypted_key);
let mut tag = [0u8; TAG_LENGTH];
tag.copy_from_slice(&cipher.into_tag());
// Save it for later serialization
encrypted_keys.push(KeyAndTag {
key: encrypted_key,
tag,
});
}

Ok(MultiRecipientPersistent {
public: *public.as_bytes(),
encrypted_keys,
})
}

/// Try to recover the shared key from the `MultiRecipientPersistent`, using the private key `private_key`
pub(crate) fn retrieve_key(
persist: &MultiRecipientPersistent,
private_key: &StaticSecret,
) -> Result<Option<[u8; KEY_SIZE]>, Error> {
// Perform an ECIES to obtain the common key
let key = derive_key(private_key, &PublicKey::from(persist.public))?;

// Try to find the correct key using the tag validation
for keytag in persist.encrypted_keys.iter() {
let mut cipher = aesgcm::AesGcm256::new(&key, ECIES_NONCE, ECIES_ASSOCIATED_DATA)?;
let mut data = [0u8; KEY_SIZE];
data.copy_from_slice(&keytag.key);
let tag = cipher.decrypt(&mut data);
if tag.ct_eq(&keytag.tag).unwrap_u8() == 1 {
return Ok(Some(data));
}
}
Ok(None)
}

#[cfg(test)]
mod tests {
use super::*;
use rand::{Rng, SeedableRng};
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use x25519_dalek::{PublicKey, StaticSecret};

Expand All @@ -133,37 +47,4 @@ mod tests {

assert_eq!(symmetric_key, receiver_key);
}

#[test]
fn multi_recipients() {
// Create fake recipients
let mut csprng = ChaChaRng::from_entropy();
let mut bytes = [0u8; 32];
let mut recipients_priv = Vec::new();
let mut recipients_pub = Vec::new();
for _ in 0..5 {
csprng.fill_bytes(&mut bytes);
let skey = StaticSecret::from(bytes);
recipients_pub.push(PublicKey::from(&skey));
recipients_priv.push(skey);
}

// Perform multi-recipients ECIES
let key = csprng.gen::<[u8; KEY_SIZE]>();
let persist = store_key_for_multi_recipients(&recipients_pub, &key, &mut csprng).unwrap();

// Count keys
assert_eq!(persist.count_keys(), 5);

// Ensure each recipient can retrieve the shared key
for private_key in recipients_priv.iter() {
let ret_key = retrieve_key(&persist, private_key).unwrap().unwrap();
assert_eq!(ret_key, key);
}

// Ensure another recipient does not obtain the shared key
csprng.fill_bytes(&mut bytes);
let fake_recipient = StaticSecret::from(bytes);
assert!(retrieve_key(&persist, &fake_recipient).unwrap().is_none());
}
}
Loading
Loading