use pqcrypto_kyber::kyber1024::SharedSecret as KyberSharedSecret;
use pqcrypto_kyber::kyber1024::{decapsulate, encapsulate, keypair};
use pqcrypto_traits::kem::SharedSecret as _;
use rand::rngs::OsRng;
use rand_core::TryRngCore;
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
use ring::hkdf::{self, Prk, Salt};
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, Zeroizing};
fn ct_eq_shared(a: &KyberSharedSecret, b: &KyberSharedSecret) -> bool {
a.as_bytes().ct_eq(b.as_bytes()).unwrap_u8() == 1
}
fn hkdf_derive_key(ss: &[u8]) -> Zeroizing<[u8; 32]> {
let salt = Salt::new(hkdf::HKDF_SHA256, b"rust_checker.pqc.hybrid.hkdf.v1");
let prk: Prk = salt.extract(ss);
let mut out = Zeroizing::new([0u8; 32]);
prk.expand(&[b"chacha20poly1305.key"], hkdf::HKDF_SHA256)
.expect("HKDF expand")
.fill(&mut *out)
.expect("HKDF fill");
out
}
fn aead_encrypt(key_bytes: &[u8; 32], plaintext: &[u8]) -> (Zeroizing<[u8; 12]>, Vec<u8>) {
let unbound = UnboundKey::new(&CHACHA20_POLY1305, key_bytes).expect("key");
let key = LessSafeKey::new(unbound);
let mut nonce_bytes = Zeroizing::new([0u8; 12]);
let mut rng = OsRng;
rng.try_fill_bytes(&mut *nonce_bytes).expect("OsRng");
let nonce = Nonce::assume_unique_for_key(*nonce_bytes);
let mut in_out = plaintext.to_vec();
key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out)
.expect("seal");
(nonce_bytes, in_out)
}
fn aead_decrypt(key_bytes: &[u8; 32], nonce_bytes: &[u8; 12], ciphertext: &[u8]) -> Vec<u8> {
let unbound = UnboundKey::new(&CHACHA20_POLY1305, key_bytes).expect("key");
let key = LessSafeKey::new(unbound);
let nonce = Nonce::assume_unique_for_key(*nonce_bytes);
let mut in_out = ciphertext.to_vec();
key.open_in_place(nonce, Aad::empty(), &mut in_out)
.expect("open")
.to_vec()
}
pub fn run_hybrid_demo() -> usize {
let (pk, sk) = keypair();
let (ss_sender, ct) = encapsulate(&pk);
let ss_receiver = decapsulate(&ct, &sk);
assert!(
ct_eq_shared(&ss_sender, &ss_receiver),
"PQC shared secrets do not match"
);
let key = hkdf_derive_key(ss_sender.as_bytes());
let plaintext = b"pqc-hybrid: hello, world";
let (nonce, ciphertext) = aead_encrypt(&key, plaintext);
let decrypted = aead_decrypt(&key, &nonce, &ciphertext);
assert_eq!(decrypted, plaintext);
let mut another_secret = Zeroizing::new(vec![0u8; 48]);
let mut rng = OsRng;
rng.try_fill_bytes(&mut another_secret[..]).expect("OsRng"); another_secret.zeroize();
ss_sender.as_bytes().len() }
#[cfg(test)]
mod tests {
use super::run_hybrid_demo;
#[test]
fn hybrid_demo_roundtrips() {
let len = run_hybrid_demo();
assert_eq!(len, 32);
}
}