ibe/kem/
mkem.rs

1//! This module contains a generic API around three KEMs to use in a multi-user setting.
2//! It leverages the underlying IBKEM and a DEM to construct a hybrid encryption scheme, which
3//! is used to encrypt a randomly drawn [`SharedSecret`].
4//!
5//! # Example usage:
6//!
7//! In this example we encapsulate a session key for two users.
8//!
9//! ```
10//! use ibe::kem::IBKEM;
11//! use ibe::kem::mkem::{MultiRecipient, Ciphertext};
12//! use ibe::kem::cgw_kv::CGWKV;
13//! use ibe::Derive;
14//!
15//! let mut rng = rand::thread_rng();
16//!
17//! let ids = ["email:w.geraedts@sarif.nl", "email:l.botros@cs.ru.nl"];
18//! let derived: Vec<<CGWKV as IBKEM>::Id> = ids.iter().map(|id| <CGWKV as IBKEM>::Id::derive_str(id)).collect();
19//!
20//! // Create a master key pair.
21//! let (pk, sk) = CGWKV::setup(&mut rng);
22//!
23//! // Generate USKs for both identities.
24//! let usk1 = CGWKV::extract_usk(None, &sk, &derived[0], &mut rng);
25//! let usk2 = CGWKV::extract_usk(None, &sk, &derived[1], &mut rng);
26//!
27//! // Encapsulate a single session key for two users.
28//! let (cts_iter, k) = CGWKV::multi_encaps(&pk, &derived, &mut rng);
29//! let cts: Vec<Ciphertext<CGWKV>> = cts_iter.collect();
30//!
31//! let k1 = CGWKV::multi_decaps(Some(&pk), &usk1, &cts[0]).unwrap();
32//! let k2 = CGWKV::multi_decaps(Some(&pk), &usk2, &cts[1]).unwrap();
33//!
34//! assert_eq!(k, k1);
35//! assert_eq!(k, k2);
36//! ```
37
38use crate::kem::{Compress, Error, SharedSecret, IBKEM, SS_BYTES};
39use core::slice::Iter;
40use rand::{CryptoRng, Rng};
41use subtle::CtOption;
42
43use aes_gcm::aead::{Nonce, Tag};
44use aes_gcm::{AeadInPlace, Aes128Gcm, KeyInit};
45
46#[cfg(feature = "cgwfo")]
47use crate::kem::cgw_fo::CGWFO;
48
49#[cfg(feature = "cgwkv")]
50use crate::kem::cgw_kv::CGWKV;
51
52#[cfg(feature = "kv1")]
53use crate::kem::kiltz_vahlis_one::KV1;
54
55const TAG_SIZE: usize = 16;
56const NONCE_SIZE: usize = 12;
57const KEY_SIZE: usize = 16;
58
59impl SharedSecret {
60    /// Sample random shared secret.
61    fn random<R: Rng + CryptoRng>(r: &mut R) -> Self {
62        let mut ss_bytes = [0u8; SS_BYTES];
63        r.fill_bytes(&mut ss_bytes);
64
65        SharedSecret(ss_bytes)
66    }
67}
68
69/// A multi-user ciphertext.
70#[derive(Debug, Clone)]
71pub struct Ciphertext<K: IBKEM> {
72    ct_asymm: K::Ct,
73    ct_symm: [u8; SS_BYTES],
74    tag: Tag<Aes128Gcm>,
75    nonce: Nonce<Aes128Gcm>,
76}
77
78/// Iterator that produces multi-user ciphertexts.
79#[derive(Debug)]
80pub struct Ciphertexts<'a, K: IBKEM, R> {
81    ss: SharedSecret,
82    pk: &'a K::Pk,
83    ids: Iter<'a, K::Id>,
84    rng: &'a mut R,
85}
86
87impl<'a, K, R> Iterator for Ciphertexts<'a, K, R>
88where
89    K: IBKEM,
90    R: Rng + CryptoRng,
91{
92    type Item = Ciphertext<K>;
93
94    fn next(&mut self) -> Option<Self::Item> {
95        let id = self.ids.next()?;
96
97        let (ct_asymm, kek) = <K as IBKEM>::encaps(self.pk, id, self.rng);
98
99        let aead = Aes128Gcm::new_from_slice(&kek.0[..KEY_SIZE]).unwrap();
100        let nonce_bytes = self.rng.gen::<[u8; NONCE_SIZE]>();
101        let nonce = Nonce::<Aes128Gcm>::from_slice(&nonce_bytes);
102
103        let mut shared_key = self.ss.0;
104
105        let tag = aead
106            .encrypt_in_place_detached(nonce, b"", &mut shared_key)
107            .unwrap();
108
109        Some(Ciphertext::<K> {
110            ct_asymm,
111            ct_symm: shared_key,
112            nonce: *nonce,
113            tag,
114        })
115    }
116}
117
118/// Trait that captures multi-user encapsulation/decapsulation.
119pub trait MultiRecipient: IBKEM {
120    /// Encapsulates a single shared secret under multiple identities.
121    fn multi_encaps<'a, R: Rng + CryptoRng>(
122        pk: &'a <Self as IBKEM>::Pk,
123        ids: impl IntoIterator<IntoIter = Iter<'a, Self::Id>>,
124        rng: &'a mut R,
125    ) -> (Ciphertexts<'a, Self, R>, SharedSecret) {
126        let ss = SharedSecret::random(rng);
127
128        let cts = Ciphertexts {
129            ss,
130            pk,
131            rng,
132            ids: ids.into_iter(),
133        };
134
135        (cts, ss)
136    }
137
138    /// Decapsulates the single shared secret from a [`Ciphertext`].
139    ///
140    /// # Notes
141    ///
142    /// In some cases this function requires the master public key, depending on the underlying
143    /// IBKEM scheme used (e.g., CGWFO).
144    fn multi_decaps(
145        mpk: Option<&Self::Pk>,
146        usk: &Self::Usk,
147        ct: &Ciphertext<Self>,
148    ) -> Result<SharedSecret, Error> {
149        let kek = <Self as IBKEM>::decaps(mpk, usk, &ct.ct_asymm)?;
150        let aead = Aes128Gcm::new_from_slice(&kek.0[..KEY_SIZE]).unwrap();
151        let mut shared_key = ct.ct_symm;
152        aead.decrypt_in_place_detached(&ct.nonce, b"", &mut shared_key, &ct.tag)
153            .map_err(|_e| Error)?;
154
155        Ok(SharedSecret(shared_key))
156    }
157}
158
159macro_rules! impl_mkemct_compress {
160    ($scheme: ident) => {
161        impl Compress for Ciphertext<$scheme> {
162            const OUTPUT_SIZE: usize = $scheme::CT_BYTES + SS_BYTES + TAG_SIZE + NONCE_SIZE;
163            type Output = [u8; Self::OUTPUT_SIZE];
164
165            fn to_bytes(&self) -> Self::Output {
166                use arrayref::mut_array_refs;
167
168                let mut res = [0u8; Self::OUTPUT_SIZE];
169                let (ct_asymm, ct_symm, tag, nonce) =
170                    mut_array_refs![&mut res, $scheme::CT_BYTES, SS_BYTES, TAG_SIZE, NONCE_SIZE];
171
172                *ct_asymm = self.ct_asymm.to_bytes();
173                *ct_symm = self.ct_symm;
174                *tag = self.tag.into();
175                *nonce = self.nonce.into();
176
177                res
178            }
179
180            fn from_bytes(output: &Self::Output) -> CtOption<Self> {
181                use arrayref::array_refs;
182
183                let (ct_asymm, ct_symm, tag, nonce) =
184                    array_refs![&output, $scheme::CT_BYTES, SS_BYTES, TAG_SIZE, NONCE_SIZE];
185
186                let ct_asymm = <$scheme as IBKEM>::Ct::from_bytes(ct_asymm);
187                let tag = Tag::<Aes128Gcm>::from_slice(tag);
188                let nonce = Nonce::<Aes128Gcm>::from_slice(nonce);
189
190                ct_asymm.map(|ct_asymm| Ciphertext {
191                    ct_asymm,
192                    ct_symm: *ct_symm,
193                    tag: *tag,
194                    nonce: *nonce,
195                })
196            }
197        }
198    };
199}
200
201#[cfg(feature = "cgwkv")]
202impl_mkemct_compress!(CGWKV);
203
204#[cfg(feature = "cgwfo")]
205impl_mkemct_compress!(CGWFO);
206
207#[cfg(feature = "kv1")]
208impl_mkemct_compress!(KV1);