diff --git a/Cargo.lock b/Cargo.lock index 4bd34687..349e38ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "dndk-gcm" +version = "0.1.0-rc.1" +dependencies = [ + "aead", + "aead-stream", + "aes", + "aes-gcm", + "cipher", + "hex-literal", +] + [[package]] name = "eax" version = "0.6.0-rc.2" diff --git a/Cargo.toml b/Cargo.toml index d5fdeb67..42ae41b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "ccm", "chacha20poly1305", "deoxys", + "dndk-gcm", "eax", "ocb3", "xaes-256-gcm", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 9193b9df..ddb957ff 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -19,6 +19,7 @@ aes-gcm-siv = { path = "../aes-gcm-siv/" } ascon-aead128 = { path = "../ascon-aead128/" } chacha20poly1305 = { path = "../chacha20poly1305/" } deoxys = { path = "../deoxys/" } +dndk-gcm = { path = "../dndk-gcm/" } eax = { path = "../eax/" } [target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies] @@ -49,6 +50,11 @@ name = "deoxys" path = "src/deoxys.rs" harness = false +[[bench]] +name = "dndk-gcm" +path = "src/dndk-gcm.rs" +harness = false + [[bench]] name = "eax" path = "src/eax.rs" diff --git a/benches/src/dndk-gcm.rs b/benches/src/dndk-gcm.rs new file mode 100644 index 00000000..928ad1fa --- /dev/null +++ b/benches/src/dndk-gcm.rs @@ -0,0 +1,49 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; + +use dndk_gcm::aead::{Aead, KeyInit}; +use dndk_gcm::DndkGcm; + +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(c: &mut Benchmarker) { + let mut group = c.benchmark_group("dndk-gcm"); + + for size in &[KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB] { + let buf = vec![0u8; *size]; + + group.throughput(Throughput::Bytes(*size as u64)); + + group.bench_function(BenchmarkId::new("encrypt-256", size), |b| { + let cipher = DndkGcm::new(&Default::default()); + b.iter(|| cipher.encrypt(&Default::default(), &*buf)) + }); + group.bench_function(BenchmarkId::new("decrypt-256", size), |b| { + let cipher = DndkGcm::new(&Default::default()); + let nonce = Default::default(); + b.iter(|| cipher.decrypt(&nonce, &*buf)) + }); + } + + group.finish(); +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); + +#[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 +); + +criterion_main!(benches); diff --git a/dndk-gcm/Cargo.toml b/dndk-gcm/Cargo.toml new file mode 100644 index 00000000..9980ff27 --- /dev/null +++ b/dndk-gcm/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "dndk-gcm" +version = "0.1.0-rc.1" +description = """ +Pure Rust implementation of the DNDK-GCM extended-nonce AEAD (without key commitment). +""" +authors = ["RustCrypto Developers"] +edition = "2024" +license = "Apache-2.0 OR MIT" +readme = "README.md" +documentation = "https://docs.rs/dndk-gcm" +repository = "https://github.com/RustCrypto/AEADs" +keywords = ["aead", "aes", "gcm", "dndk", "encryption"] +categories = ["cryptography", "no-std"] +rust-version = "1.85" + +[dependencies] +aead = { version = "0.6.0-rc.4", default-features = false } +aes = "0.9.0-rc.2" +aes-gcm = { version = "0.11.0-rc.2", default-features = false, features = ["aes"] } +cipher = "0.5.0-rc.2" +aead-stream = { version = "0.6.0-rc.2", optional = true, default-features = false } + +[dev-dependencies] +aead = { version = "0.6.0-rc.4", features = ["dev"], default-features = false } +hex-literal = "1" + +[features] +default = ["alloc", "getrandom"] +alloc = ["aead/alloc", "aead-stream?/alloc", "aes-gcm/alloc"] +arrayvec = ["aead/arrayvec", "aes-gcm/arrayvec"] +getrandom = ["aes-gcm/getrandom"] +rand_core = ["aead/rand_core", "aes-gcm/rand_core"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/dndk-gcm/README.md b/dndk-gcm/README.md new file mode 100644 index 00000000..402ebbc5 --- /dev/null +++ b/dndk-gcm/README.md @@ -0,0 +1,24 @@ +# DNDK-GCM (no key commitment) + +Pure Rust implementation of DNDK-GCM (Double Nonce Derive Key AES-GCM) with +key commitment disabled (KC_Choice = 0) as specified in +`draft-gueron-cfrg-dndkgcm`. + +This crate provides a fixed 24-byte nonce variant: `DndkGcm24`. + +## Usage + +```rust +use dndk_gcm::{ + aead::{Aead, Key, KeyInit}, + DndkGcm, Nonce, +}; + +let key = Key::::from_slice(&[0u8; 32]); +let cipher = DndkGcm::new(key); + +let nonce = Nonce::from_slice(&[0u8; 24]); +let ciphertext = cipher.encrypt(nonce, b"hello".as_ref()).unwrap(); +let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap(); +assert_eq!(&plaintext, b"hello"); +``` diff --git a/dndk-gcm/src/lib.rs b/dndk-gcm/src/lib.rs new file mode 100644 index 00000000..21d13f6b --- /dev/null +++ b/dndk-gcm/src/lib.rs @@ -0,0 +1,168 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![deny(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms)] + +//! # Usage +//! +//! Simple usage (allocating, no associated data): +//! +#![cfg_attr(feature = "getrandom", doc = "```")] +#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")] +//! # fn main() -> Result<(), Box> { +//! // NOTE: requires the `getrandom` feature is enabled +//! +//! use dndk_gcm::{ +//! aead::{Aead, AeadCore, Key, KeyInit}, +//! DndkGcm, Nonce +//! }; +//! +//! let key = Key::::from_slice(&[0u8; 32]); +//! let cipher = DndkGcm::new(key); +//! let nonce = Nonce::from_slice(&[0u8; 24]); // 192-bits; MUST be unique per message +//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())?; +//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())?; +//! assert_eq!(&plaintext, b"plaintext message"); +//! # Ok(()) +//! # } +//! ``` + +pub use aead; +pub use aes; +pub use aes_gcm; + +use aead::{ + AeadCore, AeadInOut, Error, KeyInit, KeySizeUser, TagPosition, array::Array, inout::InOutBuf, +}; +use aes::Aes256; +use aes_gcm::Aes256Gcm; +use cipher::{BlockCipherEncrypt, BlockSizeUser, consts::U12}; + +/// DNDK-GCM with a 24-byte nonce (KC_Choice = 0). +#[derive(Clone)] +pub struct DndkGcm { + aes: Aes256, +} + +type KeySize = ::KeySize; + +/// DNDK-GCM nonce (24 bytes). +pub type Nonce = aes_gcm::Nonce; + +/// DNDK-GCM key. +pub type Key = aes_gcm::Key; + +/// DNDK-GCM tag. +pub type Tag::TagSize> = aes_gcm::Tag; + +/// Maximum length of plaintext. +pub const P_MAX: u64 = (1 << 36) - 32; + +/// Maximum length of associated data. +pub const A_MAX: u64 = (1 << 61) - 1; + +/// Maximum length of ciphertext. +pub const C_MAX: u64 = (1 << 36) - 32; + +impl AeadCore for DndkGcm { + type NonceSize = cipher::consts::U24; + type TagSize = ::TagSize; + const TAG_POSITION: TagPosition = TagPosition::Postfix; +} + +impl KeySizeUser for DndkGcm { + type KeySize = KeySize; +} + +impl KeyInit for DndkGcm { + fn new(key: &Key) -> Self { + Self { + aes: Aes256::new(key), + } + } +} + +impl AeadInOut for DndkGcm { + fn encrypt_inout_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result { + if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let (gcm_iv, key) = derive_key_and_iv::<24>(&self.aes, nonce.as_slice()); + Aes256Gcm::new(&key).encrypt_inout_detached(&gcm_iv, associated_data, buffer) + } + + fn decrypt_inout_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<(), Error> { + if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let (gcm_iv, key) = derive_key_and_iv::<24>(&self.aes, nonce.as_slice()); + Aes256Gcm::new(&key).decrypt_inout_detached(&gcm_iv, associated_data, buffer, tag) + } +} + +type Block = Array::BlockSize>; + +type GcmIv = aes_gcm::Nonce; + +type DerivedKey = Key; + +fn derive_key_and_iv(aes: &Aes256, nonce: &[u8]) -> (GcmIv, DerivedKey) { + debug_assert_eq!(nonce.len(), LN); + + // Algorithm 1 (KC_Choice = 0): pad nonce, split into head/tail, derive DK and 12-byte IV. + let mut npadded = [0u8; 27]; + npadded[..LN].copy_from_slice(nonce); + + let mut gcm_iv = GcmIv::default(); + gcm_iv.copy_from_slice(&npadded[15..27]); + + let config_byte = 8u8 * ((LN - 12) as u8); + + let mut b0 = Block::default(); + b0[..15].copy_from_slice(&npadded[..15]); + b0[15] = config_byte; + + let mut b1 = b0; + b1[15] = config_byte.wrapping_add(1); + + let mut b2 = b0; + b2[15] = config_byte.wrapping_add(2); + + let mut x0 = b0; + let mut x1 = b1; + let mut x2 = b2; + aes.encrypt_block(&mut x0); + aes.encrypt_block(&mut x1); + aes.encrypt_block(&mut x2); + + let mut y1 = x1; + let mut y2 = x2; + for i in 0..y1.len() { + y1[i] ^= x0[i]; + y2[i] ^= x0[i]; + } + + let mut key = DerivedKey::default(); + key[..16].copy_from_slice(&y1); + key[16..].copy_from_slice(&y2); + + (gcm_iv, key) +} diff --git a/dndk-gcm/tests/dndkgcm.rs b/dndk-gcm/tests/dndkgcm.rs new file mode 100644 index 00000000..59d62f4e --- /dev/null +++ b/dndk-gcm/tests/dndkgcm.rs @@ -0,0 +1,22 @@ +//! DNDK-GCM test vectors (KC_Choice = 0) + +#[macro_use] +#[path = "../../aes-gcm/tests/common/mod.rs"] +mod common; + +use aes_gcm::aead::{Aead, AeadInOut, KeyInit, Payload, array::Array}; +use common::TestVector; +use dndk_gcm::DndkGcm; +use hex_literal::hex; + +/// DNDK-GCM test vectors (draft-gueron-cfrg-dndkgcm-03, Appendix A) +const TEST_VECTORS_24: &[TestVector<[u8; 32], [u8; 24]>] = &[TestVector { + key: &hex!("0100000000000000000000000000000000000000000000000000000000000000"), + nonce: &hex!("000102030405060708090a0b0c0d0e0f1011121314151617"), + plaintext: &hex!("11000001"), + aad: &hex!("0100000011"), + ciphertext: &hex!("7f6e39cc"), + tag: &hex!("b61df0a502c167164e99fa23b7d12b9d"), +}]; + +tests!(DndkGcm, TEST_VECTORS_24);