diff --git a/src/keccak.rs b/src/keccak.rs deleted file mode 100644 index 9484f9d..0000000 --- a/src/keccak.rs +++ /dev/null @@ -1,445 +0,0 @@ -// FIPS 202 / Keccak-f[1600] -// Lane indexing: -// A[x, y] = state[x + 5*y] -// Lanes are little-endian when converted to/from bytes. - -const RHO: [[u32; 5]; 5] = [ - [0, 36, 3, 41, 18], - [1, 44, 10, 45, 2], - [62, 6, 43, 15, 61], - [28, 55, 25, 21, 56], - [27, 20, 39, 8, 14], -]; - -const ROUND_CONSTANTS: [u64; 24] = [ - 0x0000_0000_0000_0001, - 0x0000_0000_0000_8082, - 0x8000_0000_0000_808a, - 0x8000_0000_8000_8000, - 0x0000_0000_0000_808b, - 0x0000_0000_8000_0001, - 0x8000_0000_8000_8081, - 0x8000_0000_0000_8009, - 0x0000_0000_0000_008a, - 0x0000_0000_0000_0088, - 0x0000_0000_8000_8009, - 0x0000_0000_8000_000a, - 0x0000_0000_8000_808b, - 0x8000_0000_0000_008b, - 0x8000_0000_0000_8089, - 0x8000_0000_0000_8003, - 0x8000_0000_0000_8002, - 0x8000_0000_0000_0080, - 0x0000_0000_0000_800a, - 0x8000_0000_8000_000a, - 0x8000_0000_8000_8081, - 0x8000_0000_0000_8080, - 0x0000_0000_8000_0001, - 0x8000_0000_8000_8008, -]; - -/// Keccak-f[1600] permutation. -/// -/// State layout: -/// state[x + 5*y] == A[x,y] -#[inline] -pub fn keccak_f1600(state: &mut [u64; 25]) { - let mut c = [0u64; 5]; - let mut d = [0u64; 5]; - let mut b = [0u64; 25]; - - for &rc in &ROUND_CONSTANTS { - // 0 step - for x in 0..5 { - c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]; - } - - for x in 0..5 { - d[x] = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1); - } - - for x in 0..5 { - for y in 0..5 { - state[x + 5 * y] ^= d[x]; - } - } - - // rho + pi step - // B[y, 2x + 3y] = ROT(A[x,y], r[x,y]) - for x in 0..5 { - for y in 0..5 { - let new_x = y; - let new_y = (2 * x + 3 * y) % 5; - - b[new_x + 5 * new_y] = state[x + 5 * y].rotate_left(RHO[x][y]); - } - } - - // Khi - for y in 0..5 { - for x in 0..5 { - let current = b[x + 5 * y]; - let next = b[(x + 1) % 5 + 5 * y]; - let next2 = b[(x + 2) % 5 + 5 * y]; - - state[x + 5 * y] = current ^ ((!next) & next2); - } - } - - // iota - state[0] ^= rc; - } -} - -/// XOR one byte into the sponge state. -/// -/// Keccak lanes are little-endian: -/// byte 0 -> bits 0..7 of lane 0 -/// byte 1 -> bits 8..15 of lane 0 -/// ... -#[inline] -fn xor_byte(state: &mut [u64; 25], offset: usize, value: u8) { - let lane = offset / 8; - let shift = (offset % 8) * 8; - - state[lane] ^= (value as u64) << shift; -} - -/// Read one byte from the sponge state. -#[inline] -fn get_byte(state: &[u64; 25], offset: usize) -> u8 { - let lane = offset / 8; - let shift = (offset % 8) * 8; - - ((state[lane] >> shift) & 0xff) as u8 -} - -/// Generic Keccak sponge. -/// -/// `rate` and `capacity` are specified in bits. -/// `suffix` is the domain-separation / delimited-suffix byte. -/// -/// Examples: -/// SHA-3: suffix = 0x06 -/// SHAKE: suffix = 0x1f -pub fn keccak( - rate: usize, - capacity: usize, - input: &[u8], - suffix: u8, - output_len: usize, -) -> Vec { - assert_eq!(rate + capacity, 1600, "rate + capacity must equal 1600"); - - assert_eq!(rate % 8, 0, "rate must be a multiple of 8 bits"); - - let rate_bytes = rate / 8; - - let mut state = [0u64; 25]; - let mut input_offset = 0; - - // Absorb - // Full blocks - while input_offset + rate_bytes <= input.len() { - for i in 0..rate_bytes { - xor_byte(&mut state, i, input[input_offset + i]); - } - - keccak_f1600(&mut state); - - input_offset += rate_bytes; - } - - // Remaining partial block - let block_size = input.len() - input_offset; - - for i in 0..block_size { - xor_byte(&mut state, i, input[input_offset + i]); - } - - // Padding - // delimitedSuffix followed by the final 0x80 bit. - - xor_byte(&mut state, block_size, suffix); - - if (suffix & 0x80) != 0 && block_size == rate_bytes - 1 { - keccak_f1600(&mut state); - } - - xor_byte(&mut state, rate_bytes - 1, 0x80); - - keccak_f1600(&mut state); - - // Squeeze - let mut output = Vec::with_capacity(output_len); - - while output.len() < output_len { - let block_size = (output_len - output.len()).min(rate_bytes); - - for i in 0..block_size { - output.push(get_byte(&state, i)); - } - - if output.len() < output_len { - keccak_f1600(&mut state); - } - } - - output -} - -// SHA-3 - -pub fn sha3_224(input: &[u8]) -> [u8; 28] { - keccak(1152, 448, input, 0x06, 28).try_into().unwrap() -} - -pub fn sha3_256(input: &[u8]) -> [u8; 32] { - keccak(1088, 512, input, 0x06, 32).try_into().unwrap() -} - -pub fn sha3_384(input: &[u8]) -> [u8; 48] { - keccak(832, 768, input, 0x06, 48).try_into().unwrap() -} - -pub fn sha3_512(input: &[u8]) -> [u8; 64] { - keccak(576, 1024, input, 0x06, 64).try_into().unwrap() -} - -// SHAKE - -pub fn shake128(input: &[u8], output_len: usize) -> Vec { - keccak(1344, 256, input, 0x1f, output_len) -} - -pub fn shake256(input: &[u8], output_len: usize) -> Vec { - keccak(1088, 512, input, 0x1f, output_len) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn hex_decode(s: &str) -> Vec { - assert!( - s.len() % 2 == 0, - "hex string must contain an even number of characters" - ); - - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) - .collect() - } - - fn assert_hex(actual: &[u8], expected: &str) { - let expected = hex_decode(expected); - - assert_eq!( - actual, - expected.as_slice(), - "\nexpected: {}\nactual: {}", - expected - .iter() - .map(|b| format!("{b:02x}")) - .collect::(), - actual - .iter() - .map(|b| format!("{b:02x}")) - .collect::(), - ); - } - - // SHA3-224 - #[test] - fn sha3_224_nist() { - // NIST CAVP SHA3_224ShortMsg.rsp - let vectors = [ - ( - "", - "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", - ), - ( - "6b2b92584146a433bee8b947cc1f35b617b73f5b1e0376ac8bdadfe5bfdf2263b205f74dfa53db7a29e5078f5c34a268119736ba390961f6", - "132cfa7e71fe0991abbd88ef588ac95ac9289b1d775b42033567dd33", - ), - ( - "d4f757d1c33b9c0b38b4e93e8e2483ec51b4861299f1d650961457496d86614d42a36e3696bf168fd4663efc26e88cd58d151e1531467b73f69dc9ce4f8d41ce579ce1c91e6760e340e7677abdf4fec1040745aa5144640a39b8c4f884df80753a691653003d634fa5bfce81f94ec3f6", - "be11259377f09821d9dc358592b6565d8ef2b414dfaa7db5609fb751", - ), - ]; - - for (message, expected) in vectors { - let input = hex_decode(message); - assert_hex(&sha3_224(&input), expected); - } - } - - // SHA3-256 - #[test] - fn sha3_256_nist() { - // NIST CAVP SHA3_256ShortMsg.rsp - let vectors = [ - ( - "", - "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", - ), - ( - "00ff6c96b7aa3cf27d036cf20af7031434113252574bda9cf9244d85aef2593d3a7a83bff6be904b75164a1766828042bc3f4f090d98a03d", - "d000eafca34815783bed9b050c6901c97f2e77d4771a0ed724dd8f6ff1448791", - ), - ( - "8d93627c0b7cbf61a7fe70e78c2c8ed23b1344b4cfed31bd85980dd37b4690e5b8758f7d6d2269957a39a1ac3451cc196696ae9e9606a04089e13456095a1ce1e593481b3ac84f53f1cb10f789b099f316c948398ad52fa13474bdf486de9b431bd5d57ef9d83a42139a05f112b2bd08", - "344ec86642eabb206b2fd930e4c5dde78aa878577d6c271cb0069d4999495652", - ), - ]; - - for (message, expected) in vectors { - let input = hex_decode(message); - assert_hex(&sha3_256(&input), expected); - } - } - - // SHA3-384 - #[test] - fn sha3_384_nist() { - // NIST CAVP SHA3_384ShortMsg.rsp - let vectors = [ - ( - "", - "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", - ), - ( - "5415c2596aa7d21e855be98491bd702357c19f21f46294f98a8aa37b3532ee1541ca35509adbef9d83eb99528ba14ef0bd2998a718da861c3f16fe6971", - "8f9fd7d879d6b51ee843e1fbcd40bb67449ae744db9f673e3452f028cb0189d9cb0fef7bdb5c760d63fea0e3ba3dd8d1", - ), - ( - "00ce225eaea24843406fa42cc8450e66f76ac9f549b8591f7d40942f4833fc734a034c8741c551d57ddafb5d94ceb4b25680f045038306e6bcc53e88386e2b45b80b3ba23dec8c13f8ca01c202ae968c4d0df04cdb38395d2df42a5aff646928", - "81d6e0d96575a9b8ca083ee9ec2ead57ddf72b97d7709086a2f4a749d3f61d16423463487562c7f09aba1b26e8cae47b", - ), - ]; - - for (message, expected) in vectors { - let input = hex_decode(message); - assert_hex(&sha3_384(&input), expected); - } - } - - // SHA3-512 - #[test] - fn sha3_512_nist() { - // NIST CAVP SHA3_512ShortMsg.rsp - let vectors = [ - ( - "", - "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", - ), - ( - "302fa84fdaa82081b1192b847b81ddea10a9f05a0f04138fd1da84a39ba5e18e18bc3cea062e6df92ff1ace89b3c5f55043130108abf631e", - "8c8eaae9a445643a37df34cfa6a7f09deccab2a222c421d2fc574bbc5641e504354391e81eb5130280b1226812556d474e951bb78dbdd9b77d19f647e2e7d7be", - ), - ( - "0ce9f8c3a990c268f34efd9befdb0f7c4ef8466cfdb01171f8de70dc5fefa92acbe93d29e2ac1a5c2979129f1ab08c0e77de7924ddf68a209cdfa0adc62f85c18637d9c6b33f4ff8", - "b018a20fcf831dde290e4fb18c56342efe138472cbe142da6b77eea4fce52588c04c808eb32912faa345245a850346faec46c3a16d39bd2e1ddb1816bc57d2da", - ), - ]; - - for (message, expected) in vectors { - let input = hex_decode(message); - assert_hex(&sha3_512(&input), expected); - } - } - - // SHA-3 standard "abc" vectors - #[test] - fn sha3_abc() { - assert_hex( - &sha3_224(b"abc"), - "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", - ); - - assert_hex( - &sha3_256(b"abc"), - "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", - ); - - assert_hex( - &sha3_384(b"abc"), - "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", - ); - - assert_hex( - &sha3_512(b"abc"), - "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", - ); - } - - // SHAKE128 - #[test] - fn shake128_nist() { - // FIPS 202 / standard known-answer vectors. - assert_hex( - &shake128(b"", 32), - "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26", - ); - - assert_hex( - &shake128(b"abc", 32), - "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8", - ); - } - - // SHAKE256 - #[test] - fn shake256_nist() { - assert_hex( - &shake256(b"", 64), - "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f\ -d75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be", - ); - - assert_hex( - &shake256(b"abc", 64), - "483366601360a8771c6863080cc4114d\ -8db44530f8f1e1ee4f94ea37e78b5739\ -d5a15bef186a5386c75744c0527e1faa\ -9f8726e462a12a4feb06bd8801e751e4", - ); - } - - #[test] - fn shake_prefix_property() { - let short = shake128(b"abc", 16); - let long = shake128(b"abc", 64); - - assert_eq!(&long[..16], &short[..]); - - let short = shake256(b"abc", 32); - let long = shake256(b"abc", 64); - - assert_eq!(&long[..32], &short[..]); - } - - #[test] - fn shake_multiple_squeeze_blocks() { - let output128 = shake128(b"abc", 200); - let output256 = shake256(b"abc", 200); - - assert_eq!(output128.len(), 200); - assert_eq!(output256.len(), 200); - - // Verify the first bytes against the standard vectors. - assert_hex( - &output128[..32], - "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8", - ); - - assert_hex( - &output256[..64], - "483366601360a8771c6863080cc4114d\ -8db44530f8f1e1ee4f94ea37e78b5739\ -d5a15bef186a5386c75744c0527e1faa\ -9f8726e462a12a4feb06bd8801e751e4", - ); - } -} diff --git a/src/keccak/keccak.rs b/src/keccak/keccak.rs new file mode 100644 index 0000000..c2cd112 --- /dev/null +++ b/src/keccak/keccak.rs @@ -0,0 +1,217 @@ +// FIPS 202 / Keccak-f[1600] +// Lane indexing: +// A[x, y] = state[x + 5*y] +// Lanes are little-endian when converted to/from bytes. + +const RHO: [[u32; 5]; 5] = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], +]; + +const ROUND_CONSTANTS: [u64; 24] = [ + 0x0000_0000_0000_0001, + 0x0000_0000_0000_8082, + 0x8000_0000_0000_808a, + 0x8000_0000_8000_8000, + 0x0000_0000_0000_808b, + 0x0000_0000_8000_0001, + 0x8000_0000_8000_8081, + 0x8000_0000_0000_8009, + 0x0000_0000_0000_008a, + 0x0000_0000_0000_0088, + 0x0000_0000_8000_8009, + 0x0000_0000_8000_000a, + 0x0000_0000_8000_808b, + 0x8000_0000_0000_008b, + 0x8000_0000_0000_8089, + 0x8000_0000_0000_8003, + 0x8000_0000_0000_8002, + 0x8000_0000_0000_0080, + 0x0000_0000_0000_800a, + 0x8000_0000_8000_000a, + 0x8000_0000_8000_8081, + 0x8000_0000_0000_8080, + 0x0000_0000_8000_0001, + 0x8000_0000_8000_8008, +]; + +/// Keccak-f[1600] permutation. +/// +/// State layout: +/// state[x + 5*y] == A[x,y] +#[inline] +pub fn keccak_f1600(state: &mut [u64; 25]) { + let mut c = [0u64; 5]; + let mut d = [0u64; 5]; + let mut b = [0u64; 25]; + + for &rc in &ROUND_CONSTANTS { + // 0 step + for x in 0..5 { + c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]; + } + + for x in 0..5 { + d[x] = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1); + } + + for x in 0..5 { + for y in 0..5 { + state[x + 5 * y] ^= d[x]; + } + } + + // rho + pi step + // B[y, 2x + 3y] = ROT(A[x,y], r[x,y]) + for x in 0..5 { + for y in 0..5 { + let new_x = y; + let new_y = (2 * x + 3 * y) % 5; + + b[new_x + 5 * new_y] = state[x + 5 * y].rotate_left(RHO[x][y]); + } + } + + // Khi + for y in 0..5 { + for x in 0..5 { + let current = b[x + 5 * y]; + let next = b[(x + 1) % 5 + 5 * y]; + let next2 = b[(x + 2) % 5 + 5 * y]; + + state[x + 5 * y] = current ^ ((!next) & next2); + } + } + + // iota + state[0] ^= rc; + } +} + +/// XOR one byte into the sponge state. +/// +/// Keccak lanes are little-endian: +/// byte 0 -> bits 0..7 of lane 0 +/// byte 1 -> bits 8..15 of lane 0 +/// ... +#[inline] +fn xor_byte(state: &mut [u64; 25], offset: usize, value: u8) { + let lane = offset / 8; + let shift = (offset % 8) * 8; + + state[lane] ^= (value as u64) << shift; +} + +/// Read one byte from the sponge state. +#[inline] +fn get_byte(state: &[u64; 25], offset: usize) -> u8 { + let lane = offset / 8; + let shift = (offset % 8) * 8; + + ((state[lane] >> shift) & 0xff) as u8 +} + +/// Generic Keccak sponge. +/// +/// `rate` and `capacity` are specified in bits. +/// `suffix` is the domain-separation / delimited-suffix byte. +/// +/// Examples: +/// SHA-3: suffix = 0x06 +/// SHAKE: suffix = 0x1f +pub fn keccak( + rate: usize, + capacity: usize, + input: &[u8], + suffix: u8, + output_len: usize, +) -> Vec { + assert_eq!(rate + capacity, 1600, "rate + capacity must equal 1600"); + + assert_eq!(rate % 8, 0, "rate must be a multiple of 8 bits"); + + let rate_bytes = rate / 8; + + let mut state = [0u64; 25]; + let mut input_offset = 0; + + // Absorb + // Full blocks + while input_offset + rate_bytes <= input.len() { + for i in 0..rate_bytes { + xor_byte(&mut state, i, input[input_offset + i]); + } + + keccak_f1600(&mut state); + + input_offset += rate_bytes; + } + + // Remaining partial block + let block_size = input.len() - input_offset; + + for i in 0..block_size { + xor_byte(&mut state, i, input[input_offset + i]); + } + + // Padding + // delimitedSuffix followed by the final 0x80 bit. + + xor_byte(&mut state, block_size, suffix); + + if (suffix & 0x80) != 0 && block_size == rate_bytes - 1 { + keccak_f1600(&mut state); + } + + xor_byte(&mut state, rate_bytes - 1, 0x80); + + keccak_f1600(&mut state); + + // Squeeze + let mut output = Vec::with_capacity(output_len); + + while output.len() < output_len { + let block_size = (output_len - output.len()).min(rate_bytes); + + for i in 0..block_size { + output.push(get_byte(&state, i)); + } + + if output.len() < output_len { + keccak_f1600(&mut state); + } + } + + output +} + +// SHA-3 + +pub fn sha3_224(input: &[u8]) -> [u8; 28] { + keccak(1152, 448, input, 0x06, 28).try_into().unwrap() +} + +pub fn sha3_256(input: &[u8]) -> [u8; 32] { + keccak(1088, 512, input, 0x06, 32).try_into().unwrap() +} + +pub fn sha3_384(input: &[u8]) -> [u8; 48] { + keccak(832, 768, input, 0x06, 48).try_into().unwrap() +} + +pub fn sha3_512(input: &[u8]) -> [u8; 64] { + keccak(576, 1024, input, 0x06, 64).try_into().unwrap() +} + +// SHAKE + +pub fn shake128(input: &[u8], output_len: usize) -> Vec { + keccak(1344, 256, input, 0x1f, output_len) +} + +pub fn shake256(input: &[u8], output_len: usize) -> Vec { + keccak(1088, 512, input, 0x1f, output_len) +} diff --git a/src/keccak/mod.rs b/src/keccak/mod.rs new file mode 100644 index 0000000..4bebdd9 --- /dev/null +++ b/src/keccak/mod.rs @@ -0,0 +1,10 @@ +//! Keccak-f[1600], SHA-3, and SHAKE implementations. + +mod keccak; + +#[cfg(test)] +mod test; + +pub use keccak::{ + keccak, keccak_f1600, sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256, +}; diff --git a/src/keccak/test.rs b/src/keccak/test.rs new file mode 100644 index 0000000..0fcdd64 --- /dev/null +++ b/src/keccak/test.rs @@ -0,0 +1,224 @@ +use super::*; + +fn hex_decode(s: &str) -> Vec { + assert!( + s.len() % 2 == 0, + "hex string must contain an even number of characters" + ); + + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} + +fn assert_hex(actual: &[u8], expected: &str) { + let expected = hex_decode(expected); + + assert_eq!( + actual, + expected.as_slice(), + "\nexpected: {}\nactual: {}", + expected + .iter() + .map(|b| format!("{b:02x}")) + .collect::(), + actual + .iter() + .map(|b| format!("{b:02x}")) + .collect::(), + ); +} + +// SHA3-224 +#[test] +fn sha3_224_nist() { + // NIST CAVP SHA3_224ShortMsg.rsp + let vectors = [ + ( + "", + "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", + ), + ( + "6b2b92584146a433bee8b947cc1f35b617b73f5b1e0376ac8bdadfe5bfdf2263b205f74dfa53db7a29e5078f5c34a268119736ba390961f6", + "132cfa7e71fe0991abbd88ef588ac95ac9289b1d775b42033567dd33", + ), + ( + "d4f757d1c33b9c0b38b4e93e8e2483ec51b4861299f1d650961457496d86614d42a36e3696bf168fd4663efc26e88cd58d151e1531467b73f69dc9ce4f8d41ce579ce1c91e6760e340e7677abdf4fec1040745aa5144640a39b8c4f884df80753a691653003d634fa5bfce81f94ec3f6", + "be11259377f09821d9dc358592b6565d8ef2b414dfaa7db5609fb751", + ), + ]; + + for (message, expected) in vectors { + let input = hex_decode(message); + assert_hex(&sha3_224(&input), expected); + } +} + +// SHA3-256 +#[test] +fn sha3_256_nist() { + // NIST CAVP SHA3_256ShortMsg.rsp + let vectors = [ + ( + "", + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + ), + ( + "00ff6c96b7aa3cf27d036cf20af7031434113252574bda9cf9244d85aef2593d3a7a83bff6be904b75164a1766828042bc3f4f090d98a03d", + "d000eafca34815783bed9b050c6901c97f2e77d4771a0ed724dd8f6ff1448791", + ), + ( + "8d93627c0b7cbf61a7fe70e78c2c8ed23b1344b4cfed31bd85980dd37b4690e5b8758f7d6d2269957a39a1ac3451cc196696ae9e9606a04089e13456095a1ce1e593481b3ac84f53f1cb10f789b099f316c948398ad52fa13474bdf486de9b431bd5d57ef9d83a42139a05f112b2bd08", + "344ec86642eabb206b2fd930e4c5dde78aa878577d6c271cb0069d4999495652", + ), + ]; + + for (message, expected) in vectors { + let input = hex_decode(message); + assert_hex(&sha3_256(&input), expected); + } +} + +// SHA3-384 +#[test] +fn sha3_384_nist() { + // NIST CAVP SHA3_384ShortMsg.rsp + let vectors = [ + ( + "", + "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", + ), + ( + "5415c2596aa7d21e855be98491bd702357c19f21f46294f98a8aa37b3532ee1541ca35509adbef9d83eb99528ba14ef0bd2998a718da861c3f16fe6971", + "8f9fd7d879d6b51ee843e1fbcd40bb67449ae744db9f673e3452f028cb0189d9cb0fef7bdb5c760d63fea0e3ba3dd8d1", + ), + ( + "00ce225eaea24843406fa42cc8450e66f76ac9f549b8591f7d40942f4833fc734a034c8741c551d57ddafb5d94ceb4b25680f045038306e6bcc53e88386e2b45b80b3ba23dec8c13f8ca01c202ae968c4d0df04cdb38395d2df42a5aff646928", + "81d6e0d96575a9b8ca083ee9ec2ead57ddf72b97d7709086a2f4a749d3f61d16423463487562c7f09aba1b26e8cae47b", + ), + ]; + + for (message, expected) in vectors { + let input = hex_decode(message); + assert_hex(&sha3_384(&input), expected); + } +} + +// SHA3-512 +#[test] +fn sha3_512_nist() { + // NIST CAVP SHA3_512ShortMsg.rsp + let vectors = [ + ( + "", + "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + ), + ( + "302fa84fdaa82081b1192b847b81ddea10a9f05a0f04138fd1da84a39ba5e18e18bc3cea062e6df92ff1ace89b3c5f55043130108abf631e", + "8c8eaae9a445643a37df34cfa6a7f09deccab2a222c421d2fc574bbc5641e504354391e81eb5130280b1226812556d474e951bb78dbdd9b77d19f647e2e7d7be", + ), + ( + "0ce9f8c3a990c268f34efd9befdb0f7c4ef8466cfdb01171f8de70dc5fefa92acbe93d29e2ac1a5c2979129f1ab08c0e77de7924ddf68a209cdfa0adc62f85c18637d9c6b33f4ff8", + "b018a20fcf831dde290e4fb18c56342efe138472cbe142da6b77eea4fce52588c04c808eb32912faa345245a850346faec46c3a16d39bd2e1ddb1816bc57d2da", + ), + ]; + + for (message, expected) in vectors { + let input = hex_decode(message); + assert_hex(&sha3_512(&input), expected); + } +} + +// SHA-3 standard "abc" vectors +#[test] +fn sha3_abc() { + assert_hex( + &sha3_224(b"abc"), + "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", + ); + + assert_hex( + &sha3_256(b"abc"), + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + ); + + assert_hex( + &sha3_384(b"abc"), + "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", + ); + + assert_hex( + &sha3_512(b"abc"), + "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + ); +} + +// SHAKE128 +#[test] +fn shake128_nist() { + // FIPS 202 / standard known-answer vectors. + assert_hex( + &shake128(b"", 32), + "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26", + ); + + assert_hex( + &shake128(b"abc", 32), + "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8", + ); +} + +// SHAKE256 +#[test] +fn shake256_nist() { + assert_hex( + &shake256(b"", 64), + "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f\ +d75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be", + ); + + assert_hex( + &shake256(b"abc", 64), + "483366601360a8771c6863080cc4114d\ +8db44530f8f1e1ee4f94ea37e78b5739\ +d5a15bef186a5386c75744c0527e1faa\ +9f8726e462a12a4feb06bd8801e751e4", + ); +} + +#[test] +fn shake_prefix_property() { + let short = shake128(b"abc", 16); + let long = shake128(b"abc", 64); + + assert_eq!(&long[..16], &short[..]); + + let short = shake256(b"abc", 32); + let long = shake256(b"abc", 64); + + assert_eq!(&long[..32], &short[..]); +} + +#[test] +fn shake_multiple_squeeze_blocks() { + let output128 = shake128(b"abc", 200); + let output256 = shake256(b"abc", 200); + + assert_eq!(output128.len(), 200); + assert_eq!(output256.len(), 200); + + // Verify the first bytes against the standard vectors. + assert_hex( + &output128[..32], + "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8", + ); + + assert_hex( + &output256[..64], + "483366601360a8771c6863080cc4114d\ +8db44530f8f1e1ee4f94ea37e78b5739\ +d5a15bef186a5386c75744c0527e1faa\ +9f8726e462a12a4feb06bd8801e751e4", + ); +} diff --git a/src/keccak_aead.rs b/src/keccak_aead/keccak_aead.rs similarity index 59% rename from src/keccak_aead.rs rename to src/keccak_aead/keccak_aead.rs index 199dd9d..e65933c 100644 --- a/src/keccak_aead.rs +++ b/src/keccak_aead/keccak_aead.rs @@ -69,8 +69,7 @@ impl KeccakAead { } // c = state[16..48] - 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); } @@ -98,8 +97,7 @@ 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); } @@ -122,8 +120,7 @@ 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); } @@ -218,132 +215,3 @@ fn shake256_state(state: &[u8; STATE_SIZE]) -> [u8; STATE_SIZE] { result } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encrypt_decrypt_roundtrip() { - 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 plaintext = b"Hello, Keccak AEAD!"; - - let encrypted = - KeccakAead::encrypt(&key, plaintext, &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!(encrypted.tag.len(), key_len); - } - } - - #[test] - fn handles_empty_plaintext() { - 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); - - assert_eq!(result.cipher.len(), 0); - assert_eq!(result.tag.len(), key_len); - } - } - - #[test] - fn handles_empty_associated_data() { - 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 result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce); - - let decrypted = - KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce); - - assert_eq!(decrypted.plaintext, plaintext); - assert_eq!(decrypted.tag, result.tag); - } - } - - #[test] - fn handles_multiple_chunks() { - let key = [0x42u8; 32]; - let iv = [0x11u8; 16]; - let nonce = [0x22u8; 16]; - - // > 16 bytes for multiple chunks. - let plaintext = b"0123456789abcdef0123456789abcdef0123"; - - let associated_data = b"abcdefghijklmnopqrstuvwxyz"; - - 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, - associated_data, - &nonce, - ); - - assert_eq!(decrypted.plaintext, plaintext); - assert_eq!(decrypted.tag, encrypted.tag); - } - - #[test] - fn tag_changes_when_ciphertext_changes() { - 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 mut modified = encrypted.cipher.clone(); - modified[0] ^= 1; - - 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); - } -} diff --git a/src/keccak_aead/mod.rs b/src/keccak_aead/mod.rs new file mode 100644 index 0000000..b53ab34 --- /dev/null +++ b/src/keccak_aead/mod.rs @@ -0,0 +1,6 @@ +mod keccak_aead; + +#[cfg(test)] +mod test; + +pub use keccak_aead::{DecryptionResult, EncryptionResult, KeccakAead}; diff --git a/src/keccak_aead/test.rs b/src/keccak_aead/test.rs new file mode 100644 index 0000000..18e7e24 --- /dev/null +++ b/src/keccak_aead/test.rs @@ -0,0 +1,113 @@ +use super::*; + +#[test] +fn encrypt_decrypt_roundtrip() { + 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 plaintext = b"Hello, Keccak AEAD!"; + + let encrypted = KeccakAead::encrypt(&key, plaintext, &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!(encrypted.tag.len(), key_len); + } +} + +#[test] +fn handles_empty_plaintext() { + 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); + + assert_eq!(result.cipher.len(), 0); + assert_eq!(result.tag.len(), key_len); + } +} + +#[test] +fn handles_empty_associated_data() { + 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 result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce); + + let decrypted = KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce); + + assert_eq!(decrypted.plaintext, plaintext); + assert_eq!(decrypted.tag, result.tag); + } +} + +#[test] +fn handles_multiple_chunks() { + let key = [0x42u8; 32]; + let iv = [0x11u8; 16]; + let nonce = [0x22u8; 16]; + + // > 16 bytes for multiple chunks. + let plaintext = b"0123456789abcdef0123456789abcdef0123"; + + let associated_data = b"abcdefghijklmnopqrstuvwxyz"; + + 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, associated_data, &nonce); + + assert_eq!(decrypted.plaintext, plaintext); + assert_eq!(decrypted.tag, encrypted.tag); +} + +#[test] +fn tag_changes_when_ciphertext_changes() { + 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 mut modified = encrypted.cipher.clone(); + modified[0] ^= 1; + + 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); +} diff --git a/src/main.rs b/src/main.rs index f4eea31..9363bb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,11 +69,7 @@ fn main() -> Result<(), Box> { encrypt_file(&input, &output, &key)?; - println!( - "Encrypted '{}' -> '{}'", - input.display(), - output.display() - ); + println!("Encrypted '{}' -> '{}'", input.display(), output.display()); } Command::Decrypt { @@ -85,11 +81,7 @@ fn main() -> Result<(), Box> { decrypt_file(&input, &output, &key)?; - println!( - "Decrypted '{}' -> '{}'", - input.display(), - output.display() - ); + println!("Decrypted '{}' -> '{}'", input.display(), output.display()); } }