diff --git a/Cargo.lock b/Cargo.lock index 5291f63f..4062a6dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,13 +401,13 @@ dependencies = [ "aegis", "aes", "cmov", + "digest", "expect-test", "hex", "hex-literal", "proptest", "rand", "rand_core", - "sha2", ] [[package]] @@ -561,27 +561,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", - "sha2-asm", -] - -[[package]] -name = "sha2-asm" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ba7066011e3fb30d808b51affff34f0a66d3a03a58edd787c6e420e40e44e" -dependencies = [ - "cc", -] - [[package]] name = "softaes" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 031e63cd..fe2d2929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,12 @@ include = ["src/**/*", "tests/**/*", "LICENSE", "README.md", "design.md", "perf. [dependencies] aes = { version = "0.8.3", features = ["hazmat"], optional = true } cmov = "0.3.1" +digest = { version = "0.10.7", default-features = false, features = ["block-buffer", "core-api"] } +hex-literal = "0.4.1" rand_core = { version = "0.6.4", default-features = false, optional = true } -sha2 = { version = "0.10.8", default-features = false } [features] -asm = ["sha2/asm"] -default = ["asm", "hedge", "std"] +default = ["hedge", "std"] docs = [] hedge = ["rand_core"] portable = ["aes"] @@ -33,7 +33,6 @@ members = ["benchmarks", "xtask"] aegis = { version = "0.4.9", features = ["pure-rust"] } expect-test = "1.4.1" hex = "0.4.3" -hex-literal = "0.4.1" proptest = "1.3.1" rand = "0.8.5" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 92799c65..5e60b165 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -30,27 +30,12 @@ dependencies = [ "libc", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "cmov" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1dc960ba75d543267db9254da8ec1cb318a037beb3f8d2497520e410096fab" -[[package]] -name = "cpufeatures" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" -dependencies = [ - "libc", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -101,6 +86,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "jobserver" version = "0.1.27" @@ -132,8 +123,9 @@ name = "lockstitch" version = "0.16.2" dependencies = [ "cmov", + "digest", + "hex-literal", "rand_core", - "sha2", ] [[package]] @@ -166,27 +158,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", - "sha2-asm", -] - -[[package]] -name = "sha2-asm" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ba7066011e3fb30d808b51affff34f0a66d3a03a58edd787c6e420e40e44e" -dependencies = [ - "cc", -] - [[package]] name = "syn" version = "2.0.38" diff --git a/src/areion.rs b/src/areion.rs new file mode 100644 index 00000000..652311c0 --- /dev/null +++ b/src/areion.rs @@ -0,0 +1,268 @@ +use core::{fmt, slice}; + +use crate::intrinsics::*; + +use digest::block_buffer::Eager; +use digest::core_api::{ + Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, FixedOutputCore, UpdateCore, +}; +use digest::crypto_common::AlgorithmName; +use digest::generic_array::GenericArray; +use digest::typenum::{Unsigned, U64}; +use digest::{HashMarker, Output, OutputSizeUser, Reset}; +use hex_literal::hex; + +#[derive(Debug, Clone)] +struct State(AesBlock, AesBlock, AesBlock, AesBlock); + +impl Default for State { + fn default() -> Self { + Self( + load_64x2(0x6a09e667f3bcc908u64, 0xbb67ae8584caa73bu64), + load_64x2(0x3c6ef372fe94f82bu64, 0xa54ff53a5f1d36f1u64), + load_64x2(0x510e527fade682d1u64, 0x9b05688c2b3e6c1fu64), + load_64x2(0x1f83d9abfb41bd6bu64, 0x5be0cd19137e2179u64), + ) + } +} + +impl State { + fn compress(&mut self, blocks: &[GenericArray]) { + let Self(mut h0, mut h1, mut h2, mut h3) = self; + for block in blocks { + let (m0, m1, m2, m3) = ( + load(&block[..16]), + load(&block[16..32]), + load(&block[32..48]), + load(&block[48..]), + ); + + // H_i = E_{H_{i-1}}(m_i) ^ m_i + h0 = xor(h0, m0); + h1 = xor(h1, m1); + h2 = xor(h2, m2); + h3 = xor(h3, m3); + let (x0, x1, x2, x3) = areion512(h0, h1, h2, h3); + h0 = xor3(x0, h0, m0); + h1 = xor3(x1, h1, m1); + h2 = xor3(x2, h2, m2); + h3 = xor3(x3, h3, m3); + } + *self = Self(h0, h1, h2, h3); + } +} + +#[derive(Debug, Default, Clone)] +pub struct Core { + state: State, + block_len: u128, +} + +impl HashMarker for Core {} + +impl BlockSizeUser for Core { + type BlockSize = U64; +} + +impl BufferKindUser for Core { + type BufferKind = Eager; +} + +impl OutputSizeUser for Core { + type OutputSize = U64; +} + +impl UpdateCore for Core { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + self.block_len += blocks.len() as u128; + self.state.compress(blocks); + } +} + +impl FixedOutputCore for Core { + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let bs = Self::BlockSize::U64 as u128; + let bit_len = 8 * (buffer.get_pos() as u128 + bs * self.block_len); + buffer.len128_padding_be(bit_len, |b| self.state.compress(slice::from_ref(b))); + + store(&mut out[..16], self.state.0); + store(&mut out[16..32], self.state.1); + store(&mut out[32..48], self.state.2); + store(&mut out[48..], self.state.3); + } +} + +impl Reset for Core { + #[inline] + fn reset(&mut self) { + *self = Default::default(); + } +} + +impl AlgorithmName for Core { + #[inline] + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Areion512-MMO") + } +} + +// A Matyas-Meyer-Oseas hash function using a single-key Even-Mansour block cipher based on the +// Areion512 permutation. +pub type Areion512Mmo = CoreWrapper; + +#[inline] +fn areion512( + x0: AesBlock, + x1: AesBlock, + x2: AesBlock, + x3: AesBlock, +) -> (AesBlock, AesBlock, AesBlock, AesBlock) { + let (x0, x1, x2, x3) = round_512::<0>(x0, x1, x2, x3); + let (x1, x2, x3, x0) = round_512::<1>(x1, x2, x3, x0); + let (x2, x3, x0, x1) = round_512::<2>(x2, x3, x0, x1); + let (x3, x0, x1, x2) = round_512::<3>(x3, x0, x1, x2); + let (x0, x1, x2, x3) = round_512::<4>(x0, x1, x2, x3); + let (x1, x2, x3, x0) = round_512::<5>(x1, x2, x3, x0); + let (x2, x3, x0, x1) = round_512::<6>(x2, x3, x0, x1); + let (x3, x0, x1, x2) = round_512::<7>(x3, x0, x1, x2); + let (x0, x1, x2, x3) = round_512::<8>(x0, x1, x2, x3); + let (x1, x2, x3, x0) = round_512::<9>(x1, x2, x3, x0); + let (x2, x3, x0, x1) = round_512::<10>(x2, x3, x0, x1); + let (x3, x0, x1, x2) = round_512::<11>(x3, x0, x1, x2); + let (x0, x1, x2, x3) = round_512::<12>(x0, x1, x2, x3); + let (x1, x2, x3, x0) = round_512::<13>(x1, x2, x3, x0); + let (x2, x3, x0, x1) = round_512::<14>(x2, x3, x0, x1); + (x0, x1, x2, x3) +} + +#[inline] +fn round_512( + x0: AesBlock, + x1: AesBlock, + x2: AesBlock, + x3: AesBlock, +) -> (AesBlock, AesBlock, AesBlock, AesBlock) { + let rc0 = load(&RC0[R]); + let rc1 = load(&RC1); + let x1 = enc(x0, x1); + let x3 = enc(x2, x3); + let x0 = enc_last(x0, rc1); + let x2 = enc(enc_last(x2, rc0), rc1); + (x0, x1, x2, x3) +} + +static RC0: [[u8; 16]; 24] = [ + hex!("886a3f24d308a3852e8a191344737003"), + hex!("223809a4d0319f2998fa2e08896c4eec"), + hex!("e62128457713d038cf6654be6c0ce934"), + hex!("b729acc0dd507cc9b5d5843f170947b5"), + hex!("d9d516921bfb7989a60b31d1acb5df98"), + hex!("db72fd2fb7df1ad0edafe1b8967e266a"), + hex!("45907cba997f2cf14799a124f76c91b3"), + hex!("282e1f8066c1ef58870d923690e67415"), + hex!("a3fe58a47e3d93f48f74950d58b68e72"), + hex!("58cd8b71ee4a15821da4547bb5595ac2"), + hex!("39d5309c1360f22a23b0d1c5f0856028"), + hex!("187941caef38dbb8b0dc798e0e183a60"), + hex!("8b0e9e6c3e8a1eb0c17715d7274b31bd"), + hex!("da2faf78605c6055f32555e694ab55aa"), + hex!("629848574014e8636a39ca55b610ab2a"), + hex!("345cccb4cee84111af8654a193e9727c"), + hex!("1114eeb32abc6f635dc5a92bf6311874"), + hex!("163e5cce1e93879b33bad6af5ccf246c"), + hex!("8153327a7786952898488f3bafb94b6b"), + hex!("1be8bfc493212866cc09d86191a921fb"), + hex!("60ac7c483280ec5d5d5d84efb17585e9"), + hex!("022326dc881b65eb813e8923c5ac96d3"), + hex!("38ffd6f69223443f2a48b4e040004248"), + hex!("4af0c8695e9b1f9e4268c6219a6ce9f6"), +]; + +static RC1: [u8; 16] = hex!("00000000000000000000000000000000"); + +#[cfg(test)] +mod tests { + use super::*; + + use digest::Digest; + use expect_test::expect; + use hex_literal::hex; + use proptest::collection::vec; + use proptest::prelude::*; + + fn writes() -> impl Strategy>> { + vec(vec(any::(), 0..200), 0..100) + } + + proptest! { + #[test] + fn incremental_hashing(writes in writes()) { + let combined = writes.iter().flatten().copied().collect::>(); + + let mut h = Areion512Mmo::new(); + h.update(&combined); + let t0 = h.finalize(); + + let mut h = Areion512Mmo::new(); + for write in writes { + h.update(&write); + } + let t1 = h.finalize(); + + prop_assert_eq!(t0, t1); + } + } + + #[test] + fn perm512_test_vector_1() { + let x0 = load(&hex!("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")); + let x1 = load(&hex!("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")); + let x2 = load(&hex!("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")); + let x3 = load(&hex!("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")); + let (x0, x1, x2, x3) = areion512(x0, x1, x2, x3); + + let mut x_p = [0u8; 64]; + store(&mut x_p[..16], x0); + store(&mut x_p[16..32], x1); + store(&mut x_p[32..48], x2); + store(&mut x_p[48..], x3); + expect![[r#" + 5f ee f7 7c bb e8 4c 79 58 08 94 59 f4 54 e9 6f + bf 21 fa b8 35 65 cc af 91 6b cf 9c fb 63 d2 5b + a0 26 42 fc c1 75 12 36 40 d6 a2 18 3b a6 82 b2 + 0b 72 3a fc 66 68 ff f3 de c4 7c 17 61 27 b9 84"#]] + .assert_eq(&hex_fmt(&x_p)); + } + + #[test] + fn perm512_test_vector_2() { + let x0 = load(&hex!("00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f")); + let x1 = load(&hex!("10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f")); + let x2 = load(&hex!("20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f")); + let x3 = load(&hex!("30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f")); + let (x0, x1, x2, x3) = areion512(x0, x1, x2, x3); + + let mut x_p = [0u8; 64]; + store(&mut x_p[..16], x0); + store(&mut x_p[16..32], x1); + store(&mut x_p[32..48], x2); + store(&mut x_p[48..], x3); + expect![[r#" + a6 09 5f e0 57 d2 83 80 ba d2 5c 28 12 b2 30 f6 + 6f 07 b0 09 a3 04 98 5a f4 37 bb 60 8a 4c b8 31 + 39 2a 6f 2f 48 e4 25 ef 24 11 96 21 67 2e 37 c4 + f1 9b 94 e0 e4 ea ed af b9 f4 eb 12 6a 6d 8a bb"#]] + .assert_eq(&hex_fmt(&x_p)); + } + + fn hex_fmt(b: &[u8]) -> String { + b.iter() + .map(|v| format!("{:02x}", v)) + .collect::>() + .chunks(16) + .map(|v| v.join(" ")) + .collect::>() + .join("\n") + } +} diff --git a/src/intrinsics/aarch64.rs b/src/intrinsics/aarch64.rs index 1ca0935e..e02fcd1c 100644 --- a/src/intrinsics/aarch64.rs +++ b/src/intrinsics/aarch64.rs @@ -70,3 +70,9 @@ pub fn and(a: AesBlock, b: AesBlock) -> AesBlock { pub fn enc(state: AesBlock, round_key: AesBlock) -> AesBlock { unsafe { veorq_u8(vaesmcq_u8(vaeseq_u8(state, zero())), round_key) } } + +/// Perform the final AES round on the given state using the given round key. +#[inline] +pub fn enc_last(state: uint8x16_t, round_key: uint8x16_t) -> uint8x16_t { + unsafe { veorq_u8(vaeseq_u8(state, zero()), round_key) } +} diff --git a/src/intrinsics/portable.rs b/src/intrinsics/portable.rs index 6ea836e0..e9140d0c 100644 --- a/src/intrinsics/portable.rs +++ b/src/intrinsics/portable.rs @@ -65,3 +65,10 @@ pub fn enc(mut state: AesBlock, round_key: AesBlock) -> AesBlock { aes::hazmat::cipher_round(&mut state, &round_key); state } + +/// Perform the final AES round on the given state using the given round key. +#[inline] +pub fn enc_last(mut state: AesBlock, round_key: AesBlock) -> AesBlock { + aes::hazmat::cipher_round(&mut state, &round_key); + state +} diff --git a/src/lib.rs b/src/lib.rs index fa5890ba..262a59f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,13 @@ use core::fmt::Debug; use crate::aegis_128l::Aegis128L; +use crate::areion::Areion512Mmo; use cmov::CmovEq; -use sha2::digest::{Digest, FixedOutputReset}; -use sha2::Sha256; +use digest::{Digest, FixedOutputReset}; mod aegis_128l; +mod areion; mod intrinsics; #[cfg(feature = "docs")] @@ -40,7 +41,7 @@ pub const TAG_LEN: usize = 16; /// message authentication codes, pseudo-random functions, authenticated encryption, and more. #[derive(Debug, Clone)] pub struct Protocol { - state: Sha256, + state: Areion512Mmo, } impl Protocol { @@ -48,7 +49,7 @@ impl Protocol { #[inline] pub fn new(domain: &str) -> Protocol { // Create a protocol with a fresh SHA-256 instance. - let mut protocol = Protocol { state: Sha256::new() }; + let mut protocol = Protocol { state: Areion512Mmo::new() }; // Update the protocol with the domain and the INIT operation. protocol.process(domain.as_bytes(), Operation::Init); @@ -209,22 +210,11 @@ impl Protocol { #[must_use] fn chain(&mut self, operation: Operation) -> Aegis128L { // Finalize the current state and reset it to an uninitialized state. - let hash = self.state.finalize_fixed_reset(); - - // Split the hash into a key and nonce and initialize an AEGIS-128L instance for PRF output. - let (prf_key, prf_nonce) = hash.split_at(16); - let mut prf = Aegis128L::new( - &prf_key.try_into().expect("should be valid AEGIS-128L key"), - &prf_nonce.try_into().expect("should be valid AEGIS-128L nonce"), - ); - - // Generate 64 bytes of PRF output. - let mut prf_out = [0u8; 64]; - prf.prf(&mut prf_out); - - // Split the PRF output into a 32-byte chain key, a 16-byte output key, and a 16-byte output - // nonce, setting the first bytes of the output nonce to the operation code. - let (chain_key, output_key) = prf_out.split_at_mut(32); + let mut hash = self.state.finalize_fixed_reset(); + + // Split the output into a chain key, an output key, and an output nonce, setting the first + // byte of the output nonce to the operation code. + let (chain_key, output_key) = hash.split_at_mut(32); let (output_key, output_nonce) = output_key.split_at_mut(16); output_nonce[0] = operation as u8; @@ -293,7 +283,7 @@ enum Operation { #[cfg(feature = "std")] #[derive(Debug)] pub struct MixWriter { - state: Sha256, + state: Areion512Mmo, inner: W, total: u64, } @@ -338,11 +328,11 @@ mod tests { protocol.mix(b"one"); protocol.mix(b"two"); - expect!["3f6d24ea37711c9e"].assert_eq(&hex::encode(protocol.derive_array::<8>())); + expect!["c584175ba5497acb"].assert_eq(&hex::encode(protocol.derive_array::<8>())); let mut plaintext = b"this is an example".to_vec(); protocol.encrypt(&mut plaintext); - expect!["368ee0e2c781264276958471a2bbf634269b"].assert_eq(&hex::encode(plaintext)); + expect!["8b26af16851265e1a07561f591e1421a8d7f"].assert_eq(&hex::encode(plaintext)); protocol.ratchet(); @@ -351,10 +341,10 @@ mod tests { sealed[..plaintext.len()].copy_from_slice(plaintext); protocol.seal(&mut sealed); - expect!["c042e1f16a2ed317ad6590cbc6500247d9007d8446ea1f6cd7682c845714474f1b23"] + expect!["b57136a3333abd4b54308b25e72fde1a12dc2f492e563db4c207dec56fa2732054a7"] .assert_eq(&hex::encode(sealed)); - expect!["2e9d721872356471"].assert_eq(&hex::encode(protocol.derive_array::<8>())); + expect!["bfe2b9ead33d6059"].assert_eq(&hex::encode(protocol.derive_array::<8>())); } #[test]