diff --git a/.github/workflows/grain-128aeadv2.yml b/.github/workflows/grain-128aeadv2.yml new file mode 100644 index 00000000..0f18450b --- /dev/null +++ b/.github/workflows/grain-128aeadv2.yml @@ -0,0 +1,60 @@ +name: grain-128aeadv2 + +on: + pull_request: + paths: + - ".github/workflows/grain-128aeadv2.yml" + - "grain-128aeadv2/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: grain-128aeadv2 + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 + - stable + target: + - armv7a-none-eabi + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo test --release --no-default-features --lib + - run: cargo test --release + - run: cargo test --release --all-features + - run: cargo test --release --no-default-features -F 'arrayvec' \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bd0e8084..40e268a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "assert_no_alloc" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "belt-block" version = "0.2.0-rc.2" @@ -136,6 +148,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.9.0" @@ -300,6 +327,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.3.1" @@ -321,6 +370,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "grain-128aeadv2" +version = "0.1.2" +dependencies = [ + "aead", + "assert_no_alloc", + "num-traits", + "proptest", + "zeroize", +] + [[package]] name = "hex-literal" version = "1.1.0" @@ -352,6 +412,21 @@ version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "ocb3" version = "0.2.0-rc.2" @@ -367,6 +442,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -405,6 +486,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -414,6 +504,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.40" @@ -423,12 +538,81 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + [[package]] name = "rand_core" version = "0.10.0-rc-2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "104a23e4e8b77312a823b6b5613edbac78397e2f34320bc7ac4277013ec4478e" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "subtle" version = "2.6.1" @@ -446,12 +630,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -468,6 +671,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" @@ -477,6 +689,30 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -562,6 +798,26 @@ dependencies = [ "hex-literal", ] +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.2" diff --git a/Cargo.toml b/Cargo.toml index d5fdeb67..f22c0e58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "chacha20poly1305", "deoxys", "eax", + "grain-128aeadv2", "ocb3", "xaes-256-gcm", ] diff --git a/README.md b/README.md index 9dbe843d..2cdcaad3 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,10 @@ crate. | [`chacha20poly1305`] | [(X)ChaCha20Poly1305] | [![crates.io](https://img.shields.io/crates/v/chacha20poly1305.svg)](https://crates.io/crates/chacha20poly1305) | [![Documentation](https://docs.rs/chacha20poly1305/badge.svg)](https://docs.rs/chacha20poly1305) | 1.85 | | [`deoxys`] | [Deoxys-I/II] | [![crates.io](https://img.shields.io/crates/v/deoxys.svg)](https://crates.io/crates/deoxys) | [![Documentation](https://docs.rs/deoxys/badge.svg)](https://docs.rs/deoxys) | 1.85 | | [`eax`] | [EAX] | [![crates.io](https://img.shields.io/crates/v/eax.svg)](https://crates.io/crates/eax) | [![Documentation](https://docs.rs/eax/badge.svg)](https://docs.rs/eax) | 1.85 | +| [`grain-128aeadv2`] | [Grain-128AEADv2] | [![crates.io](https://img.shields.io/crates/v/grain-128aeadv2.svg)](https://crates.io/crates/grain-128aeadv2) | [![Documentation](https://docs.rs/grain-128aeadv2/badge.svg)](https://docs.rs/grain-128aeadv2) | 1.85 | | [`mgm`] | [MGM] | [![crates.io](https://img.shields.io/crates/v/mgm.svg)](https://crates.io/crates/mgm) | [![Documentation](https://docs.rs/mgm/badge.svg)](https://docs.rs/mgm) | 1.85 | + ## License All crates licensed under either of @@ -69,6 +71,7 @@ dual licensed as above, without any additional terms or conditions. [`chacha20poly1305`]: https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305 [`deoxys`]: https://github.com/RustCrypto/AEADs/tree/master/deoxys [`eax`]: https://github.com/RustCrypto/AEADs/tree/master/eax +[`grain-128aeadv2`]: https://github.com/RustCrypto/AEADs/tree/master/grain-128aeadv2 [`mgm`]: https://github.com/RustCrypto/AEADs/tree/master/mgm [//]: # (algorithms) @@ -81,5 +84,6 @@ dual licensed as above, without any additional terms or conditions. [CCM]: https://en.wikipedia.org/wiki/CCM_mode [Deoxys-I/II]: https://sites.google.com/view/deoxyscipher [EAX]: https://en.wikipedia.org/wiki/EAX_mode +[Grain-128AEADv2]: https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/grain-128aead-spec-final.pdf [MGM]: https://eprint.iacr.org/2019/123.pdf [(X)ChaCha20Poly1305]: https://tools.ietf.org/html/rfc8439 diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 9193b9df..170864a8 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -20,6 +20,7 @@ ascon-aead128 = { path = "../ascon-aead128/" } chacha20poly1305 = { path = "../chacha20poly1305/" } deoxys = { path = "../deoxys/" } eax = { path = "../eax/" } +grain-128aeadv2 = { path = "../grain-128aeadv2/" } [target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies] criterion-cycles-per-byte = "0.4.0" @@ -53,3 +54,8 @@ harness = false name = "eax" path = "src/eax.rs" harness = false + +[[bench]] +name = "grain-128" +path = "src/grain-128.rs" +harness = false diff --git a/benches/src/grain-128aeadv2.rs b/benches/src/grain-128aeadv2.rs new file mode 100644 index 00000000..6e20362c --- /dev/null +++ b/benches/src/grain-128aeadv2.rs @@ -0,0 +1,56 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; + +use grain_128aeadv2::Grain128; +use grain_128aeadv2::aead::{AeadInOut, KeyInit}; + +const KB: usize = 1024; + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +type Benchmarker = Criterion; +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +type Benchmarker = Criterion; + +fn bench(name: &str, c: &mut Benchmarker) { + let mut group = c.benchmark_group(name); + let nonce = black_box(Default::default()); + let cipher = black_box(A::new(&Default::default())); + + let mut buf = vec![0u8; 16 * KB]; + for size in [KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB] { + let buf = &mut buf[..size]; + let tag = cipher + .encrypt_inout_detached(&nonce, b"", buf.into()) + .unwrap(); + + group.throughput(Throughput::Bytes(size as u64)); + + group.bench_function(BenchmarkId::new("encrypt-128", size), |b| { + b.iter(|| cipher.encrypt_inout_detached(&nonce, b"", buf.into())) + }); + group.bench_function(BenchmarkId::new("decrypt-128", size), |b| { + b.iter(|| cipher.decrypt_inout_detached(&nonce, b"", buf.into(), &tag)) + }); + } + + group.finish(); +} + +fn bench_grain128(c: &mut Benchmarker) { + bench::("Grain-128AEADv2", c); +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench_grain128, +); + +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +criterion_group!( + name = benches; + config = Criterion::default().with_measurement(criterion_cycles_per_byte::CyclesPerByte); + targets = bench_grain128, +); + +criterion_main!(benches); diff --git a/grain-128aeadv2/CHANGELOG.md b/grain-128aeadv2/CHANGELOG.md new file mode 100644 index 00000000..37b68e3d --- /dev/null +++ b/grain-128aeadv2/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [0.1.1] - 2025-12-13 + +### Added + +- Support environment without allocator + +### Fixed + +- Documentation examples are now tested by doc.rs + +## [0.1.1] - 2025-12-11 + +### Added + +- Better documentation and README + +### Fixed + +- Allow to use this crate without Rust standard library + +## 0.1.0 +- Initial release diff --git a/grain-128aeadv2/Cargo.toml b/grain-128aeadv2/Cargo.toml new file mode 100644 index 00000000..414d00d1 --- /dev/null +++ b/grain-128aeadv2/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "grain-128aeadv2" +version = "0.1.2" +edition = "2024" +description = "Implementation of Grain-128AEADv2 stream cipher." +rust-version = "1.85" +license = "Apache-2.0 OR MIT" +readme = "README.md" +documentation = "https://docs.rs/grain-128aeadv2" +repository = "https://github.com/acmo0/grain-128aeadv2/" +keywords = ["AEAD", "grain-128", "encryption"] +categories = ["cryptography", "no-std"] +authors = ["Grégoire Frémion + +# Grain-128AEADv2 + +![Crates.io Total Downloads](https://img.shields.io/crates/d/grain-128aeadv2) +![Crates.io Version](https://img.shields.io/crates/v/grain-128aeadv2) + +Efficient pure Rust implementation of Grain-128AEADv2. + +Please see installation details and doc on [crates.io](https://crates.io/crates/grain-128aeadv2). + + + +*** + +Pure Rust implementation of Grain-128AEADv2, a lightweight stream cipher. + +**It works without standard library and even without allocator if your disable the `vec` default feature** + +## Security Notes + +> [!CAUTION] +> No security audits of this crate have ever been performed. +> **USE AT YOUR OWN RISK!** + +## Minimum Supported Rust Version + +This crate requires **Rust 1.85** at a minimum. + +## Quickstart + +With randomly sampled keys and nonces (requires `getrandom` feature): + +```rust +use grain_128aeadv2::{Grain128, aead::{Aead, AeadCore, KeyInit}}; + +let key = Grain128::generate_key().expect("Unable to generate key"); +let cipher = Grain128::new(&key); + +// A nonce must be USED ONLY ONCE ! +let nonce = Grain128::generate_nonce().expect("Unable to generate nonce"); +let (ciphertext, tag) = cipher.encrypt_aead( + &nonce, + b"Some additional data", + b"this is a secret message" +); + +let plaintext = cipher.decrypt_aead( + &nonce, + b"Some additional data", + &ciphertext, + &tag +).expect("Tag verification failed"); + +assert_eq!(&plaintext, b"this is a secret message"); +``` + +In-place encryption (requires `alloc` feature) : + +```rust +use grain_128aeadv2::{ + Grain128, Key, Nonce, + aead::{AeadCore, AeadInOut, KeyInit, arrayvec::ArrayVec} +}; + +let key = Grain128::generate_key().expect("Unable to generate key"); +let cipher = Grain128::new(&key); + +// A nonce must be USED ONLY ONCE ! +let nonce = Grain128::generate_nonce().expect("Unable to generate nonce"); +// Take care : 8 bytes overhead to store the tag +let mut buffer: Vec = vec![]; +buffer.extend_from_slice(b"a secret message"); + +// Perform in place encryption inside 'buffer' +cipher.encrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Unable to encrypt"); + +// Perform in place decryption +cipher.decrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Tag verification failed"); + +assert_eq!(&buffer, b"a secret message"); +``` + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (general links) + +[1]: https://en.wikipedia.org/wiki/Authenticated_encryption +[2]: https://doi.org/10.6028/NIST.SP.800-232 \ No newline at end of file diff --git a/grain-128aeadv2/src/fsr.rs b/grain-128aeadv2/src/fsr.rs new file mode 100644 index 00000000..1390a691 --- /dev/null +++ b/grain-128aeadv2/src/fsr.rs @@ -0,0 +1,208 @@ +use crate::utils::{get_2bytes_at_bit, get_4bytes_at_bit}; + +use crate::traits::{Accumulator, Xfsr}; + +/// Core structure of the 128bits grain LFSR +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] +pub struct GrainLfsr { + pub(crate) state: u128, +} + +impl GrainLfsr { + /// Return a new Grain LFSR initialized with the given state + pub fn new(initial_state: u128) -> GrainLfsr { + GrainLfsr { + state: initial_state, + } + } +} + +/// Clock sixteen bits at once to speed up +/// keystream & authentication stream generation. +impl Xfsr for GrainLfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value; + } + /// Update the grain's LFSR state according to the spec : + /// - compute s' = s0 + s7 + s38 + s70 + s81 + s96 + /// - set the new state : s127 = s' + /// - right shift the remaining bits by one + /// + /// (i.e s126 = s127, ..., s0 = s1) + /// **The update is done on 16 bits directly (i.e 16 clocks)** + fn feedback_function(&self) -> u128 { + (get_2bytes_at_bit(&self.state, 0) + ^ get_2bytes_at_bit(&self.state, 7) + ^ get_2bytes_at_bit(&self.state, 38) + ^ get_2bytes_at_bit(&self.state, 70) + ^ get_2bytes_at_bit(&self.state, 81) + ^ get_2bytes_at_bit(&self.state, 96)) as u128 + } +} + +/// Clock thirty-two bits at once to speed up +/// keystream & authentication stream generation. +impl Xfsr for GrainLfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value; + } + + /// Update the grain's LFSR state according to the spec : + /// - compute s' = s0 + s7 + s38 + s70 + s81 + s96 + /// - set the new state : s127 = s' + /// - right shift the remaining bits by one + /// + /// (i.e s126 = s127, ..., s0 = s1) + /// **The update is done on 32 bits directly (i.e 32 clocks)** + fn feedback_function(&self) -> u128 { + (get_4bytes_at_bit(&self.state, 0) + ^ get_4bytes_at_bit(&self.state, 7) + ^ get_4bytes_at_bit(&self.state, 38) + ^ get_4bytes_at_bit(&self.state, 70) + ^ get_4bytes_at_bit(&self.state, 81) + ^ get_4bytes_at_bit(&self.state, 96)) as u128 + } +} + +/// Core structure for the Grain128-AEADv2 NFSR +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] +pub struct GrainNfsr { + pub(crate) state: u128, +} + +impl GrainNfsr { + /// Return a new Grain LFSR initialized with the given state + pub(crate) fn new(initial_state: u128) -> Self { + GrainNfsr { + state: initial_state, + } + } + + pub(crate) fn xor_last_2bytes(&mut self, bytes: u16) { + self.state ^= (bytes as u128) << 112; + } + + pub(crate) fn xor_last_4bytes(&mut self, bytes: u32) { + self.state ^= (bytes as u128) << 96; + } +} + +impl Xfsr for GrainNfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value; + } + + /// Update the grain's NFSR state accord to the spec + /// EXCEPT that the feedback bit is not xored with + /// the bit from the grain LFSR output + fn feedback_function(&self) -> u128 { + let output = (get_2bytes_at_bit(&self.state, 0) + ^ get_2bytes_at_bit(&self.state, 26) + ^ get_2bytes_at_bit(&self.state, 56) + ^ get_2bytes_at_bit(&self.state, 91) + ^ get_2bytes_at_bit(&self.state, 96) + ^ get_2bytes_at_bit(&self.state, 3) & get_2bytes_at_bit(&self.state, 67) + ^ get_2bytes_at_bit(&self.state, 11) & get_2bytes_at_bit(&self.state, 13) + ^ get_2bytes_at_bit(&self.state, 17) & get_2bytes_at_bit(&self.state, 18) + ^ get_2bytes_at_bit(&self.state, 27) & get_2bytes_at_bit(&self.state, 59) + ^ get_2bytes_at_bit(&self.state, 40) & get_2bytes_at_bit(&self.state, 48) + ^ get_2bytes_at_bit(&self.state, 61) & get_2bytes_at_bit(&self.state, 65) + ^ get_2bytes_at_bit(&self.state, 68) & get_2bytes_at_bit(&self.state, 84) + ^ (get_2bytes_at_bit(&self.state, 22) + & get_2bytes_at_bit(&self.state, 24) + & get_2bytes_at_bit(&self.state, 25)) + ^ (get_2bytes_at_bit(&self.state, 70) + & get_2bytes_at_bit(&self.state, 78) + & get_2bytes_at_bit(&self.state, 82)) + ^ (get_2bytes_at_bit(&self.state, 88) + & get_2bytes_at_bit(&self.state, 92) + & get_2bytes_at_bit(&self.state, 93) + & get_2bytes_at_bit(&self.state, 95))) as u128; + + debug_assert!(output < (1u128 << 16)); + output + } +} + +impl Xfsr for GrainNfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value; + } + + /// Update the grain's NFSR state accord to the spec + /// EXCEPT that the feedback bit is not xored with + /// the bit from the grain LFSR output + fn feedback_function(&self) -> u128 { + let output = (get_4bytes_at_bit(&self.state, 0) + ^ get_4bytes_at_bit(&self.state, 26) + ^ get_4bytes_at_bit(&self.state, 56) + ^ get_4bytes_at_bit(&self.state, 91) + ^ get_4bytes_at_bit(&self.state, 96) + ^ get_4bytes_at_bit(&self.state, 3) & get_4bytes_at_bit(&self.state, 67) + ^ get_4bytes_at_bit(&self.state, 11) & get_4bytes_at_bit(&self.state, 13) + ^ get_4bytes_at_bit(&self.state, 17) & get_4bytes_at_bit(&self.state, 18) + ^ get_4bytes_at_bit(&self.state, 27) & get_4bytes_at_bit(&self.state, 59) + ^ get_4bytes_at_bit(&self.state, 40) & get_4bytes_at_bit(&self.state, 48) + ^ get_4bytes_at_bit(&self.state, 61) & get_4bytes_at_bit(&self.state, 65) + ^ get_4bytes_at_bit(&self.state, 68) & get_4bytes_at_bit(&self.state, 84) + ^ (get_4bytes_at_bit(&self.state, 22) + & get_4bytes_at_bit(&self.state, 24) + & get_4bytes_at_bit(&self.state, 25)) + ^ (get_4bytes_at_bit(&self.state, 70) + & get_4bytes_at_bit(&self.state, 78) + & get_4bytes_at_bit(&self.state, 82)) + ^ (get_4bytes_at_bit(&self.state, 88) + & get_4bytes_at_bit(&self.state, 92) + & get_4bytes_at_bit(&self.state, 93) + & get_4bytes_at_bit(&self.state, 95))) as u128; + + debug_assert!(output < (1u128 << 32)); + output + } +} + +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] +pub struct GrainAuthAccumulator { + pub(crate) state: u64, +} + +impl GrainAuthAccumulator { + pub(crate) fn new() -> GrainAuthAccumulator { + GrainAuthAccumulator { state: 0u64 } + } +} + +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] +pub struct GrainAuthRegister { + pub(crate) state: u64, +} + +impl Accumulator for GrainAuthRegister { + fn accumulate(&mut self, new: u8) -> u8 { + let output = self.state & 1; + self.state >>= 1; + self.state |= (new as u64) << 63; + + output as u8 + } + + fn new() -> GrainAuthRegister { + GrainAuthRegister { state: 0u64 } + } +} diff --git a/grain-128aeadv2/src/grain_core.rs b/grain-128aeadv2/src/grain_core.rs new file mode 100644 index 00000000..701c3a9a --- /dev/null +++ b/grain-128aeadv2/src/grain_core.rs @@ -0,0 +1,619 @@ +#[cfg(feature = "vec")] +use alloc::vec::Vec; + +use aead::{Error, consts::U2, inout::InOutBuf}; + +use crate::fsr::{GrainAuthAccumulator, GrainAuthRegister, GrainLfsr, GrainNfsr}; +use crate::traits::{Accumulator, Xfsr}; +use crate::utils::{self, get_2bytes_at_bit, get_4bytes_at_bit}; + +pub(crate) struct GrainCore { + lfsr: GrainLfsr, + nfsr: GrainNfsr, + auth_accumulator: GrainAuthAccumulator, + auth_register: GrainAuthRegister, +} + +// Macro to generate the pre-output of grain according +// according to grain-128AEADv2 spec. It allows to clock +// the cipher 8 times, 16 times, 24 times or 32 times +// at once, enhancing performances. +// function_name : the name of the generated function +// byte_getter_function : function the retrieve n bytes from a u128 +// updater_function : the function to update the NFSR (i.e how many bytes to xor with the NFSR state) +// output_type : the type outputted by the generated function (used also to know how to clock the xFSRs) +macro_rules! clock { + ($function_name:tt, $byte_getter_function:tt, $updater_function: tt, $output_type: ty) => { + fn $function_name(&mut self) -> $output_type { + // Get the 9 taps from LFSR/NFSR and compute the + // "pre-output" as defined in grain-128AEADv2 spec + let x0 = $byte_getter_function(&self.nfsr.state, 12); + let x1 = $byte_getter_function(&self.lfsr.state, 8); + let x2 = $byte_getter_function(&self.lfsr.state, 13); + let x3 = $byte_getter_function(&self.lfsr.state, 20); + let x4 = $byte_getter_function(&self.nfsr.state, 95); + let x5 = $byte_getter_function(&self.lfsr.state, 42); + let x6 = $byte_getter_function(&self.lfsr.state, 60); + let x7 = $byte_getter_function(&self.lfsr.state, 79); + let x8 = $byte_getter_function(&self.lfsr.state, 94); + + let output = (x0 & x1) + ^ (x2 & x3) + ^ (x4 & x5) + ^ (x6 & x7) + ^ (x0 & x4 & x8) + ^ $byte_getter_function(&self.lfsr.state, 93) + ^ $byte_getter_function(&self.nfsr.state, 2) + ^ $byte_getter_function(&self.nfsr.state, 15) + ^ $byte_getter_function(&self.nfsr.state, 36) + ^ $byte_getter_function(&self.nfsr.state, 45) + ^ $byte_getter_function(&self.nfsr.state, 64) + ^ $byte_getter_function(&self.nfsr.state, 73) + ^ $byte_getter_function(&self.nfsr.state, 89); + + // Clock/update the xFSRs + let lfsr_output: $output_type = self.lfsr.clock(); + let _: $output_type = self.nfsr.clock(); + self.nfsr.$updater_function(lfsr_output); + + output + } + }; +} + +impl GrainCore { + /// Init a new instance of Grain-128AEADv2 as specified + /// in the NIST spec in Section 2.2. + pub(crate) fn new(key: u128, iv: u128) -> Self { + // Ensure that the size of the IV doesn't exceed 12 bytes. + if iv >= (1u128 << 96) { + panic!("Unable to init Grain-128AEADv2, IV is too big (must be 12 bytes)"); + } + + // Init/load the keys into grain cipher + let mut cipher = GrainCore { + lfsr: GrainLfsr::new((0x7fffffff << 96) | iv), + nfsr: GrainNfsr::new(key), + auth_accumulator: GrainAuthAccumulator::new(), + auth_register: GrainAuthRegister::new(), + }; + + // Clock 320 times and re-input the feedback to both LFSR and NFSR + for _i in 0..10 { + let fb: u128 = cipher.clock_u32() as u128; + cipher.lfsr.state ^= fb << 96; + cipher.nfsr.state ^= fb << 96; + } + + // Clock 64 times and re-input the feedback to both LFSR and NFSR + // + re-introduce key + for i in [0, 32] { + let fb = cipher.clock_u32(); + cipher.lfsr.state ^= ((fb ^ get_4bytes_at_bit(&key, i + 64)) as u128) << 96; + cipher.nfsr.state ^= ((fb ^ get_4bytes_at_bit(&key, i)) as u128) << 96; + } + + // Init the accumulator + let fb1 = cipher.clock_u32() as u64; + let fb2 = cipher.clock_u32() as u64; + cipher.auth_accumulator.state = (fb2 << 32) | fb1; + + // Init the register + let fb1 = cipher.clock_u32() as u64; + let fb2 = cipher.clock_u32() as u64; + cipher.auth_register.state = (fb2 << 32) | fb1; + + cipher + } + + /* + ######################################################### + # Code related to clocking the cipher (by 2 ou 4 bytes) # + ######################################################### + */ + // Use macro to generate the pre-output computation + // function for 16 and 32 bits at once + clock!(clock_u16, get_2bytes_at_bit, xor_last_2bytes, u16); + clock!(clock_u32, get_4bytes_at_bit, xor_last_4bytes, u32); + + /// Update grain-128AEADv2 accumulator according to + /// NIST spec. in Section 2.3 + #[inline(always)] + fn update_auth_accumulator(&mut self) { + self.auth_accumulator.state ^= self.auth_register.state; + } + + /* + ############################################################### + # Code related to encryption/authentication stream generation # + ############################################################### + */ + /// Clock 32 times the cipher and extract the streams for + /// authentication and encryption/decryption according to + /// NIST spec. in Section 2.3. + #[inline(always)] + fn get_stream16(&mut self) -> (u16, u16) { + let keystream = self.clock_u32(); + utils::deinterleave32(&keystream) + } + + /// Clock 16 times the cipher and extract the streams for + /// authentication and encryption/decryption according to + /// NIST spec. in Section 2.3. + #[inline(always)] + fn get_stream8(&mut self) -> (u8, u8) { + let keystream = self.clock_u16(); + utils::deinterleave16(&keystream) + } + + /* + ################################## + # Code related to authentication # + ################################## + */ + /// Authenticate a single byte of plaintext according to + /// NIST spec. in Section 2.3. + fn auth_2bytes(&mut self, auth_stream: &u16, data: &[u8]) { + // Update the auth register + for (i, byte) in data.iter().enumerate().take(2) { + for j in 0..8 { + if (byte >> j) & 1 == 1u8 { + self.update_auth_accumulator() + } + self.auth_register + .accumulate(((auth_stream >> ((i << 3) + j)) & 1) as u8); + } + } + } + + /// Authenticate additional data according + /// to Grain-128AEADv2 spec. in Section 2.5 + fn auth_additional_data(&mut self, authenticated_data: &[u8]) { + // Init the output with the associated data encoded length + let (size, encoded_len_arr) = utils::len_encode(authenticated_data.len()); + + let encoded_len = &encoded_len_arr[0..size]; + + // Authenticate the additional data len representation + //let (blocks, last_block) = encoded_len.as_chunks::<2>(); + for block in encoded_len.chunks(2) { + match *block { + [b1, b2] => { + let (_, auth_stream) = self.get_stream16(); + self.auth_2bytes(&auth_stream, &[b1, b2]); + } + [b] => { + let (_, auth_stream) = self.get_stream8(); + self.auth_byte(&auth_stream, &b); + } + _ => {} + } + } + + // Authenticate additional data + for block in authenticated_data.chunks(2) { + match *block { + [b1, b2] => { + let (_, auth_stream) = self.get_stream16(); + self.auth_2bytes(&auth_stream, &[b1, b2]); + } + [b] => { + let (_, auth_stream) = self.get_stream8(); + self.auth_byte(&auth_stream, &b); + } + _ => {} + } + } + } + + /* + ##################################################################### + # Code related to encryption/decryption/authentication by 1/2 bytes # + ##################################################################### + */ + /// Perform the encryption and authentication of 2 bytes + /// of data according to NIST spec. in Section 2.3. + fn encrypt_and_auth_2bytes(&mut self, data: &[u8]) -> [u8; 2] { + let (encrypt_stream, auth_stream) = self.get_stream16(); + + // Auth the plaintext byte + self.auth_2bytes(&auth_stream, data); + + // Encrypt the plaintext + [ + data[0] ^ ((encrypt_stream & 0xff) as u8), + data[1] ^ ((encrypt_stream >> 8) as u8), + ] + } + + /// Perform the decryption and authentication of 2 bytes + /// of data according to NIST spec. in Section 2.3. + fn decrypt_and_auth_2bytes(&mut self, data: &[u8]) -> [u8; 2] { + let (encrypt_stream, auth_stream) = self.get_stream16(); + + let output = [ + data[0] ^ ((encrypt_stream & 0xff) as u8), + data[1] ^ ((encrypt_stream >> 8) as u8), + ]; + + // Auth the plaintext byte + self.auth_2bytes(&auth_stream, &output); + + output + } + + /// Perform the encryption and authentication of a single + /// byte of data according to NIST spec. in Section 2.3. + fn encrypt_and_auth_byte(&mut self, data: &u8) -> u8 { + let (encrypt_stream, auth_stream) = self.get_stream8(); + self.auth_byte(&auth_stream, data); + + data ^ encrypt_stream + } + + /// Perform the decryption and authentication of a single + /// byte of data according to NIST spec. in Section 2.3. + fn decrypt_and_auth_byte(&mut self, data: &u8) -> u8 { + let (encrypt_stream, auth_stream) = self.get_stream8(); + + let output = data ^ encrypt_stream; + + self.auth_byte(&auth_stream, &output); + + output + } + + /// Authenticate a single byte of plaintext according to + /// NIST spec. in Section 2.3. + fn auth_byte(&mut self, auth_stream: &u8, data: &u8) { + // Update the auth register + for i in 0..8 { + if (data >> i) & 1 == 1u8 { + self.update_auth_accumulator() + } + self.auth_register.accumulate((auth_stream >> i) & 1); + } + } + + /* + ################################################################# + # Public functions to encrypt/decrypt without RustCrypto traits # + ################################################################# + */ + /// Encrypts and authenticate a given plaintext, and (potential) additional + /// authenticated data according to the NIST spec. in Section 2.6.1. It returns + /// the ciphertext and the authentication tag. + #[cfg(feature = "vec")] + pub(crate) fn encrypt_aead( + &mut self, + authenticated_data: &[u8], + data: &[u8], + ) -> (Vec, [u8; 8]) { + let mut output: Vec = Vec::with_capacity(data.len()); + + // Auth additional data + self.auth_additional_data(authenticated_data); + + // Split plaintext by block of two bytes and encrypt it + for block in data.chunks(2) { + match *block { + [b1, b2] => { + output.extend(self.encrypt_and_auth_2bytes(&[b1, b2])); + } + [b] => { + output.push(self.encrypt_and_auth_2bytes(&[b, 1u8])[0]); + } + _ => { + panic!("Matched none !"); + } + } + } + + if (data.len() & 1) == 0 { + self.encrypt_and_auth_byte(&1u8); + } + + (output, self.auth_accumulator.state.to_le_bytes()) + } + + /// Decrypts and authenticate a given ciphertext, and (potential) additional + /// authenticated data according to the NIST spec. in Section 2.6.1. + /// It returns the plaintext if the given tag is correct, otherwise it fails. + #[cfg(feature = "vec")] + pub(crate) fn decrypt_aead( + &mut self, + authenticated_data: &[u8], + data: &[u8], + tag: &[u8], + ) -> Result, Error> { + let mut output: Vec = Vec::with_capacity(data.len()); + + // Authenticate data + self.auth_additional_data(authenticated_data); + + for block in data.chunks(2) { + match *block { + [b1, b2] => { + output.extend(self.decrypt_and_auth_2bytes(&[b1, b2])); + } + [b] => { + output.push(self.decrypt_and_auth_byte(&b)); + self.encrypt_and_auth_byte(&1u8); + } + _ => { + panic!("Matched none !"); + } + } + } + + if (data.len() & 1) == 0 { + self.encrypt_and_auth_byte(&1u8); + } + + if self.auth_accumulator.state.to_le_bytes().as_slice() != tag { + return Err(Error); + } + + Ok(output) + } + + /* + ############################################################## + # Public functions to encrypt/decrypt with RustCrypto traits # + ############################################################## + */ + /// Encrypts and authenticate a given plaintext, and (potential) additional + /// authenticated data according to the NIST spec. in Section 2.6.1. The + /// encryption is done in-place to match the RustCrypto traits requirements. + /// It returns only the tag since the encrypted data is stored in the data + /// buffer. + pub(crate) fn encrypt_auth_aead_inout( + &mut self, + authenticated_data: &[u8], + data: InOutBuf<'_, '_, u8>, + ) -> [u8; 8] { + self.auth_additional_data(authenticated_data); + + let (blocks, mut last_block) = data.into_chunks::(); + + for mut block in blocks { + let encrypted = self.encrypt_and_auth_2bytes(block.get_in()); + block.get_out().copy_from_slice(&encrypted); + } + + if !last_block.is_empty() { + let encrypted_byte = self.encrypt_and_auth_2bytes(&[last_block.get_in()[0], 1u8])[0]; + last_block.get_out()[0..1].copy_from_slice(&[encrypted_byte]); + } else { + // Add padding + encrypt/auth + self.encrypt_and_auth_byte(&1u8); + } + + self.auth_accumulator.state.to_le_bytes() + } + + /// Decrypts and authenticate a given ciphertext, and (potential) additional + /// authenticated data according to the NIST spec. in Section 2.6.1. The + /// decryption is done in-place to match the RustCrypto traits requirements. + /// It fails if the given tag doesn't match the computed tag. + pub(crate) fn decrypt_auth_aead_inout( + &mut self, + authenticated_data: &[u8], + data: InOutBuf<'_, '_, u8>, + tag: &[u8], + ) -> Result<(), Error> { + self.auth_additional_data(authenticated_data); + + let (blocks, mut last_block) = data.into_chunks::(); + + for mut block in blocks { + let decrypted = self.decrypt_and_auth_2bytes(block.get_in()); + block.get_out().copy_from_slice(&decrypted); + } + + if !last_block.is_empty() { + let decrypted_byte = self.decrypt_and_auth_byte(&last_block.get_in()[0]); + last_block.get_out()[0..1].copy_from_slice(&[decrypted_byte]); + } + + // Add padding + encrypt/auth + self.encrypt_and_auth_byte(&1u8); + + if tag != self.auth_accumulator.state.to_le_bytes().as_slice() { + Err(Error) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + // Useful function to convert test vectors + // in big endian + bit reverse each byte + #[cfg(feature = "vec")] + fn to_test_vector(test_vec: u128, size: usize) -> u128 { + let mut output = 0u128; + + for i in 0..size { + let byte = (test_vec >> i * 8) & 0xff; + output += byte << ((size - 1) * 8 - (i * 8)); + } + + output + } + + // Performs an initialization and an encryption + // of an all-zero key/nonce with an empty plaintext. + // It checks the LFSR/NFSR/Accumulator/Register states + // and the computed tag according the the tests + // vectors given in the NIST spec. in Section 7. + #[test] + #[cfg(feature = "vec")] + fn test_load_null() { + // Test vectors from Grain-128AEADv2 spec + let lfsr_state = 0x8f395a9421b0963364e2ed30679c8ee1u128; + let nfsr_state = 0x81f7e0c655d035823310c278438dbc20u128; + let acc_state = 0xe89a32b9c0461a6au128; + let reg_state = 0xb199ade7204c6bfeu128; + let tag = 0x7137d5998c2de4a5u128; + + // Init and load keys into the cipher + let mut cipher = GrainCore::new(0, 0); + + assert_eq!(nfsr_state, to_test_vector(cipher.nfsr.state, 16)); + assert_eq!(lfsr_state, to_test_vector(cipher.lfsr.state, 16)); + assert_eq!( + acc_state, + to_test_vector(cipher.auth_accumulator.state.into(), 8) + ); + assert_eq!( + reg_state, + to_test_vector(cipher.auth_register.state.into(), 8) + ); + + cipher.encrypt_aead(&[], &[]); + assert_eq!(tag, to_test_vector(cipher.auth_accumulator.state.into(), 8)); + } + + // Performs an initialization and an encryption + // of given key/nonce/plaintext/auth data set + // It checks the LFSR/NFSR/Accumulator/Register states + // and the computed ciphertext/tag according the the tests + // vectors given in the NIST spec. in Section 7. + #[test] + #[cfg(feature = "vec")] + fn test_load_non_null() { + // Test vectors from Grain-128AEADv2 spec + let nfsr_state = 0xb3c2e1b1eec1f08c2d6eae957f6af9d0u128; + let lfsr_state = 0x0e1f950d45e05087c4cd63fd00eab310u128; + let acc_state = 0xc77202737ae7c7eeu128; + let reg_state = 0x33126dd7a21b9073u128; + let enc_state = 0x96d1bda7ae11f0bau128; + let tag_state = 0x22b0c12039a20e28u128; + + // Plaintext / authenticated data from test vectors + let ad = (0x0001020304050607u64).to_be_bytes(); + let pt = (0x0001020304050607u64).to_be_bytes(); + + // Init and load keys into the cipher + let mut cipher = GrainCore::new( + to_test_vector(0x000102030405060708090a0b0c0d0e0fu128, 16), + to_test_vector(0x000102030405060708090a0bu128, 12), + ); + + assert_eq!(nfsr_state, to_test_vector(cipher.nfsr.state, 16)); + assert_eq!(lfsr_state, to_test_vector(cipher.lfsr.state, 16)); + assert_eq!( + acc_state, + to_test_vector(cipher.auth_accumulator.state.into(), 8) + ); + assert_eq!( + reg_state, + to_test_vector(cipher.auth_register.state.into(), 8) + ); + + let (encrypted, tag) = cipher.encrypt_aead(&ad, &pt); + + let ct: [u8; 8] = encrypted.try_into().unwrap(); + + assert_eq!(enc_state, to_test_vector(u64::from_le_bytes(ct) as u128, 8)); + assert_eq!( + tag_state, + to_test_vector(u64::from_le_bytes(tag) as u128, 8) + ); + } + + // Tries to init, encrypt and decrypt without any modification + // of the ciphertext. It checks then that we indeed retrieve + // the right decrypted plaintext. + #[test] + #[cfg(feature = "vec")] + fn test_encrypt_decrypt() { + // Plaintext / authenticated data from test vectors + let ad = (0x0001020304050607u64).to_be_bytes(); + let pt = (0x0001020304050607u64).to_be_bytes(); + + // Init and load keys into the cipher + let mut cipher = GrainCore::new(0, 0); + + let (encrypted, tag) = cipher.encrypt_aead(&ad, &pt); + + // Init and load keys into the cipher + cipher = GrainCore::new(0, 0); + + let decrypted = cipher + .decrypt_aead(&ad, &encrypted, &tag) + .expect("Unable to decrypt"); + + assert_eq!(decrypted, pt); + } + + // Tries to init, encrypt and decrypt but while modifying + // the ciphertext. This should fail because the tag is + // no longer valid. + #[test] + #[should_panic(expected = "Unable to decrypt")] + #[cfg(feature = "vec")] + fn test_encrypt_decrypt_wrong_ct() { + // Plaintext / authenticated data from test vectors + let ad = (0x0001020304050607u64).to_be_bytes(); + let pt = (0x0001020304050607u64).to_be_bytes(); + + // Init and load keys into the cipher + let mut cipher = GrainCore::new(0, 0); + + let (mut encrypted, tag) = cipher.encrypt_aead(&ad, &pt); + + // Init and load keys into the cipher + cipher = GrainCore::new(0, 0); + + encrypted[0] = 0; + + let decrypted = cipher + .decrypt_aead(&ad, &encrypted, &tag) + .expect("Unable to decrypt"); + + assert_eq!(decrypted, pt); + } + + // Tries to init, encrypt and decrypt but while modifying + // the tag. It should fail because the tag is not valid + // anymore. + #[test] + #[should_panic(expected = "Unable to decrypt")] + #[cfg(feature = "vec")] + fn test_encrypt_decrypt_wrong_ad() { + // Plaintext / authenticated data from test vectors + let ad = (0x0001020304050607u64).to_be_bytes(); + let ad2 = (0x0101020304050607u64).to_be_bytes(); + let pt = (0x0001020304050607u64).to_be_bytes(); + + // Init and load keys into the cipher + let mut cipher = GrainCore::new(0, 0); + + let (encrypted, tag) = cipher.encrypt_aead(&ad, &pt); + + // Init and load keys into the cipher + cipher = GrainCore::new(0, 0); + + let decrypted = cipher + .decrypt_aead(&ad2, &encrypted, &tag) + .expect("Unable to decrypt"); + + assert_eq!(decrypted, pt); + } + + // Tries to init a new Grain128-AEADv2 cipher with an + // nonce that is too big acc. to the specs + proptest! { + #[test] + #[should_panic(expected = "Unable to init Grain-128AEADv2, IV is too big (must be 12 bytes)")] + fn test_init_too_big(iv in (1 << 96)..(u128::MAX)) { + GrainCore::new(0, iv); + } + } +} diff --git a/grain-128aeadv2/src/lib.rs b/grain-128aeadv2/src/lib.rs new file mode 100644 index 00000000..07774269 --- /dev/null +++ b/grain-128aeadv2/src/lib.rs @@ -0,0 +1,315 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![deny(unsafe_code)] +#![warn(missing_docs)] + +//! ## Quickstart +//! +//! Basic usage (with default `vec` feature): +//! +//! If you don't want to use the module to generate the keys : +#![cfg_attr(feature = "vec", doc = "```")] +#![cfg_attr(not(feature = "vec"), doc = "```ignore")] +//! use grain_128aeadv2::{ +//! Grain128, Key, Nonce, +//! aead::{KeyInit, AeadCore} +//! }; +//! +//! // PLEASE use a RANDOM key/nonce (don't copy-paste this...) +//! let key = [12, 33, 91, 88, 1, 0, 132, 11, 231, 28, 1, 3, 5, 1, 5, 1]; +//! let nonce = [91, 88, 1, 0, 132, 11, 231, 1, 23, 32, 22, 33]; +//! let cipher = Grain128::new(&key.into()); +//! +//! let (ciphertext, tag) = cipher.encrypt_aead( +//! &nonce.into(), +//! b"Some additional data", +//! b"this is a secret message" +//! ); +//! +//! let plaintext = cipher.decrypt_aead( +//! &nonce.into(), +//! b"Some additional data", +//! &ciphertext, +//! &tag +//! ).expect("Tag verification failed"); +//! +//! assert_eq!(&plaintext, b"this is a secret message"); +//! +//! ``` +//! With randomly sampled keys and nonces (requires `getrandom` feature): +//! +#![cfg_attr(all(feature = "vec", feature = "getrandom"), doc = "```")] +#![cfg_attr(not(all(feature = "vec", feature = "getrandom")), doc = "```ignore")] +//! use grain_128aeadv2::{Grain128, aead::{Aead, AeadCore, KeyInit}}; +//! +//! let key = Grain128::generate_key().expect("Unable to generate key"); +//! let cipher = Grain128::new(&key); +//! +//! // A nonce must be USED ONLY ONCE ! +//! let nonce = Grain128::generate_nonce().expect("Unable to generate nonce"); +//! let (ciphertext, tag) = cipher.encrypt_aead( +//! &nonce, +//! b"Some additional data", +//! b"this is a secret message" +//! ); +//! +//! let plaintext = cipher.decrypt_aead( +//! &nonce, +//! b"Some additional data", +//! &ciphertext, +//! &tag +//! ).expect("Tag verification failed"); +//! +//! assert_eq!(&plaintext, b"this is a secret message"); +//! ``` +//! +//! ## In-place encryption (`arrayvec` or `alloc`) +//! +//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`] +//! methods accept any type that impls the [`aead::Buffer`] trait which +//! contains the plaintext for encryption or ciphertext for decryption. +//! +//! Enabling the `arrayvec` feature of this crate will provide an impl of +//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as +//! [`aead::arrayvec::ArrayVec`]). +//! Enabling the `alloc` feature of this crate will provide an impl of +//! [`aead::Buffer`] for `Vec`. +//! +//! It can then be passed as the `buffer` parameter to the in-place encrypt +//! and decrypt methods: +//! +#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")] +#![cfg_attr( + not(all(feature = "getrandom", feature = "arrayvec")), + doc = "```ignore" +)] +//! use grain_128aeadv2::{ +//! Grain128, Key, Nonce, +//! aead::{AeadCore, AeadInOut, KeyInit, arrayvec::ArrayVec} +//! }; +//! +//! let key = Grain128::generate_key().expect("Unable to generate key"); +//! let cipher = Grain128::new(&key); +//! +//! // A nonce must be USED ONLY ONCE ! +//! let nonce = Grain128::generate_nonce().expect("Unable to generate nonce"); +//! // Take care : 8 bytes overhead to store the tag +//! let mut buffer: ArrayVec = ArrayVec::new(); +//! buffer.try_extend_from_slice(b"a secret message").unwrap(); +//! +//! // Perform in place encryption inside 'buffer' +//! cipher.encrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Unable to encrypt"); +//! +//! // Perform in place decryption +//! cipher.decrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Tag verification failed"); +//! +//! assert_eq!(buffer.as_ref(), b"a secret message"); +//! ``` +#![cfg_attr(all(feature = "getrandom", feature = "alloc"), doc = "```")] +#![cfg_attr(not(all(feature = "getrandom", feature = "alloc")), doc = "```ignore")] +//! use grain_128aeadv2::{ +//! Grain128, Key, Nonce, +//! aead::{AeadCore, AeadInOut, KeyInit, arrayvec::ArrayVec} +//! }; +//! +//! let key = Grain128::generate_key().expect("Unable to generate key"); +//! let cipher = Grain128::new(&key); +//! +//! // A nonce must be USED ONLY ONCE ! +//! let nonce = Grain128::generate_nonce().expect("Unable to generate nonce"); +//! // Take care : 8 bytes overhead to store the tag +//! let mut buffer: Vec = vec![]; +//! buffer.extend_from_slice(b"a secret message"); +//! +//! // Perform in place encryption inside 'buffer' +//! cipher.encrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Unable to encrypt"); +//! +//! // Perform in place decryption +//! cipher.decrypt_in_place(&nonce, b"Some AD", &mut buffer).expect("Tag verification failed"); +//! +//! assert_eq!(&buffer, b"a secret message"); +//! ``` + +#[cfg(feature = "vec")] +extern crate alloc; +#[cfg(feature = "vec")] +use alloc::vec::Vec; + +#[cfg(feature = "zeroize")] +pub use zeroize; + +pub use aead::{ + self, AeadCore, AeadInOut, Buffer, Error, Key, KeyInit, KeySizeUser, Nonce, Tag, TagPosition, + array::Array, + consts::{U1, U8, U12, U16}, + inout::InOutBuf, +}; + +mod fsr; +mod grain_core; +mod traits; +mod utils; + +use grain_core::GrainCore; + +/// Grain-128AEADv2 cipher. +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] +pub struct Grain128 { + pub(crate) key: u128, +} + +// Implement to define key/iv size +impl KeySizeUser for Grain128 { + type KeySize = U16; +} + +impl KeyInit for Grain128 { + fn new(key: &Key) -> Self { + let mut key_int: u128 = 0; + for i in 0..key.len() { + key_int |= (key[i] as u128) << (i * 8); + } + + Grain128 { key: key_int } + } +} + +// Implement to define Nonce/Tag size and +// where the tag is stored inside the buffer +impl AeadCore for Grain128 { + type NonceSize = U12; + type TagSize = U8; + const TAG_POSITION: TagPosition = TagPosition::Postfix; +} + +#[cfg(feature = "vec")] +impl Grain128 { + /// Init a new grain128-AEADv2 cipher for the given nonce + /// and encrypts the plaintext. One may provide associated + /// data that will be authenticated too. + /// + /// ``` + /// use grain_128aeadv2::{Grain128, KeyInit}; + /// + /// let key = b"my secret key !!"; + /// let cipher = Grain128::new(key.into()); + /// + /// // A nonce must be USED ONLY ONCE ! + /// let (ciphertext, tag) = cipher.encrypt_aead( + /// b"super nonce!".into(), + /// b"this is associated data", + /// b"my secret" + /// ); + /// ``` + pub fn encrypt_aead( + &self, + nonce: &Nonce, + associated_data: &[u8], + plaintext: &[u8], + ) -> (Vec, Tag) { + let mut nonce_int: u128 = 0; + for i in 0..nonce.len() { + nonce_int |= (nonce[i] as u128) << (i * 8); + } + + let mut cipher = GrainCore::new(self.key, nonce_int); + + let (ct, tag) = cipher.encrypt_aead(associated_data, plaintext); + + (ct, Tag::::from(tag)) + } + + /// Init a new grain128-AEADv2 cipher for the given nonce + /// and decrypts the ciphertext. You need to provide the + /// associated data if any. + /// + /// ``` + /// use grain_128aeadv2::{Grain128, KeyInit}; + /// + /// let key = b"my secret key !!"; + /// let cipher = Grain128::new(key.into()); + /// + /// // A nonce must be USED ONLY ONCE ! + /// let (ciphertext, tag) = cipher.encrypt_aead( + /// b"super nonce!".into(), + /// b"this is associated data", + /// b"my secret" + /// ); + /// let decrypted = cipher.decrypt_aead( + /// b"super nonce!".into(), + /// b"this is associated data", + /// &ciphertext, + /// &tag + /// ).expect("Unable to decrypt"); + //// + /// assert_eq!(decrypted, b"my secret"); + /// ``` + pub fn decrypt_aead( + &self, + nonce: &Nonce, + associated_data: &[u8], + ciphertext: &[u8], + expected_tag: &Tag, + ) -> Result, Error> { + let mut nonce_int: u128 = 0; + for i in 0..nonce.len() { + nonce_int |= (nonce[i] as u128) << (i * 8); + } + + let mut cipher = GrainCore::new(self.key, nonce_int); + + cipher.decrypt_aead(associated_data, ciphertext, expected_tag.as_slice()) + } +} + +impl AeadInOut for Grain128 { + fn encrypt_inout_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result, Error> { + let mut nonce_int: u128 = 0; + for i in 0..nonce.len() { + nonce_int |= (nonce[i] as u128) << (i * 8); + } + + let mut cipher = GrainCore::new(self.key, nonce_int); + + let tag = Tag::::from(cipher.encrypt_auth_aead_inout(associated_data, buffer)); + + Ok(tag) + } + + fn decrypt_inout_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + mut buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<(), Error> { + let mut nonce_int: u128 = 0; + for i in 0..nonce.len() { + nonce_int |= (nonce[i] as u128) << (i * 8); + } + + let mut cipher = GrainCore::new(self.key, nonce_int); + + let decrypt_res = + cipher.decrypt_auth_aead_inout(associated_data, buffer.reborrow(), tag.as_slice()); + + match decrypt_res { + Ok(()) => Ok(()), + _ => { + // Avoid leaking the decrypted ciphertext + buffer.get_out().fill(0); + // Then return the error + Err(Error) + } + } + } +} diff --git a/grain-128aeadv2/src/traits.rs b/grain-128aeadv2/src/traits.rs new file mode 100644 index 00000000..c47369ec --- /dev/null +++ b/grain-128aeadv2/src/traits.rs @@ -0,0 +1,122 @@ +use num_traits::int::PrimInt; + +/// Trait that both LFSR and NFSR will implement. +/// +/// This trait provide a method to apply a feedback function +/// to the xFSR state and a clock method to clock once the xFSR +pub(crate) trait Xfsr { + fn get_state(&self) -> u128; + + fn set_state(&mut self, new_value: u128); + + fn feedback_function(&self) -> u128; + + fn clock(&mut self) -> T { + let size = (T::max_value()).count_ones() as usize; + let mask = (1 << size) - 1; + + let state = self.get_state(); + + let output = T::from(state & mask).expect("Unable to clock xFSR"); + + self.set_state((state >> size) | (self.feedback_function() << (128 - size))); + + output + } +} + +pub(crate) trait Accumulator { + fn accumulate(&mut self, new: T) -> T; + fn new() -> Self; +} + +#[cfg(test)] +mod tests { + use super::*; + + use core::ops::{BitAnd, Shr}; + use num_traits::cast::{FromPrimitive, ToPrimitive}; + use num_traits::identities::One; + use num_traits::sign::Unsigned; + + // Useful function for clocking the + // LFSR/NFSR during the tests + /// Extract the next 8 bits starting from a given position. + fn get_byte_at_bit + One + ToPrimitive + FromPrimitive>( + value: &T, + index: usize, + ) -> u8 + where + for<'a> &'a T: Shr, + { + ((value >> index) & T::from_u8(0xff).expect("Unable to get the given byte")) + .to_u8() + .expect("Unable extract the given byte index") + } + + struct Lfsr { + pub(crate) state: u128, + } + + struct Nfsr { + pub(crate) state: u128, + } + + impl Xfsr for Lfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value + } + #[inline(always)] + fn feedback_function(&self) -> u128 { + (get_byte_at_bit(&self.state, 0) + ^ get_byte_at_bit(&self.state, 4) + ^ get_byte_at_bit(&self.state, 7) + ^ get_byte_at_bit(&self.state, 33)) as u128 + } + } + + impl Xfsr for Nfsr { + fn get_state(&self) -> u128 { + self.state + } + + fn set_state(&mut self, new_value: u128) { + self.state = new_value + } + + #[inline(always)] + fn feedback_function(&self) -> u128 { + ((get_byte_at_bit(&self.state, 0) & get_byte_at_bit(&self.state, 4)) + ^ (get_byte_at_bit(&self.state, 7) & get_byte_at_bit(&self.state, 33))) + as u128 + } + } + + #[test] + fn test_lfsr() { + let mut lfsr = Lfsr { + state: 827238322173621362173923281382u128, + }; + + let output: u8 = lfsr.clock(); + + assert_eq!(output, 230); + assert_eq!(lfsr.state, 45193751859918539374720148495523603401); + } + + #[test] + fn test_nfsr() { + let mut nfsr = Nfsr { + state: 827238322173621362173923281382u128, + }; + + let output: u8 = nfsr.clock(); + + assert_eq!(output, 230); + assert_eq!(nfsr.state, 9304595973725810806317357867954299849); + } +} diff --git a/grain-128aeadv2/src/utils.rs b/grain-128aeadv2/src/utils.rs new file mode 100644 index 00000000..6a81004b --- /dev/null +++ b/grain-128aeadv2/src/utils.rs @@ -0,0 +1,183 @@ +use core::ops::{BitAnd, Shr}; +use num_traits::cast::{FromPrimitive, ToPrimitive}; +use num_traits::identities::One; +use num_traits::sign::Unsigned; + +/// Extract the next 16 bits starting from a given position. +pub(crate) fn get_2bytes_at_bit< + T: Unsigned + BitAnd + One + ToPrimitive + FromPrimitive, +>( + value: &T, + index: usize, +) -> u16 +where + for<'a> &'a T: Shr, +{ + ((value >> index) & T::from_u16(0xffff).expect("Unable to get the given byte")) + .to_u16() + .expect("Unable extract the given byte index") +} + +/// Extract the next 32 bits starting from a given position. +pub(crate) fn get_4bytes_at_bit< + T: Unsigned + BitAnd + One + ToPrimitive + FromPrimitive, +>( + value: &T, + index: usize, +) -> u32 +where + for<'a> &'a T: Shr, +{ + ((value >> index) & T::from_u32(0xffffffff).expect("Unable to get the given byte")) + .to_u32() + .expect("Unable extract the given byte index") +} + +/// Deinterleave 32 bits of pre-output, +/// output the keystream (at even indexes) +/// and the authentication stream (at odd ones) +pub(crate) fn deinterleave32(input: &u32) -> (u16, u16) { + let input = *input as u64; + let mut output = (((input) << 31) | input) & 0x5555555555555555; + output = (output | (output >> 1)) & 0x3333333333333333; + output = (output | (output >> 2)) & 0x0f0f0f0f0f0f0f0f; + output = (output | (output >> 4)) & 0x00ff00ff00ff00ff; + output = output | (output >> 8); + + ((output & 0xffff) as u16, (output >> 32) as u16) +} + +/// Deinterleave 16 bits of pre-output, +/// output the keystream (at even indexes) +/// and the authentication stream (at odd ones) +pub fn deinterleave16(input: &u16) -> (u8, u8) { + let input = *input as u32; + let mut output = (((input) << 15) | input) & 0x55555555; + output = (output | (output >> 1)) & 0x33333333; + output = (output | (output >> 2)) & 0x0f0f0f0f; + output = output | (output >> 4); + + ((output & 0xff) as u8, (output >> 16) as u8) +} + +/// Encode a length according to Grain spec +pub fn len_encode(length: usize) -> (usize, [u8; 9]) { + let mut output = [0u8; 9]; + + if length <= 127 { + output[0] = length as u8; + + (1usize, output) + } else { + let length_bytes = length.to_be_bytes(); + let mut size_len = 0usize; + + while length_bytes[size_len] == 0 { + size_len += 1 + } + + output[0] = 0x80u8 + ((8 - size_len) as u8); + for (i, e) in length_bytes[size_len..].iter().enumerate() { + output[i + 1] = *e; + } + + ((8 - size_len) + 1, output) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use proptest::prelude::*; + + extern crate std; + use std::mem; + + // ******************************** + // Tests for `get_2bytes_at_bit` function + // ******************************** + // Define a macro to generate a test function based on proptest module + // to perform unit/property tests of evaluate_poly. + macro_rules! test_get_2bytes_at_bit_for { + ($name:tt, $type: ty) => { + proptest! { + #[test] + fn $name(value in 0..(<$type>::MAX), pos in 0..(mem::size_of::<$type>())) { + assert_eq!(get_2bytes_at_bit(&value, pos), ((value >> pos) & 0xffff) as u16); + } + } + }; + } + + test_get_2bytes_at_bit_for!(test_get_2bytes_at_bit_u16, u16); + test_get_2bytes_at_bit_for!(test_get_2bytes_at_bit_u32, u32); + test_get_2bytes_at_bit_for!(test_get_2bytes_at_bit_u64, u64); + test_get_2bytes_at_bit_for!(test_get_2bytes_at_bit_u128, u128); + + proptest! { + #[test] + fn test_len_encode_le_127(l in 0..=127usize) { + let (size, arr) = len_encode(l); + + assert_eq!(&arr[0..size], &[l as u8]); + } + } + + proptest! { + #[test] + fn test_len_encode_ge_127(l in 128..4294967296usize) { + let (size, arr) = len_encode(l); + let encoded = &arr[0..size]; + + // Ensure first bit is set to 1 + assert_eq!((encoded[0] >> 7) & 1, 1); + + // Ensure the remaining first byte bits encode + // the byte length of the size + assert_eq!(encoded[0] & 0x7f, l.to_be_bytes().into_iter().skip_while(|&x| x == 0).count() as u8); + + // Ensure the remaining bytes represents the len + let encoded_size: usize = { + let mut s = 0; + for i in 1..(encoded.len()) { + s += (encoded[i] as usize) << (encoded.len() - i - 1) * 8 + } + s + }; + assert_eq!(encoded_size, l); + } + } + + // Test deinterleave16 + #[test] + fn test_deinterleave16() { + let i = 0b1010101010101010; + let (x, y) = deinterleave16(&i); + + assert_eq!(x, 0); + assert_eq!(y, 255); + + let j = 0b0101010101010101; + let (x, y) = deinterleave16(&j); + + assert_eq!(y, 0); + assert_eq!(x, 255); + } + + // Test deinterleave32 + #[test] + fn test_deinterleave32() { + let i = 0b10101010101010101010101010101010; + let (x, y) = deinterleave32(&i); + + assert_eq!(x, 0); + assert_eq!(y, 0xffff); + + let j = 0b01010101010101010101010101010101; + let (x, y) = deinterleave32(&j); + + assert_eq!(y, 0); + assert_eq!(x, 0xffff); + } +} diff --git a/grain-128aeadv2/tests/tests.rs b/grain-128aeadv2/tests/tests.rs new file mode 100644 index 00000000..53268017 --- /dev/null +++ b/grain-128aeadv2/tests/tests.rs @@ -0,0 +1,24 @@ +#![cfg(feature = "vec")] + +use grain_128aeadv2::Grain128; +use grain_128aeadv2::KeyInit; + +#[test] +fn test_encrypt_decrypt_aead() { + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + let pt = [0u8; 32]; + + let cipher = Grain128::new(&key.into()); + + let (encrypted, tag) = cipher.encrypt_aead(&nonce.into(), b"this is authenticated data", &pt); + cipher + .decrypt_aead( + &nonce.into(), + b"this is authenticated data", + &encrypted, + &tag, + ) + .expect("Unable to decrypt"); +} diff --git a/grain-128aeadv2/tests/tests_feature.rs b/grain-128aeadv2/tests/tests_feature.rs new file mode 100644 index 00000000..122ab215 --- /dev/null +++ b/grain-128aeadv2/tests/tests_feature.rs @@ -0,0 +1,211 @@ +#[cfg(any(feature = "arrayvec", feature = "alloc"))] +pub fn to_test_vector(test_vec: u128, size: usize) -> u128 { + let mut output = 0u128; + + for i in 0..size { + let byte = (test_vec >> i * 8) & 0xff; + output += byte << ((size - 1) * 8 - (i * 8)); + } + + output +} + +#[cfg(any(feature = "arrayvec", feature = "alloc"))] +macro_rules! test_encrypt_for { + ($name:tt, $type: ty) => { + #[test] + fn $name() { + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + + let mut buffer = <$type>::new(); + for i in 0..7 { + buffer.push(i); + } + + let cipher = Grain128::new(&key.into()); + + cipher + .encrypt_in_place(&nonce.into(), b"this is authenticated data", &mut buffer) + .expect("Unable to encrypt"); + cipher + .decrypt_in_place(&nonce.into(), b"this is authenticated data", &mut buffer) + .expect("Unable to decrypt"); + } + }; +} + +#[cfg(any(feature = "arrayvec", feature = "alloc"))] +macro_rules! test_encrypt_test_vectors_for { + ($name:tt, $type: ty) => { + #[test] + fn $name() { + // First set of pt/ad test vectors + let tag = 0x7137d5998c2de4a5u128; + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + + let mut buffer = <$type>::new(); + let cipher = Grain128::new(&key.into()); + + cipher + .encrypt_in_place(&nonce.into(), b"", &mut buffer) + .expect("Unable to encrypt"); + + assert_eq!( + tag, + to_test_vector( + u64::from_le_bytes(buffer[..8].try_into().expect("Unable to get the tag")) + as u128, + 8 + ) + ); + + // First set of pt/ad test vectors + let tag = 0x22b0c12039a20e28u128; + let ct = 0x96d1bda7ae11f0bau128; + + // Init and load keys into the cipher + let key: [u8; 16] = core::array::from_fn(|i| i as u8); + let nonce: [u8; 12] = core::array::from_fn(|i| i as u8); + let ad: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; + + let mut buffer = <$type>::new(); + for i in 0..8 { + buffer.push(i); + } + + let cipher = Grain128::new(&key.into()); + cipher + .encrypt_in_place(&nonce.into(), &ad, &mut buffer) + .expect("Unable to encrypt"); + + let computed_ct = to_test_vector( + u64::from_le_bytes(buffer[..8].try_into().expect("Unable to get the tag")) as u128, + 8, + ); + let computed_tag = to_test_vector( + u64::from_le_bytes(buffer[8..].try_into().expect("Unable to get the tag")) as u128, + 8, + ); + + assert_eq!(tag, computed_tag); + assert_eq!(ct, computed_ct); + } + }; +} + +#[cfg(any(feature = "arrayvec", feature = "alloc"))] +macro_rules! test_bad_ct_for { + ($name:tt, $type: ty) => { + #[test] + #[should_panic(expected = "Unable to decrypt")] + fn $name() { + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + + let mut buffer = <$type>::new(); + for i in 0..8 { + buffer.push(i); + } + + let cipher = Grain128::new(&key.into()); + + cipher + .encrypt_in_place(&nonce.into(), b"", &mut buffer) + .expect("Unable to encrypt"); + + // Change the ciphertext + buffer[0] = 0; + + match cipher.decrypt_in_place(&nonce.into(), b"", &mut buffer) { + Ok(_) => { + panic!("Encryption should fail"); + } + Err(_) => { + // Ensure that the buffer is filled with zeroes + // in case of a tag verification failure + for i in 0..8 { + assert_eq!(buffer[i], 0); + } + + panic!("Unable to decrypt"); + } + } + } + }; +} + +#[cfg(any(feature = "arrayvec", feature = "alloc"))] +macro_rules! test_bad_tag_for { + ($name:tt, $type: ty) => { + #[test] + #[should_panic(expected = "Unable to decrypt")] + fn $name() { + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + + let mut buffer = <$type>::new(); + for i in 0..8 { + buffer.push(i); + } + + let cipher = Grain128::new(&key.into()); + + cipher + .encrypt_in_place(&nonce.into(), b"", &mut buffer) + .expect("Unable to encrypt"); + + // Change tag + buffer[10] = 0; + + match cipher.decrypt_in_place(&nonce.into(), b"", &mut buffer) { + Ok(_) => { + panic!("Encryption should fail"); + } + Err(_) => { + // Ensure that the buffer is filled with zeroes + // in case of a tag verification failure + for i in 0..8 { + assert_eq!(buffer[i], 0); + } + + panic!("Unable to decrypt"); + } + } + } + }; +} + +#[cfg(feature = "alloc")] +mod test_alloc { + use super::*; + + use grain_128aeadv2::Grain128; + use grain_128aeadv2::KeyInit; + use grain_128aeadv2::aead::AeadInOut; + + test_encrypt_for!(test_encrypt_vec, Vec); + test_encrypt_test_vectors_for!(test_encrypt_test_vectors_vec, Vec); + test_bad_ct_for!(test_bad_ct_vec, Vec); + test_bad_tag_for!(test_bad_tag_vec, Vec); +} + +#[cfg(feature = "arrayvec")] +mod test_arrayvec { + use super::*; + + use grain_128aeadv2::Grain128; + use grain_128aeadv2::KeyInit; + use grain_128aeadv2::aead::AeadInOut; + use grain_128aeadv2::aead::arrayvec::ArrayVec; + + test_encrypt_for!(test_encrypt_arrayvec, ArrayVec); + test_encrypt_test_vectors_for!(test_encrypt_test_vectors_arrayvec, ArrayVec); + test_bad_ct_for!(test_bad_ct_arrayvec, ArrayVec); + test_bad_tag_for!(test_bad_tag_arrayvec, ArrayVec); +} diff --git a/grain-128aeadv2/tests/tests_noalloc.rs b/grain-128aeadv2/tests/tests_noalloc.rs new file mode 100644 index 00000000..01b646a9 --- /dev/null +++ b/grain-128aeadv2/tests/tests_noalloc.rs @@ -0,0 +1,32 @@ +#![no_std] + +#[cfg(not(feature = "vec"))] +#[test] +fn test_assert_no_alloc() { + use assert_no_alloc::{AllocDisabler, assert_no_alloc}; + use grain_128aeadv2::aead::{AeadInOut, arrayvec::ArrayVec}; + use grain_128aeadv2::{Grain128, KeyInit}; + + #[global_allocator] + static A: AllocDisabler = AllocDisabler; + + assert_no_alloc(|| { + // Init and load keys into the cipher + let key = [0u8; 16]; + let nonce = [0u8; 12]; + + let mut buffer = ArrayVec::::new(); + for i in 0..7 { + buffer.push(i); + } + + let cipher = Grain128::new(&key.into()); + + cipher + .encrypt_in_place(&nonce.into(), b"this is authenticated data", &mut buffer) + .expect("Unable to encrypt"); + cipher + .decrypt_in_place(&nonce.into(), b"this is authenticated data", &mut buffer) + .expect("Unable to decrypt"); + }); +}