Skip to content

Commit 77dba47

Browse files
committed
Add DNDK-GCM (no key commitment)
1 parent 3b5e0ba commit 77dba47

File tree

8 files changed

+319
-0
lines changed

8 files changed

+319
-0
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ members = [
1010
"ccm",
1111
"chacha20poly1305",
1212
"deoxys",
13+
"dndk-gcm",
1314
"eax",
1415
"ocb3",
1516
"xaes-256-gcm",

benches/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ aes-gcm-siv = { path = "../aes-gcm-siv/" }
1919
ascon-aead128 = { path = "../ascon-aead128/" }
2020
chacha20poly1305 = { path = "../chacha20poly1305/" }
2121
deoxys = { path = "../deoxys/" }
22+
dndk-gcm = { path = "../dndk-gcm/" }
2223
eax = { path = "../eax/" }
2324

2425
[target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
@@ -49,6 +50,11 @@ name = "deoxys"
4950
path = "src/deoxys.rs"
5051
harness = false
5152

53+
[[bench]]
54+
name = "dndk-gcm"
55+
path = "src/dndk-gcm.rs"
56+
harness = false
57+
5258
[[bench]]
5359
name = "eax"
5460
path = "src/eax.rs"

benches/src/dndk-gcm.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
2+
3+
use dndk_gcm::aead::{Aead, KeyInit};
4+
use dndk_gcm::DndkGcm;
5+
6+
const KB: usize = 1024;
7+
8+
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
9+
type Benchmarker = Criterion;
10+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
11+
type Benchmarker = Criterion<criterion_cycles_per_byte::CyclesPerByte>;
12+
13+
fn bench(c: &mut Benchmarker) {
14+
let mut group = c.benchmark_group("dndk-gcm");
15+
16+
for size in &[KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB] {
17+
let buf = vec![0u8; *size];
18+
19+
group.throughput(Throughput::Bytes(*size as u64));
20+
21+
group.bench_function(BenchmarkId::new("encrypt-256", size), |b| {
22+
let cipher = DndkGcm::new(&Default::default());
23+
b.iter(|| cipher.encrypt(&Default::default(), &*buf))
24+
});
25+
group.bench_function(BenchmarkId::new("decrypt-256", size), |b| {
26+
let cipher = DndkGcm::new(&Default::default());
27+
let nonce = Default::default();
28+
b.iter(|| cipher.decrypt(&nonce, &*buf))
29+
});
30+
}
31+
32+
group.finish();
33+
}
34+
35+
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
36+
criterion_group!(
37+
name = benches;
38+
config = Criterion::default();
39+
targets = bench
40+
);
41+
42+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
43+
criterion_group!(
44+
name = benches;
45+
config = Criterion::default().with_measurement(criterion_cycles_per_byte::CyclesPerByte);
46+
targets = bench
47+
);
48+
49+
criterion_main!(benches);

dndk-gcm/Cargo.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "dndk-gcm"
3+
version = "0.1.0-rc.1"
4+
description = """
5+
Pure Rust implementation of the DNDK-GCM extended-nonce AEAD (without key commitment).
6+
"""
7+
authors = ["RustCrypto Developers"]
8+
edition = "2024"
9+
license = "Apache-2.0 OR MIT"
10+
readme = "README.md"
11+
documentation = "https://docs.rs/dndk-gcm"
12+
repository = "https://github.com/RustCrypto/AEADs"
13+
keywords = ["aead", "aes", "gcm", "dndk", "encryption"]
14+
categories = ["cryptography", "no-std"]
15+
rust-version = "1.85"
16+
17+
[dependencies]
18+
aead = { version = "0.6.0-rc.4", default-features = false }
19+
aes = "0.9.0-rc.2"
20+
aes-gcm = { version = "0.11.0-rc.2", default-features = false, features = ["aes"] }
21+
cipher = "0.5.0-rc.2"
22+
aead-stream = { version = "0.6.0-rc.2", optional = true, default-features = false }
23+
24+
[dev-dependencies]
25+
aead = { version = "0.6.0-rc.4", features = ["dev"], default-features = false }
26+
hex-literal = "1"
27+
28+
[features]
29+
default = ["alloc", "getrandom"]
30+
alloc = ["aead/alloc", "aead-stream?/alloc", "aes-gcm/alloc"]
31+
arrayvec = ["aead/arrayvec", "aes-gcm/arrayvec"]
32+
getrandom = ["aes-gcm/getrandom"]
33+
rand_core = ["aead/rand_core", "aes-gcm/rand_core"]
34+
35+
[package.metadata.docs.rs]
36+
all-features = true
37+
rustdoc-args = ["--cfg", "docsrs"]

dndk-gcm/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# DNDK-GCM (no key commitment)
2+
3+
Pure Rust implementation of DNDK-GCM (Double Nonce Derive Key AES-GCM) with
4+
key commitment disabled (KC_Choice = 0) as specified in
5+
`draft-gueron-cfrg-dndkgcm`.
6+
7+
This crate provides a fixed 24-byte nonce variant: `DndkGcm24`.
8+
9+
## Usage
10+
11+
```rust
12+
use dndk_gcm::{
13+
aead::{Aead, Key, KeyInit},
14+
DndkGcm, Nonce,
15+
};
16+
17+
let key = Key::<DndkGcm>::from_slice(&[0u8; 32]);
18+
let cipher = DndkGcm::new(key);
19+
20+
let nonce = Nonce::from_slice(&[0u8; 24]);
21+
let ciphertext = cipher.encrypt(nonce, b"hello".as_ref()).unwrap();
22+
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap();
23+
assert_eq!(&plaintext, b"hello");
24+
```

dndk-gcm/src/lib.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#![no_std]
2+
#![cfg_attr(docsrs, feature(doc_cfg))]
3+
#![doc = include_str!("../README.md")]
4+
#![doc(
5+
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6+
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
7+
)]
8+
#![deny(unsafe_code)]
9+
#![warn(missing_docs, rust_2018_idioms)]
10+
11+
//! # Usage
12+
//!
13+
//! Simple usage (allocating, no associated data):
14+
//!
15+
#![cfg_attr(feature = "getrandom", doc = "```")]
16+
#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
17+
//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
18+
//! // NOTE: requires the `getrandom` feature is enabled
19+
//!
20+
//! use dndk_gcm::{
21+
//! aead::{Aead, AeadCore, Key, KeyInit},
22+
//! DndkGcm, Nonce
23+
//! };
24+
//!
25+
//! let key = Key::<DndkGcm>::from_slice(&[0u8; 32]);
26+
//! let cipher = DndkGcm::new(key);
27+
//! let nonce = Nonce::from_slice(&[0u8; 24]); // 192-bits; MUST be unique per message
28+
//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())?;
29+
//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())?;
30+
//! assert_eq!(&plaintext, b"plaintext message");
31+
//! # Ok(())
32+
//! # }
33+
//! ```
34+
35+
pub use aead;
36+
pub use aes;
37+
pub use aes_gcm;
38+
39+
use aead::{
40+
AeadCore, AeadInOut, Error, KeyInit, KeySizeUser, TagPosition, array::Array, inout::InOutBuf,
41+
};
42+
use aes::Aes256;
43+
use aes_gcm::Aes256Gcm;
44+
use cipher::{BlockCipherEncrypt, BlockSizeUser, consts::U12};
45+
46+
/// DNDK-GCM with a 24-byte nonce (KC_Choice = 0).
47+
#[derive(Clone)]
48+
pub struct DndkGcm {
49+
aes: Aes256,
50+
}
51+
52+
type KeySize = <Aes256Gcm as KeySizeUser>::KeySize;
53+
54+
/// DNDK-GCM nonce (24 bytes).
55+
pub type Nonce = aes_gcm::Nonce<cipher::consts::U24>;
56+
57+
/// DNDK-GCM key.
58+
pub type Key<B = Aes256> = aes_gcm::Key<B>;
59+
60+
/// DNDK-GCM tag.
61+
pub type Tag<Size = <Aes256Gcm as AeadCore>::TagSize> = aes_gcm::Tag<Size>;
62+
63+
/// Maximum length of plaintext.
64+
pub const P_MAX: u64 = (1 << 36) - 32;
65+
66+
/// Maximum length of associated data.
67+
pub const A_MAX: u64 = (1 << 61) - 1;
68+
69+
/// Maximum length of ciphertext.
70+
pub const C_MAX: u64 = (1 << 36) - 32;
71+
72+
impl AeadCore for DndkGcm {
73+
type NonceSize = cipher::consts::U24;
74+
type TagSize = <Aes256Gcm as AeadCore>::TagSize;
75+
const TAG_POSITION: TagPosition = TagPosition::Postfix;
76+
}
77+
78+
impl KeySizeUser for DndkGcm {
79+
type KeySize = KeySize;
80+
}
81+
82+
impl KeyInit for DndkGcm {
83+
fn new(key: &Key) -> Self {
84+
Self {
85+
aes: Aes256::new(key),
86+
}
87+
}
88+
}
89+
90+
impl AeadInOut for DndkGcm {
91+
fn encrypt_inout_detached(
92+
&self,
93+
nonce: &Nonce,
94+
associated_data: &[u8],
95+
buffer: InOutBuf<'_, '_, u8>,
96+
) -> Result<Tag, Error> {
97+
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
98+
return Err(Error);
99+
}
100+
101+
let (gcm_iv, key) = derive_key_and_iv::<24>(&self.aes, nonce.as_slice());
102+
Aes256Gcm::new(&key).encrypt_inout_detached(&gcm_iv, associated_data, buffer)
103+
}
104+
105+
fn decrypt_inout_detached(
106+
&self,
107+
nonce: &Nonce,
108+
associated_data: &[u8],
109+
buffer: InOutBuf<'_, '_, u8>,
110+
tag: &Tag,
111+
) -> Result<(), Error> {
112+
if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
113+
return Err(Error);
114+
}
115+
116+
let (gcm_iv, key) = derive_key_and_iv::<24>(&self.aes, nonce.as_slice());
117+
Aes256Gcm::new(&key).decrypt_inout_detached(&gcm_iv, associated_data, buffer, tag)
118+
}
119+
}
120+
121+
type Block = Array<u8, <Aes256 as BlockSizeUser>::BlockSize>;
122+
123+
type GcmIv = aes_gcm::Nonce<U12>;
124+
125+
type DerivedKey = Key<Aes256Gcm>;
126+
127+
fn derive_key_and_iv<const LN: usize>(aes: &Aes256, nonce: &[u8]) -> (GcmIv, DerivedKey) {
128+
debug_assert_eq!(nonce.len(), LN);
129+
130+
// Algorithm 1 (KC_Choice = 0): pad nonce, split into head/tail, derive DK and 12-byte IV.
131+
let mut npadded = [0u8; 27];
132+
npadded[..LN].copy_from_slice(nonce);
133+
134+
let mut gcm_iv = GcmIv::default();
135+
gcm_iv.copy_from_slice(&npadded[15..27]);
136+
137+
let config_byte = 8u8 * ((LN - 12) as u8);
138+
139+
let mut b0 = Block::default();
140+
b0[..15].copy_from_slice(&npadded[..15]);
141+
b0[15] = config_byte;
142+
143+
let mut b1 = b0;
144+
b1[15] = config_byte.wrapping_add(1);
145+
146+
let mut b2 = b0;
147+
b2[15] = config_byte.wrapping_add(2);
148+
149+
let mut x0 = b0;
150+
let mut x1 = b1;
151+
let mut x2 = b2;
152+
aes.encrypt_block(&mut x0);
153+
aes.encrypt_block(&mut x1);
154+
aes.encrypt_block(&mut x2);
155+
156+
let mut y1 = x1;
157+
let mut y2 = x2;
158+
for i in 0..y1.len() {
159+
y1[i] ^= x0[i];
160+
y2[i] ^= x0[i];
161+
}
162+
163+
let mut key = DerivedKey::default();
164+
key[..16].copy_from_slice(&y1);
165+
key[16..].copy_from_slice(&y2);
166+
167+
(gcm_iv, key)
168+
}

dndk-gcm/tests/dndkgcm.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! DNDK-GCM test vectors (KC_Choice = 0)
2+
3+
#[macro_use]
4+
#[path = "../../aes-gcm/tests/common/mod.rs"]
5+
mod common;
6+
7+
use aes_gcm::aead::{Aead, AeadInOut, KeyInit, Payload, array::Array};
8+
use common::TestVector;
9+
use dndk_gcm::DndkGcm;
10+
use hex_literal::hex;
11+
12+
/// DNDK-GCM test vectors (draft-gueron-cfrg-dndkgcm-03, Appendix A)
13+
const TEST_VECTORS_24: &[TestVector<[u8; 32], [u8; 24]>] = &[TestVector {
14+
key: &hex!("0100000000000000000000000000000000000000000000000000000000000000"),
15+
nonce: &hex!("000102030405060708090a0b0c0d0e0f1011121314151617"),
16+
plaintext: &hex!("11000001"),
17+
aad: &hex!("0100000011"),
18+
ciphertext: &hex!("7f6e39cc"),
19+
tag: &hex!("b61df0a502c167164e99fa23b7d12b9d"),
20+
}];
21+
22+
tests!(DndkGcm, TEST_VECTORS_24);

0 commit comments

Comments
 (0)