Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"ccm",
"chacha20poly1305",
"deoxys",
"dndk-gcm",
"eax",
"ocb3",
"xaes-256-gcm",
Expand Down
6 changes: 6 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand Down
49 changes: 49 additions & 0 deletions benches/src/dndk-gcm.rs
Original file line number Diff line number Diff line change
@@ -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<criterion_cycles_per_byte::CyclesPerByte>;

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);
37 changes: 37 additions & 0 deletions dndk-gcm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
24 changes: 24 additions & 0 deletions dndk-gcm/README.md
Original file line number Diff line number Diff line change
@@ -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::<DndkGcm>::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");
```
168 changes: 168 additions & 0 deletions dndk-gcm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<dyn core::error::Error>> {
//! // NOTE: requires the `getrandom` feature is enabled
//!
//! use dndk_gcm::{
//! aead::{Aead, AeadCore, Key, KeyInit},
//! DndkGcm, Nonce
//! };
//!
//! let key = Key::<DndkGcm>::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 = <Aes256Gcm as KeySizeUser>::KeySize;

/// DNDK-GCM nonce (24 bytes).
pub type Nonce = aes_gcm::Nonce<cipher::consts::U24>;

/// DNDK-GCM key.
pub type Key<B = Aes256> = aes_gcm::Key<B>;

/// DNDK-GCM tag.
pub type Tag<Size = <Aes256Gcm as AeadCore>::TagSize> = aes_gcm::Tag<Size>;

/// 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 = <Aes256Gcm as AeadCore>::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<Tag, Error> {
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<u8, <Aes256 as BlockSizeUser>::BlockSize>;

type GcmIv = aes_gcm::Nonce<U12>;

type DerivedKey = Key<Aes256Gcm>;

fn derive_key_and_iv<const LN: usize>(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)
}
22 changes: 22 additions & 0 deletions dndk-gcm/tests/dndkgcm.rs
Original file line number Diff line number Diff line change
@@ -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);