From 31ceedc233236888b29e4d3292485bbf0250b65f Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Sun, 27 Sep 2026 17:27:10 +0200 Subject: [PATCH] change key length --- Cargo.toml | 5 ++ src/kdf/kdf.rs | 14 ++-- src/kdf/test.rs | 2 +- src/keccak_aead.rs | 165 +++++++++++++++++++++++++++------------------ 4 files changed, 113 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cb48d1..2b53735 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,8 @@ subtle = "2.6.1" [dev-dependencies] tempfile = "3.27.0" + +[profile.release] +strip = true +lto = true +panic = "abort" diff --git a/src/kdf/kdf.rs b/src/kdf/kdf.rs index b4ed638..2d694f7 100644 --- a/src/kdf/kdf.rs +++ b/src/kdf/kdf.rs @@ -1,23 +1,21 @@ //! Password-to-key derivation. //! -//! This currently uses SHA3-256 directly and truncates the result to 24 bytes -//! because `KeccakAead` accepts keys up to 24 bytes. +//! This currently uses SHA3-512 directly //! //! This is not a proper password-based KDF. There is currently no salt, //! work factor, memory hardness, or iteration count. -use crate::keccak::sha3_256; +use crate::keccak::sha3_512; /// Length of the key produced for `KeccakAead`. -pub const DERIVED_KEY_LEN: usize = 24; +pub const DERIVED_KEY_LEN: usize = 64; -/// Derive a 24-byte encryption key from a password. +/// Derive a 64-byte encryption key from a password. /// /// The password is encoded as UTF-8 and hashed using the project's -/// SHA3-256 implementation. The first 24 bytes of the digest are used -/// as the AEAD key. +/// SHA3-512 implementation. pub fn derive_file_key(password: &str) -> [u8; DERIVED_KEY_LEN] { - let digest = sha3_256(password.as_bytes()); + let digest = sha3_512(password.as_bytes()); let mut key = [0u8; DERIVED_KEY_LEN]; key.copy_from_slice(&digest[..DERIVED_KEY_LEN]); diff --git a/src/kdf/test.rs b/src/kdf/test.rs index 1e55ce5..6639358 100644 --- a/src/kdf/test.rs +++ b/src/kdf/test.rs @@ -3,7 +3,7 @@ use crate::kdf::kdf::DERIVED_KEY_LEN; #[test] fn key_has_expected_length() { - assert_eq!(DERIVED_KEY_LEN, 24); + assert_eq!(DERIVED_KEY_LEN, 64); let key = derive_file_key("test password"); assert_eq!(key.len(), DERIVED_KEY_LEN); diff --git a/src/keccak_aead.rs b/src/keccak_aead.rs index 2f16a07..199dd9d 100644 --- a/src/keccak_aead.rs +++ b/src/keccak_aead.rs @@ -1,9 +1,11 @@ use crate::keccak::shake256; const RATE_SIZE: usize = 16; -const CAPACITY_SIZE: usize = 24; +const CAPACITY_SIZE: usize = 64; const STATE_SIZE: usize = RATE_SIZE + CAPACITY_SIZE; const CHUNK_SIZE: usize = 16; +const MIN_KEY_SIZE: usize = 16; +const MAX_KEY_SIZE: usize = 64; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptionResult { @@ -23,13 +25,19 @@ pub struct KeccakAead { } impl KeccakAead { + #[inline] + fn assert_valid_key_size(key: &[u8]) { + assert!( + (MIN_KEY_SIZE..=MAX_KEY_SIZE).contains(&key.len()), + "key must be between {} and {} bytes", + MIN_KEY_SIZE, + MAX_KEY_SIZE + ); + } + /// Initialize the AEAD state. pub fn new(iv: &[u8], key: &[u8], nonce: &[u8]) -> Self { - assert!( - key.len() <= CAPACITY_SIZE, - "key must be at most {} bytes", - CAPACITY_SIZE - ); + Self::assert_valid_key_size(key); let mut input = Vec::with_capacity(iv.len() + key.len() + nonce.len()); @@ -42,8 +50,8 @@ impl KeccakAead { let mut state = [0u8; STATE_SIZE]; state.copy_from_slice(&squeezed); - for i in 0..key.len() { - state[RATE_SIZE + i] ^= key[i]; + for (i, &byte) in key.iter().enumerate() { + state[RATE_SIZE + i] ^= byte; } Self { state } @@ -60,8 +68,9 @@ impl KeccakAead { input[i] = byte ^ self.state[i]; } - // c = state[16..40] - input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); + // c = state[16..48] + input[RATE_SIZE..STATE_SIZE] + .copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); self.state = shake256_state(&input); } @@ -70,7 +79,7 @@ impl KeccakAead { } /// Encrypt plaintext. - /// Returns the ciphertext split logically into 16-byte chunks, then concatenated + /// Returns the ciphertext split logically into 16-byte chunks, then concatenated. pub fn plaintext_processing(&mut self, plaintext: &[u8]) -> Vec { let mut ciphertext = Vec::with_capacity(plaintext.len()); @@ -89,7 +98,8 @@ impl KeccakAead { input[..chunk.len()].copy_from_slice(&ciphertext[start..]); - input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); + input[RATE_SIZE..STATE_SIZE] + .copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); self.state = shake256_state(&input); } @@ -112,7 +122,8 @@ impl KeccakAead { // input = ciphertext || c input[..chunk.len()].copy_from_slice(chunk); - input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); + input[RATE_SIZE..STATE_SIZE] + .copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]); self.state = shake256_state(&input); } @@ -123,11 +134,7 @@ impl KeccakAead { /// Finalize and generate the authentication tag. /// The tag has the same length as the key. pub fn finalize(&mut self, key: &[u8]) -> Vec { - assert!( - key.len() <= CAPACITY_SIZE, - "key must be at most {} bytes", - CAPACITY_SIZE - ); + Self::assert_valid_key_size(key); // Construct padded key: let mut padded_key = [0u8; CAPACITY_SIZE]; @@ -137,22 +144,23 @@ impl KeccakAead { padded_key[padding_len..].copy_from_slice(key); // XOR padded key into c. - for (i, &byte) in padded_key.iter().enumerate().take(CAPACITY_SIZE) { - self.state[RATE_SIZE + i] ^= byte; + for (state_byte, &key_byte) in self.state[RATE_SIZE..STATE_SIZE] + .iter_mut() + .zip(padded_key.iter()) + { + *state_byte ^= key_byte; } - // SHAKE256(state, 40) + // SHAKE256(state, 48) let output = shake256(&self.state, STATE_SIZE); + // Take the last `key.len()` bytes from the output. let tag_start = STATE_SIZE - key.len(); - let mut tag = Vec::with_capacity(key.len()); - - for i in 0..key.len() { - tag.push(key[i] ^ output[tag_start + i]); - } - - tag + key.iter() + .zip(&output[tag_start..]) + .map(|(&key_byte, &output_byte)| key_byte ^ output_byte) + .collect() } /// Encrypt: @@ -199,7 +207,7 @@ impl KeccakAead { } } -/// SHAKE256 wrapper returning exactly a 40-byte AEAD state. +/// SHAKE256 wrapper returning exactly a 48-byte AEAD state. #[inline] fn shake256_state(state: &[u8; STATE_SIZE]) -> [u8; STATE_SIZE] { let output = shake256(state, STATE_SIZE); @@ -217,55 +225,62 @@ mod tests { #[test] fn encrypt_decrypt_roundtrip() { - let key = [0x00u8; 16]; - let iv = [0x11u8; 16]; - let nonce = [0x22u8; 16]; + for key_len in [16, 17, 24, 25, 31, 32] { + let key = vec![0x00u8; key_len]; + let iv = [0x11u8; 16]; + let nonce = [0x22u8; 16]; - let associated_data = b"associated data"; + let associated_data = b"associated data"; + let plaintext = b"Hello, Keccak AEAD!"; - let plaintext = b"Hello, Keccak AEAD!"; + let encrypted = + KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce); - let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce); + let decrypted = + KeccakAead::decrypt(&key, &encrypted.cipher, &iv, associated_data, &nonce); - let decrypted = KeccakAead::decrypt(&key, &encrypted.cipher, &iv, associated_data, &nonce); - - assert_eq!(decrypted.plaintext, plaintext); - - assert_eq!(decrypted.tag, encrypted.tag); + assert_eq!(decrypted.plaintext, plaintext); + assert_eq!(decrypted.tag, encrypted.tag); + assert_eq!(encrypted.tag.len(), key_len); + } } #[test] fn handles_empty_plaintext() { - let key = [0x42u8; 16]; - let iv = [0x11u8; 16]; - let nonce = [0x22u8; 16]; + for key_len in [16, 24, 32] { + let key = vec![0x42u8; key_len]; + let iv = [0x11u8; 16]; + let nonce = [0x22u8; 16]; - let result = KeccakAead::encrypt(&key, &[], &iv, b"test", &nonce); + let result = KeccakAead::encrypt(&key, &[], &iv, b"test", &nonce); - assert_eq!(result.cipher.len(), 0); - assert_eq!(result.tag.len(), key.len()); + assert_eq!(result.cipher.len(), 0); + assert_eq!(result.tag.len(), key_len); + } } #[test] fn handles_empty_associated_data() { - let key = [0x42u8; 16]; - let iv = [0x11u8; 16]; - let nonce = [0x22u8; 16]; + for key_len in [16, 24, 32] { + let key = vec![0x42u8; key_len]; + let iv = [0x11u8; 16]; + let nonce = [0x22u8; 16]; - let plaintext = b"test plaintext"; + let plaintext = b"test plaintext"; - let result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce); + let result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce); - let decrypted = KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce); + let decrypted = + KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce); - assert_eq!(decrypted.plaintext, plaintext); - - assert_eq!(decrypted.tag, result.tag); + assert_eq!(decrypted.plaintext, plaintext); + assert_eq!(decrypted.tag, result.tag); + } } #[test] fn handles_multiple_chunks() { - let key = [0x42u8; 16]; + let key = [0x42u8; 32]; let iv = [0x11u8; 16]; let nonce = [0x22u8; 16]; @@ -274,39 +289,61 @@ mod tests { let associated_data = b"abcdefghijklmnopqrstuvwxyz"; - let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce); + let encrypted = + KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce); assert_eq!(encrypted.cipher.len(), plaintext.len()); + assert_eq!(encrypted.tag.len(), key.len()); let decrypted = KeccakAead::decrypt( &key, &encrypted.cipher, - iv.as_slice(), + &iv, associated_data, - nonce.as_slice(), + &nonce, ); assert_eq!(decrypted.plaintext, plaintext); - assert_eq!(decrypted.tag, encrypted.tag); } #[test] fn tag_changes_when_ciphertext_changes() { - let key = [0x42u8; 16]; + let key = [0x42u8; 32]; let iv = [0x11u8; 16]; let nonce = [0x22u8; 16]; let plaintext = b"test plaintext"; - let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, b"ad", &nonce); + let encrypted = + KeccakAead::encrypt(&key, plaintext, &iv, b"ad", &nonce); let mut modified = encrypted.cipher.clone(); - modified[0] ^= 1; - let decrypted = KeccakAead::decrypt(&key, &modified, &iv, b"ad", &nonce); + let decrypted = + KeccakAead::decrypt(&key, &modified, &iv, b"ad", &nonce); assert_ne!(decrypted.tag, encrypted.tag); } + + #[test] + #[should_panic(expected = "key must be between 16 and 64 bytes")] + fn rejects_short_key() { + let key = [0u8; 15]; + let iv = [0u8; 16]; + let nonce = [0u8; 16]; + + KeccakAead::encrypt(&key, b"test", &iv, b"", &nonce); + } + + #[test] + #[should_panic(expected = "key must be between 16 and 64 bytes")] + fn rejects_long_key() { + let key = [0u8; 65]; + let iv = [0u8; 16]; + let nonce = [0u8; 16]; + + KeccakAead::encrypt(&key, b"test", &iv, b"", &nonce); + } }