From f2a90a51ce3045e676ba105caf96d5037e03d4b4 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Sun, 26 Nov 2023 18:45:59 +0100 Subject: initial commit --- src/kem.rs | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 src/kem.rs (limited to 'src/kem.rs') diff --git a/src/kem.rs b/src/kem.rs new file mode 100644 index 0000000..a8261b6 --- /dev/null +++ b/src/kem.rs @@ -0,0 +1,489 @@ +//! High-level wrapper around HIBE operations. +//! +//! Usually, people don't communicate by sending each other group elements. Therefore, we provide +//! this opinionated and easier-to-use wrapper over the raw HIBE operations. +//! +//! The main struct is [`HybridKem`], which wraps a [`Hibe`] to provide high-level operations. The +//! main differences include: +//! +//! * Generated keys (from [`HibeKem`]) are hashed to `[u8; 16]` (128 Bit) using SHA3 to make it +//! easy to use them in other cryptographic primitives. +//! * Encryption and decryption (from [`HibeCrypt`][super::hibe::HibeCrypt]) is realized by using +//! hybrid encryption, using the KEM and AES encryption on top. This allows you to easily encrypt +//! byte sequences instead of group elements. +//! * Identities are mapped through a [`Mapper`], which makes it easier to specify identities at +//! the call-site. You can for example provide a mapper that takes IP addresses as input, and +//! outputs the correct "raw" identity. +//! * The types are wrapped in proper opaque structs instead of being type aliases. This makes it +//! easier to implement new methods on those types or customize their behaviour (for example, by +//! providing an easier-to-use [`Debug`] implementation). +//! * The methods in this module are restricted to [`CryptoRng`] random generators to enforce the +//! use of cryptographically secure algorithms. +//! +//! To provide easier usability, this implementation is hardwired to use [`BonehBoyenGoh`] as the +//! underlying HIBE. This allows us to not fiddle around with too many generic parameters and +//! generic bounds. Maybe this will change in the future. +//! +//! As a default mapper, [`HashMapper`] is provided, which uses the existing [`std::hash::Hash`] +//! implementation on types to hash to a identity. Note that this may lead to collisions, like with +//! any hash — but they are expected to by unlikely, as the underlying hash is SHA3-256. +use super::{ + error::{Error, Result}, + hibe::{BonehBoyenGoh, Hibe, HibeKem}, + ByteAccess, Mapper, +}; + +use std::{ + fmt::{self, Debug}, + hash::{Hash, Hasher}, +}; + +use aes::cipher::{KeyIvInit, StreamCipher}; +use bls12_381_plus::{Gt, Scalar}; +use rand::{CryptoRng, Rng}; +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Sha3_256}; + +type AesCtr = ctr::Ctr64LE; +static IV: [u8; 16] = [0; 16]; + +/// Represents a public key. +/// +/// In the context of HIBE, this is the "master public key", sometimes also called the "public +/// parameters". The key does not represent the public key for a single identity, but rather the +/// global public key. The encryption functionality then takes the identity as an additional +/// parameter. +/// +/// You mainly want to pass this object around (e.g. to [`HybridKem::encrypt`]) without caring +/// about its internals. You can however serialize and deserialize a key to save or transmit it. +/// +/// Note that the debug output does not output all inner bytes and instead outputs a small +/// fingerprint only. This makes it easier to use the debug output, as the actual value has too +/// many bytes to show nicely. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PublicKey(::PublicKey); + +impl From<::PublicKey> for PublicKey { + fn from(value: ::PublicKey) -> Self { + Self(value) + } +} + +impl ByteAccess for PublicKey { + fn bytes(&self) -> Vec { + bincode::serialize(&self.0).unwrap() + } +} + +impl Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PublicKey") + .field(&self.fingerprint()) + .finish() + } +} + +/// Represents a the master secret key. +/// +/// The master secret key allows the holder to generate secret keys for any identity. +/// +/// You mainly want to pass this object around (e.g. to [`HybridKem::generate_key`]) without caring +/// about its internals. You can however serialize and deserialize a key to save or transmit it. +/// +/// Note that the debug output does not output all inner bytes and instead outputs a small +/// fingerprint only. This makes it easier to use the debug output, as the actual value has too +/// many bytes to show nicely. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct MasterKey(::MasterKey); + +impl From<::MasterKey> for MasterKey { + fn from(value: ::MasterKey) -> Self { + Self(value) + } +} + +impl ByteAccess for MasterKey { + fn bytes(&self) -> Vec { + bincode::serialize(&self.0).unwrap() + } +} + +impl Debug for MasterKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("MasterKey") + .field(&self.fingerprint()) + .finish() + } +} + +/// Represents an identity's secret key. +/// +/// This private key allows the holder to decrypt ciphertexts for the identity it belongs to. +/// +/// You mainly want to pass this object around (e.g. to [`HybridKem::decrypt`]) without caring +/// about its internals. You can however serialize and deserialize a key to save or transmit it. +/// +/// Note that the debug output does not output all inner bytes and instead outputs a small +/// fingerprint only. This makes it easier to use the debug output, as the actual value has too +/// many bytes to show nicely. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PrivateKey(::PrivateKey); + +impl From<::PrivateKey> for PrivateKey { + fn from(value: ::PrivateKey) -> Self { + Self(value) + } +} + +impl ByteAccess for PrivateKey { + fn bytes(&self) -> Vec { + bincode::serialize(&self.0).unwrap() + } +} + +impl Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PrivateKey") + .field(&self.fingerprint()) + .finish() + } +} + +/// Represents an encapsulated key. +/// +/// An encapsulated key is the precursor to a shared secret: by applying their secret key to it, an +/// identity can generate the same key that has been embedded by the creator of the encapsulation. +/// +/// You mainly want to pass this object around (e.g. to [`HybridKem::decapsulate`]) without caring +/// about its internals. You can however serialize and deserialize a key to save or transmit it. +/// +/// Note that the debug output does not output all inner bytes and instead outputs a small +/// fingerprint only. This makes it easier to use the debug output, as the actual value has too +/// many bytes to show nicely. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct EncapsulatedKey(::EncapsulatedKey); + +impl From<::EncapsulatedKey> for EncapsulatedKey { + fn from(value: ::EncapsulatedKey) -> Self { + Self(value) + } +} + +impl ByteAccess for EncapsulatedKey { + fn bytes(&self) -> Vec { + bincode::serialize(&self.0).unwrap() + } +} + +impl Debug for EncapsulatedKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("EncapsulatedKey") + .field(&self.fingerprint()) + .finish() + } +} + +fn hash_from_curve(element: Gt) -> [u8; 16] { + let mut result = [0; 16]; + result.copy_from_slice(&Sha3_256::digest(element.to_bytes())[..16]); + result +} + +/// High-level implementation of HIBE operations. +/// +/// This struct internally uses the [`BonehBoyenGoh`]-HIBE to provide high-level key-encapsulation +/// and encryption. In addition, this struct keeps a [`Mapper`] around to do the mapping of +/// high-level identities to low-level identities. +/// +/// For more information about the differences to [`Hibe`], see the [module-level][self] documentation. +#[derive(Clone, Debug)] +pub struct HybridKem { + hibe: BonehBoyenGoh, + mapper: M, +} + +impl HybridKem { + /// Create a new [`HybridKem`] using the [`HashMapper`] mapper. + /// + /// Parameters: + /// + /// * `max_depth` - Maximum depth that the hierarchy should support. + pub fn new(max_depth: usize) -> HybridKem { + Self::new_with_mapper(max_depth, HashMapper) + } +} + +impl HybridKem { + /// Create a new [`HybridKem`] with the given [`Mapper`]. + /// + /// Parameters: + /// + /// * `max_depth` - Maximum depth that the hierarchy should support. + /// * `mapper` - The mapper to use for identity mapping. + pub fn new_with_mapper(max_depth: usize, mapper: M) -> HybridKem { + let hibe = BonehBoyenGoh::new(max_depth); + Self { hibe, mapper } + } + + /// Wraps an existing [`BonehBoyenGoh`] HIBE. + /// + /// This will result in the [`HybridKem`] supporting the same hierarchy depth that the wrapped + /// HIBE supports. + /// + /// Parameters: + /// + /// * `hibe` - The inner HIBE to wrap. + /// * `mapper` - The mapper to use for identity mapping. + pub fn wrap(hibe: BonehBoyenGoh, mapper: M) -> HybridKem { + Self { hibe, mapper } + } + + /// Sets up the system parameters. + /// + /// This operation will return the master public key and the master secret key. + /// + /// Parameters: + /// + /// * `rng` - The randomness to use. + pub fn setup(&self, rng: R) -> Result<(PublicKey, MasterKey)> { + let (public_key, master_key) = self.hibe.setup(rng)?; + Ok((public_key.into(), master_key.into())) + } + + /// Generates the secret key for a user using the master secret key. + /// + /// Parameters: + /// + /// * `rng` - The randomness to use. + /// * `public_key` - The public key of the system. + /// * `master_key` - The master secret key. + /// * `identity` - The identity for which to generate the key. + pub fn generate_key( + &self, + rng: R, + public_key: &PublicKey, + master_key: &MasterKey, + identity: I, + ) -> Result + where + M: Mapper::Identity>, + { + let identity = self.mapper.map_identity(identity)?; + let private_key = self + .hibe + .generate_key(rng, &public_key.0, &master_key.0, &identity)?; + Ok(private_key.into()) + } + + /// Derives the secret key for an identity given the secret key of its parent. + /// + /// If the given `parent_key` does not actually belong to the parent of `identity`, the + /// resulting key will be wrong. + /// + /// Parameters: + /// + /// * `rng` - The randomness to use. + /// * `public_key` - The public key of the system. + /// * `parent_key` - The key of the parent identity. + /// * `identity` - The identity for which to generate the key. + pub fn derive_key( + &self, + rng: R, + public_key: &PublicKey, + parent_key: &PrivateKey, + identity: I, + ) -> Result + where + M: Mapper::Identity>, + { + let identity = self.mapper.map_identity(identity)?; + let Some((child, parent)) = identity.split_last() else { + return Err(Error::EmptyIdentity); + }; + let private_key = self + .hibe + .derive_key(rng, &public_key.0, &parent_key.0, parent, child)?; + Ok(private_key.into()) + } + + /// Encapsulate a key for the given identity. + /// + /// This returns the key and its encapsulation. + /// + /// Parameters: + /// + /// * `rng` - The randomness to use. + /// * `public_key` - The public key of the system. + /// * `identity` - The identity for which to generate the key. + pub fn encapsulate( + &self, + rng: R, + public_key: &PublicKey, + identity: I, + ) -> Result<([u8; 16], EncapsulatedKey)> + where + M: Mapper::Identity>, + { + let identity = self.mapper.map_identity(identity)?; + let (key, encapsulation) = self.hibe.encapsulate(rng, &public_key.0, &identity)?; + Ok((hash_from_curve(key), encapsulation.into())) + } + + /// Decapsulate the given key. + /// + /// If the correct secret key is given, this will return the same key that the corresponding + /// [`HybridKem::encapsulate`] call also returned. + /// + /// Parameters: + /// + /// * `public_key` - The public key of the system. + /// * `key` - The private key of the receiving identity. + /// * `encapsulation` - The encapsulation of the key. + pub fn decapsulate( + &self, + public_key: &PublicKey, + key: &PrivateKey, + encapsulation: &EncapsulatedKey, + ) -> Result<[u8; 16]> { + let key = self + .hibe + .decapsulate(&public_key.0, &key.0, &encapsulation.0)?; + Ok(hash_from_curve(key)) + } + + /// Encrypt the given byte sequence for the given identity. + /// + /// This internally uses a hybrid encryption where the key is encapsulated by the KEM, and the + /// payload is then encrypted symetrically with AES (counter mode). + /// + /// Note that the resulting ciphertext is longer than the payload, as some space is needed for + /// the encapsulated key. The key is automatically prepended to the encrypted payload. + /// + /// Parameters: + /// + /// * `rng` - The randomness to use. + /// * `public_key` - The public key of the system. + /// * `identity` - The identity for which to encrypt the payload. + /// * `payload` - Payload to encrypt. + pub fn encrypt( + &self, + rng: R, + public_key: &PublicKey, + identity: I, + payload: &[u8], + ) -> Result> + where + M: Mapper::Identity>, + { + let (key, encapsulation) = self.encapsulate(rng, public_key, identity)?; + let mut buffer = Vec::from(payload); + let mut cipher = AesCtr::new(&key.into(), &IV.into()); + cipher.apply_keystream(&mut buffer); + Ok(bincode::serialize(&(encapsulation, buffer)).expect("Serialization failed")) + } + + /// Decrypt the given ciphertext. + /// + /// Returns the payload. + /// + /// Parameters: + /// + /// * `public_key` - The public key of the system. + /// * `key` - The private key of the receiving identity. + /// * `ciphertext` - The ciphertext, as previously returned by [`HybridKem::encrypt`]. + pub fn decrypt( + &self, + public_key: &PublicKey, + key: &PrivateKey, + ciphertext: &[u8], + ) -> Result> { + let (encapsulation, mut buffer): (EncapsulatedKey, Vec) = + bincode::deserialize(ciphertext).map_err(|_| Error::MalformedCiphertext)?; + let key = self.decapsulate(public_key, key, &encapsulation)?; + let mut cipher = AesCtr::new(&key.into(), &IV.into()); + cipher.apply_keystream(&mut buffer); + Ok(buffer) + } +} + +#[derive(Default)] +struct Sha3Hasher(Sha3_256); + +impl Sha3Hasher { + fn hash_to_scalar(self) -> Scalar { + let mut bytes = [0; 48]; + bytes[..32].copy_from_slice(&self.0.finalize()); + Scalar::from_okm(&bytes) + } + + fn hash(element: &H) -> Scalar { + let mut hasher = Sha3Hasher::default(); + element.hash(&mut hasher); + hasher.hash_to_scalar() + } +} + +impl Hasher for Sha3Hasher { + fn finish(&self) -> u64 { + u64::from_be_bytes(self.0.clone().finalize()[..8].try_into().unwrap()) + } + + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes); + } +} + +/// A [`Mapper`] that works for all types implementing [`std::hash::Hash`]. +/// +/// This mapper uses the hash implementation to hash objects to [`Scalar`]s, the underlying +/// identity element for [`HybridKem`]. Internally, a SHA3-256 instance is used to provide +/// consistent hashing and collision resistance. +/// +/// In order to provide a hierarchy, the mapper does not accept single elements, but rather +/// iterators over elements. The more elements the iterator produces, the deeper down the hierarchy +/// we go. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct HashMapper; + +impl HashMapper { + /// Create a new [`HashMapper`]. + pub fn new() -> HashMapper { + HashMapper + } +} + +impl Mapper for HashMapper +where + I: IntoIterator, + F: Hash, +{ + fn map_identity(&self, input: I) -> Result> { + Ok(input + .into_iter() + .map(|element| Sha3Hasher::hash(&element)) + .collect()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_encrypt_decrypt() { + let mut rng = rand::thread_rng(); + let kem = HybridKem::new(5); + let (public_key, master_key) = kem.setup(&mut rng).unwrap(); + + let identity = &[1, 2, 3] as &[_]; + let secret_key = kem + .generate_key(&mut rng, &public_key, &master_key, identity) + .unwrap(); + let message = b"Hello, world!"; + let ciphertext = kem + .encrypt(&mut rng, &public_key, identity, message) + .unwrap(); + let decryption = kem.decrypt(&public_key, &secret_key, &ciphertext).unwrap(); + assert_eq!(message.as_slice(), decryption.as_slice()); + } +} -- cgit v1.2.3