diff --git a/.github/workflows/ascon.yml b/.github/workflows/ascon.yml index 475d2a1..8808b58 100644 --- a/.github/workflows/ascon.yml +++ b/.github/workflows/ascon.yml @@ -21,7 +21,7 @@ jobs: set-msrv: uses: RustCrypto/actions/.github/workflows/set-msrv.yml@master with: - msrv: 1.59.0 + msrv: 1.60.0 benches: runs-on: ubuntu-latest @@ -56,6 +56,7 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --no-default-features --target ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} --features aead minimal-versions: uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master @@ -80,3 +81,4 @@ jobs: override: true - run: cargo test --no-default-features - run: cargo test + - run: cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index 680a29b..83c1418 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,65 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ascon" version = "0.2.0-pre" +dependencies = [ + "aead", + "hex-literal", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/ascon/Cargo.toml b/ascon/Cargo.toml index 218ed2b..d2a8c7e 100644 --- a/ascon/Cargo.toml +++ b/ascon/Cargo.toml @@ -13,11 +13,21 @@ keywords = ["crypto", "sponge"] categories = ["cryptography", "no-std"] readme = "README.md" edition = "2021" -rust-version = "1.59" +rust-version = "1.60" + +[dependencies] +aead = { version = "0.5", optional = true, default-features = false } +subtle = { version = "2", optional = true, default-features = false } + +[dev-dependencies] +hex-literal = "0.3" [features] -default = ["alloc"] -alloc = [] +default = ["aead"] +alloc = ["aead?/alloc"] +std = ["aead?/std"] + +aead = ["dep:aead", "alloc", "subtle"] # TODO(tarcieri): remove alloc dependency [package.metadata.docs.rs] all-features = true diff --git a/ascon/LICENSE-APACHE b/ascon/LICENSE-APACHE index deed143..78173fa 100644 --- a/ascon/LICENSE-APACHE +++ b/ascon/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright (c) 2016-2023 quininer kel, RustCrypto Developers +Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/ascon/README.md b/ascon/README.md index 5348294..641d751 100644 --- a/ascon/README.md +++ b/ascon/README.md @@ -28,7 +28,7 @@ portfolio of the [CAESAR competition]. ## Minimum Supported Rust Version -This crate requires **Rust 1.59** at a minimum. +This crate requires **Rust 1.60** at a minimum. We may change the MSRV in the future, but it will be accompanied by a minor version bump. @@ -57,7 +57,7 @@ dual licensed as above, without any additional terms or conditions. [build-image]: https://github.com/RustCrypto/sponges/actions/workflows/ascon.yml/badge.svg [build-link]: https://github.com/RustCrypto/sponges/actions/workflows/ascon.yml [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.59+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/369879-sponges diff --git a/ascon/benches/bench.rs b/ascon/benches/bench.rs index 9c3508d..b12caf5 100644 --- a/ascon/benches/bench.rs +++ b/ascon/benches/bench.rs @@ -2,28 +2,35 @@ extern crate test; -use ascon::aead; +use ascon::aead::{AeadInPlace, AsconAead, KeyInit}; +use hex_literal::hex; use test::Bencher; -#[bench] -fn ascon_encrypt_bench(b: &mut Bencher) { - let key = [4; 16]; - let iv = [8; 16]; - let aad = [3; 16]; - let message = [99; 1025]; - - b.bytes = message.len() as u64; - b.iter(|| aead::encrypt(&key, &iv, &message, &aad)); -} +const KEY: [u8; 16] = [4; 16]; +const NONCE: [u8; 16] = [8; 16]; +const AAD: [u8; 16] = [3; 16]; +const PLAINTEXT: [u8; 1025] = [99; 1025]; +const CIPHERTEXT: [u8; 1025] = hex!("d5cfb4650e0a82b9e86455de588cfd48c4bf240301efbf1981a57f00c5ec31eb9127d5da85fab0bf3c927b9d875cd1f71e0945f0281a0774e890bae1f98d488580c63e19972d73b6df19773503b0d8f0317727b8ab7681e15237eec369e8ea2915695d6f03f44541ea28f737574d5c7277361636a0a246fbc8c4b6764ef642b9a15211e11f5059fd08d173e4a9fff4cc55389d04a392f0a12dbccce67439e967bb8f39887b078cd68eaa8fd5a3c3b7299536c50a35b7871ab612417fdd1313527cbc82591d20417f2ef3dc37dc1298b32677daac9e79cf28307ceb886aea42694322ac5e978405fff9df6b9da86c83661fa165c957b830dc85350bb41ae6a6f9ee7c713d47b6277b54c64d0ceefd41945f23cfe4f0ba0ea39ba7c829c308ab53591817adeda57c4d3858fff07f57ec4100905e8568e855f61d558f7cffc3dc47b4e2ab9b90053caef52bede5871c5839b33f48e8822dbca59e96532ded31e809d901de72d65d5112ac067f7135670445bdec7e9f41287190dd091f906d6d98b6d15aa3fa475a2b4f99bc0e12e9c057140308a73572dcabad2e2140103028c075e9da74d92177aaea9d1899dace0ee8f4a9edfa9c4abc39a4b6239a2ea3841af1f90af884d10ef43b48924cba8474cb0162c8204acab4647cb680e9b64fb77e4d2195f63dd45e6c21b637db5c15101a72259e5af940ce9e5c0fe0b4affefef059e2982c32dfe50c0aefee368f15867ab2ff205fa855a895a0379b5678f432e25fd884771873acf6ee72fe4cf02c13843239b3114ff9e9bfebc44618bffc731b272cf08e627e47d759f1ae63191892d493a19fb54b96dd2132e416612d4e4a3bb536d0470c151c998a9ce5a44883ac395efa23e201734ef6a9e7ac1dade6a48fff8445e39a38157b5263cf9e52abf8cb604be9f1ed7ebe85e10933ab1f3e0546c17bbf7561233d7f95b32f8086fdbb06948af5a0ffd06dd47e89c3f4a419cc5b34300a76a542aeeae716563204f68f880cac077855297150fd217d6185116d542b64ce1319712760bf61fa1b6b52c8e9feb609bf6caa07b41ed80c7423cbe5390ec9851a2629d9d307971515c0a1696b0d4d52deabab089b945326b1e62a6ec5eb2cf2c58a5af0404962923a0c8605b6d55fd899765b193db8f5a849ecab478e187476779cf5f08d38a812b14ddff6143870ce5d9afc3e2eda91624067654940856421f5a8e0f6e3eefe888bd11c9364a29e2821374a1550e888359230ba863424746c793708e0ba23be967fa527ee5989eaf48541928e594ea846d67d5a6a56aa886be7b693d662cd675c786687c0d870bf19ab7564fb0b87bc6bba157ae04f482fca52858f8ff600141efa24f2f29cb0b8e222d88d9bfd9e60818474e55cc2b11e70125a956934c75cad4207f83b372c501ca7864bf8d387d0f81ec1cc872ff180"); +const TAG: [u8; 16] = hex!("17d7dca595adcaaddf537a1e9fd02854"); #[bench] fn ascon_decrypt_bench(b: &mut Bencher) { - let key = [4; 16]; - let iv = [8; 16]; - let aad = [3; 16]; - let message = [99; 1025]; - let (ciphertext, tag) = aead::encrypt(&key, &iv, &message, &aad); - - b.bytes = message.len() as u64; - b.iter(|| aead::decrypt(&key, &iv, &ciphertext, &aad, &tag)); + let aead = AsconAead::<12, 8>::new(&KEY.into()); + b.bytes = CIPHERTEXT.len() as u64; + b.iter(|| { + let mut buffer = CIPHERTEXT; + aead.decrypt_in_place_detached(&NONCE.into(), &AAD, &mut buffer, &TAG.into()) + .unwrap(); + }); +} + +#[bench] +fn ascon_encrypt_bench(b: &mut Bencher) { + let aead = AsconAead::<12, 8>::new(&KEY.into()); + b.bytes = PLAINTEXT.len() as u64; + b.iter(|| { + let mut buffer = PLAINTEXT; + aead.encrypt_in_place_detached(&NONCE.into(), &AAD, &mut buffer) + .unwrap(); + }); } diff --git a/ascon/src/aead.rs b/ascon/src/aead.rs index e78caa9..2a59a1e 100644 --- a/ascon/src/aead.rs +++ b/ascon/src/aead.rs @@ -1,156 +1,164 @@ //! Authenticated Encryption with Associated Data. -use crate::{Ascon, Key, Nonce, KEY_SIZE, RATE, S_SIZE}; -use alloc::{vec, vec::Vec}; +pub use ::aead::{self, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser}; -/// Ascon(a,b) `b`-parameter. -const B: usize = 8; +use crate::{Ascon, KEY_SIZE, RATE, S_SIZE}; +use ::aead::consts::{U0, U16}; -/// Tag length. -const TAG_LEN: usize = 16; +// TODO(tarcieri): remove hard dependency on alloc +use alloc::vec; -/// Authentication tag. -type Tag = [u8; TAG_LEN]; +/// Ascon AEAD key. +pub type Key = ::aead::generic_array::GenericArray; -/// Decryption errors. -#[derive(Debug)] -pub enum DecryptFail { - /// Invalid tag length. - TagLengthError, +/// Ascon AEAD nonce. +pub type Nonce = ::aead::generic_array::GenericArray; - /// Authentication failure (invalid tag). - AuthenticationFail, +/// Ascon AEAD authentication tag. +pub type Tag = ::aead::generic_array::GenericArray; + +/// Ascon AEAD encryption. +#[derive(Clone)] +pub struct AsconAead { + key: Key, } -/// AEAD encryption. -pub fn encrypt(key: &Key, nonce: &Nonce, message: &[u8], aad: &[u8]) -> (Vec, Tag) { - let s = aad.len() / RATE + 1; - let t = message.len() / RATE + 1; - let l = message.len() % RATE; +impl KeySizeUser for AsconAead { + type KeySize = U16; +} - let mut aa = vec![0; s * RATE]; - let mut mm = vec![0; t * RATE]; - - let mut output = vec![0; message.len()]; - - // pad aad - aa[..aad.len()].copy_from_slice(aad); - aa[aad.len()] = 0x80; - - // pad message - mm[..message.len()].copy_from_slice(message); - mm[message.len()] = 0x80; - - // init - let mut ss = Ascon::new(key, nonce); - - // aad - if !aad.is_empty() { - process_aad(&mut ss, &aa, s); +impl KeyInit for AsconAead { + fn new(key_bytes: &Key) -> Self { + Self { key: *key_bytes } } +} - ss.state[S_SIZE - 1] ^= 1; +impl AeadCore for AsconAead { + type NonceSize = U16; + type TagSize = U16; + type CiphertextOverhead = U0; +} - // plaintext - for i in 0..(t - 1) { - for j in 0..RATE { - ss.state[j] ^= mm[i * RATE + j]; +impl AeadInPlace for AsconAead { + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: &mut [u8], + ) -> Result { + let s = aad.len() / RATE + 1; + let t = buffer.len() / RATE + 1; + let l = buffer.len() % RATE; + + let mut aa = vec![0; s * RATE]; + let mut mm = vec![0; t * RATE]; + + // pad aad + aa[..aad.len()].copy_from_slice(aad); + aa[aad.len()] = 0x80; + + // pad message + mm[..buffer.len()].copy_from_slice(buffer); + mm[buffer.len()] = 0x80; + + // init + let mut ss = Ascon::::new(self.key.as_ref(), nonce.as_ref()); + + // aad + if !aad.is_empty() { + process_aad(&mut ss, &aa, s); } - output[(i * RATE)..(i * RATE + RATE)].copy_from_slice(&ss.state[..RATE]); - ss.permutation(12 - B, B); - } - for j in 0..RATE { - ss.state[j] ^= mm[(t - 1) * RATE + j]; - } + ss.state[S_SIZE - 1] ^= 1; - for j in 0..l { - output[(t - 1) * RATE + j] = ss.state[j]; - } - - // tag - let mut tag = Tag::default(); - tag.copy_from_slice(&ss.finalize()[S_SIZE - KEY_SIZE..]); - - (output, tag) -} - -/// AEAD decryption. -pub fn decrypt( - key: &Key, - nonce: &Nonce, - ciphertext: &[u8], - aad: &[u8], - tag: &[u8], -) -> Result, DecryptFail> { - if tag.len() != KEY_SIZE { - Err(DecryptFail::TagLengthError)? - }; - - let s = aad.len() / RATE + 1; - let t = ciphertext.len() / RATE + 1; - let l = ciphertext.len() % RATE; - - let mut aa = vec![0; s * RATE]; - let mut mm = vec![0; t * RATE]; - - // pad aad - aa[..aad.len()].copy_from_slice(aad); - aa[aad.len()] = 0x80; - - // init - let mut ss = Ascon::new(key, nonce); - - // aad - if !aad.is_empty() { - process_aad(&mut ss, &aa, s); - } - - ss.state[S_SIZE - 1] ^= 1; - - // ciphertext - for i in 0..(t - 1) { - for j in 0..RATE { - mm[i * RATE + j] = ss.state[j] ^ ciphertext[i * RATE + j]; + // plaintext + for i in 0..(t - 1) { + for j in 0..RATE { + ss.state[j] ^= mm[i * RATE + j]; + } + buffer[(i * RATE)..(i * RATE + RATE)].copy_from_slice(&ss.state[..RATE]); + ss.permutation(12 - B, B); } - ss.state[..RATE].copy_from_slice(&ciphertext[(i * RATE)..(i * RATE + RATE)]); - ss.permutation(12 - B, B); + + for j in 0..RATE { + ss.state[j] ^= mm[(t - 1) * RATE + j]; + } + + for j in 0..l { + buffer[(t - 1) * RATE + j] = ss.state[j]; + } + + // tag + let mut tag = Tag::default(); + tag.copy_from_slice(&ss.finalize()[S_SIZE - KEY_SIZE..]); + + Ok(tag) } - for j in 0..l { - mm[(t - 1) * RATE + j] = ss.state[j] ^ ciphertext[(t - 1) * RATE + j]; - } + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + let s = aad.len() / RATE + 1; + let t = buffer.len() / RATE + 1; + let l = buffer.len() % RATE; - for j in 0..l { - ss.state[j] = ciphertext[(t - 1) * RATE + j]; - } + let mut aa = vec![0; s * RATE]; + let mut mm = vec![0; t * RATE]; - ss.state[l] ^= 0x80; + // pad aad + aa[..aad.len()].copy_from_slice(aad); + aa[aad.len()] = 0x80; - // finalization - let expected_tag = ss.finalize(); + // init + let mut ss = Ascon::::new(self.key.as_ref(), nonce.as_ref()); - if ct_eq(&expected_tag[S_SIZE - KEY_SIZE..], tag) { - Ok(mm[..ciphertext.len()].into()) - } else { - Err(DecryptFail::AuthenticationFail) + // aad + if !aad.is_empty() { + process_aad(&mut ss, &aa, s); + } + + ss.state[S_SIZE - 1] ^= 1; + + // ciphertext + for i in 0..(t - 1) { + for j in 0..RATE { + mm[i * RATE + j] = ss.state[j] ^ buffer[i * RATE + j]; + } + ss.state[..RATE].copy_from_slice(&buffer[(i * RATE)..(i * RATE + RATE)]); + ss.permutation(12 - B, B); + } + + for j in 0..l { + mm[(t - 1) * RATE + j] = ss.state[j] ^ buffer[(t - 1) * RATE + j]; + } + + for j in 0..l { + ss.state[j] = buffer[(t - 1) * RATE + j]; + } + + ss.state[l] ^= 0x80; + + // finalization + let expected_tag = ss.finalize(); + + use subtle::ConstantTimeEq; + if expected_tag[S_SIZE - KEY_SIZE..] + .ct_eq(tag.as_slice()) + .into() + { + buffer.copy_from_slice(&mm[..buffer.len()]); + Ok(()) + } else { + Err(Error) + } } } -// TODO(tarcieri): use `subtle` -fn ct_eq(a: &[u8], b: &[u8]) -> bool { - if a.len() != b.len() { - false - } else { - a.iter() - .zip(b) - .map(|(x, y)| x ^ y) - .fold(0, |sum, next| sum | next) - .eq(&0) - } -} - -fn process_aad(ss: &mut Ascon, aa: &[u8], s: usize) { +fn process_aad(ss: &mut Ascon, aa: &[u8], s: usize) { for i in 0..s { for j in 0..RATE { ss.state[j] ^= aa[i * RATE + j]; @@ -162,38 +170,50 @@ fn process_aad(ss: &mut Ascon, aa: &[u8], s: usize) { #[cfg(test)] mod tests { - use super::{decrypt, encrypt}; + use super::{AeadInPlace, AsconAead, KeyInit}; + use hex_literal::hex; #[test] fn round_trip() { let key = [0; 16]; - let iv = [0; 16]; + let nonce = [0; 16]; let aad = [0; 16]; let message = [0; 64]; - let (ciphertext, tag) = encrypt(&key, &iv, &message, &aad); - let plaintext = decrypt(&key, &iv, &ciphertext, &aad, &tag).unwrap(); - assert_eq!(plaintext, &message[..]); + let aead = AsconAead::<12, 8>::new(&key.into()); + let mut buffer = message; + + let tag = aead + .encrypt_in_place_detached(&nonce.into(), &aad, &mut buffer) + .unwrap(); + aead.decrypt_in_place_detached(&nonce.into(), &aad, &mut buffer, &tag) + .unwrap(); + + assert_eq!(message, buffer); } #[test] fn test_vectors() { - let key = [0; 16]; - let iv = [0; 16]; - let aad = b"ASCON"; - let message = b"ascon"; + const KEY: [u8; 16] = [0; 16]; + const NONCE: [u8; 16] = [0; 16]; + const AAD: &[u8; 5] = b"ASCON"; + const PLAINTEXT: &[u8; 5] = b"ascon"; + const CIPHERTEXT: &[u8; 5] = &hex!("4c8c428949"); + const TAG: &[u8; 16] = &hex!("65fd17b6d30cd876a05a8efcecad993a"); - let (ciphertext, tag) = encrypt(&key, &iv, message, aad); - assert_eq!(ciphertext, [0x4c, 0x8c, 0x42, 0x89, 0x49]); - assert_eq!( - tag, - [ - 0x65, 0xfd, 0x17, 0xb6, 0xd3, 0x0c, 0xd8, 0x76, 0xa0, 0x5a, 0x8e, 0xfc, 0xec, 0xad, - 0x99, 0x3a - ] - ); + let aead = AsconAead::<12, 8>::new(&KEY.into()); + let mut buffer = *PLAINTEXT; - let plaintext = decrypt(&key, &iv, &ciphertext, aad, &tag).unwrap(); - assert_eq!(plaintext, message); + let tag = aead + .encrypt_in_place_detached(&NONCE.into(), AAD, &mut buffer) + .unwrap(); + + assert_eq!(CIPHERTEXT, &buffer); + assert_eq!(TAG, tag.as_slice()); + + aead.decrypt_in_place_detached(&NONCE.into(), AAD, &mut buffer, &tag) + .unwrap(); + + assert_eq!(PLAINTEXT, &buffer); } } diff --git a/ascon/src/lib.rs b/ascon/src/lib.rs index 04a099a..6a6a390 100644 --- a/ascon/src/lib.rs +++ b/ascon/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", @@ -17,9 +18,12 @@ #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "alloc")] +#[cfg(feature = "aead")] pub mod aead; +#[cfg(feature = "aead")] +pub use crate::aead::AsconAead; + use core::convert::TryInto; /// Size of an Ascon key in bytes.