From 5a2555a327d7cfd7c6e16908658fccbcc0b9888d Mon Sep 17 00:00:00 2001 From: David Niehues <7667041+DavidNiehues@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:31:02 +0100 Subject: [PATCH] dev(ciphers): add implementation of shake256 --- Cargo.lock | 1 + ciphers/Cargo.toml | 1 + .../subtle/hash_functions/keyed_shake256.rs | 65 +++++++++++++++++++ ciphers/src/subtle/hash_functions/mod.rs | 1 + ciphers/src/subtle/mod.rs | 3 + 5 files changed, 71 insertions(+) create mode 100644 ciphers/src/subtle/hash_functions/keyed_shake256.rs create mode 100644 ciphers/src/subtle/hash_functions/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1e1c013..4e6ec91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1838,6 +1838,7 @@ dependencies = [ "blake2", "chacha20poly1305", "libcrux", + "rosenpass-cipher-traits", "rosenpass-constant-time", "rosenpass-oqs", "rosenpass-secret-memory", diff --git a/ciphers/Cargo.toml b/ciphers/Cargo.toml index df6ca34..d794f37 100644 --- a/ciphers/Cargo.toml +++ b/ciphers/Cargo.toml @@ -19,6 +19,7 @@ rosenpass-constant-time = { workspace = true } rosenpass-secret-memory = { workspace = true } rosenpass-oqs = { workspace = true } rosenpass-util = { workspace = true } +rosenpass-cipher-traits = { workspace = true } static_assertions = { workspace = true } zeroize = { workspace = true } chacha20poly1305 = { workspace = true } diff --git a/ciphers/src/subtle/hash_functions/keyed_shake256.rs b/ciphers/src/subtle/hash_functions/keyed_shake256.rs new file mode 100644 index 0000000..255d38b --- /dev/null +++ b/ciphers/src/subtle/hash_functions/keyed_shake256.rs @@ -0,0 +1,65 @@ +use anyhow::ensure; +use sha3::digest::{ExtendableOutput, Update, XofReader}; +use sha3::Shake256; +use rosenpass_cipher_traits::KeyedHash; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SHAKE256Core; + +impl KeyedHash for SHAKE256Core { + type Error = anyhow::Error; + + /// TODO: Rework test + /// Provides a keyed hash function based on SHAKE256. To work for the protocol, the output length + /// and key length are fixed to 32 bytes (also see [KEY_LEN] and [HASH_LEN]). + /// + /// Note that the SHAKE256 is designed for 64 bytes output length, which we truncate to 32 bytes + /// to work well with the overall protocol. Referring to Table 4 of FIPS 202, this offers the + /// same collision resistance as SHAKE128, but 256 bits of preimage resistance. We therefore + /// prefer a truncated SHAKE256 over SHAKE128. + /// + /// TODO: Example/Test + /// #Examples + /// ```rust + /// # use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core; + /// use rosenpass_cipher_traits::KeyedHash; + /// const KEY_LEN: usize = 32; + /// const HASH_LEN: usize = 32; + /// let key: [u8; 32] = [0; KEY_LEN]; + /// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer + /// // buffer for the hash output + /// let mut hash_data: [u8; 32] = [0u8; HASH_LEN]; + /// + /// assert!(SHAKE256Core::<32, 32>::keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result"); + /// # let expected_hash: &[u8] = &[44, 102, 248, 251, 141, 145, 55, 194, 165, 228, 156, 42, 220, + /// 108, 50, 224, 48, 19, 197, 253, 105, 136, 95, 34, 95, 203, 149, 192, 124, 223, 243, 87]; + /// # assert_eq!(hash_data, expected_hash); + /// ``` + fn keyed_hash(key: &[u8; KEY_LEN], data: &[u8], out: &mut [u8; HASH_LEN]) -> Result<(), Self::Error> { + // Since SHAKE256 is a XOF, we fix the output length manually to what is required for the + // protocol. + ensure!(out.len() == HASH_LEN); + // Not bothering with padding; the implementation + // uses appropriately sized keys. + ensure!(key.len() == KEY_LEN); + let mut shake256 = Shake256::default(); + shake256.update(key); + shake256.update(data); + + // Following the NIST recommendations in Section A.2 of the FIPS 202 standard, + // (pages 24/25, i.e., 32/33 in the PDF) we append the length of the input to the end of + // the input. This prevents that if the same input is used with two different output lengths, + // the shorter output is a prefix of the longer output. See the Section A.2 of the FIPS 202 + // standard for more details. + // TODO: Explain why we do not include the length here + // shake256.update(&((HASH_LEN as u8).to_le_bytes())); + shake256.finalize_xof().read(out); + Ok(()) + } +} + +impl SHAKE256Core { + pub fn new() -> Self { + Self + } +} diff --git a/ciphers/src/subtle/hash_functions/mod.rs b/ciphers/src/subtle/hash_functions/mod.rs new file mode 100644 index 0000000..328de65 --- /dev/null +++ b/ciphers/src/subtle/hash_functions/mod.rs @@ -0,0 +1 @@ +pub mod keyed_shake256; \ No newline at end of file diff --git a/ciphers/src/subtle/mod.rs b/ciphers/src/subtle/mod.rs index 7e9431b..93bd094 100644 --- a/ciphers/src/subtle/mod.rs +++ b/ciphers/src/subtle/mod.rs @@ -11,3 +11,6 @@ pub mod chacha20poly1305_ietf; pub mod chacha20poly1305_ietf_libcrux; pub mod incorrect_hmac_blake2b; pub mod xchacha20poly1305_ietf; +pub mod hash_functions; + +pub use hash_functions::{keyed_shake256}; \ No newline at end of file