gimli_crypto/
aead_impl.rs

1//! # `aead/gimli24v1` implementation
2//!
3//! This module implements the `aead/gimli24v1` authenticated encryption with associated data
4//! (AEAD) cipher using a sponge-based construction on the Gimli permutation.
5//!
6//! # Usage
7//!
8//! This module provides `no_std`-compatible in-place encryption/decryption:
9//!
10//! ```
11//! use gimli_crypto::{encrypt_in_place, decrypt_in_place, KEY_SIZE, NONCE_SIZE};
12//!
13//! let key = [0u8; KEY_SIZE];
14//! let nonce = [1u8; NONCE_SIZE];
15//! let mut data = *b"Secret message";
16//! let aad = b"public header";
17//!
18//! // Encrypt in-place.
19//! let tag = encrypt_in_place(&key, &nonce, aad, &mut data);
20//!
21//! // Decrypt in-place with authentication.
22//! decrypt_in_place(&key, &nonce, aad, &mut data, &tag)
23//!     .expect("authentication failed");
24//!
25//! assert_eq!(&data, b"Secret message");
26//! ```
27//!
28//! For allocating APIs with separate input/output buffers, use the RustCrypto [`Aead`](crate::rustcrypto::GimliAead) trait.
29
30use crate::gimli::{State, gimli};
31use crate::{KEY_SIZE, NONCE_SIZE, RATE, STATE_LAST_BYTE, TAG_SIZE};
32use subtle::ConstantTimeEq;
33
34/// Authentication tag (16 bytes).
35pub type Tag = [u8; TAG_SIZE];
36
37/// Authentication tag verification failed.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct AuthenticationFailed;
40
41/// Initialize the Gimli AEAD state with key and nonce.
42fn initialize(key: &[u8; KEY_SIZE], nonce: &[u8; NONCE_SIZE]) -> State {
43    let mut state = State::new();
44    let state_bytes = state.as_bytes_mut();
45
46    // Load nonce (16 bytes) into state[0..16].
47    state_bytes[..16].copy_from_slice(nonce);
48
49    // Load key (32 bytes) into state[16..48].
50    state_bytes[16..].copy_from_slice(key);
51
52    gimli(&mut state);
53
54    state
55}
56
57/// Process associated data.
58fn process_aad(state: &mut State, associated_data: &[u8]) {
59    let mut iter = associated_data.chunks_exact(RATE);
60
61    // Process full blocks.
62    for chunk in iter.by_ref() {
63        let state_bytes = state.as_bytes_mut();
64        for i in 0..RATE {
65            state_bytes[i] ^= chunk[i];
66        }
67        gimli(state);
68    }
69
70    // Process remainder with domain separation.
71    let remainder = iter.remainder();
72    let state_bytes = state.as_bytes_mut();
73    for i in 0..remainder.len() {
74        state_bytes[i] ^= remainder[i];
75    }
76
77    state_bytes[remainder.len()] ^= 1;
78    state_bytes[STATE_LAST_BYTE] ^= 1;
79
80    gimli(state);
81}
82
83/// Encrypt plaintext using Gimli AEAD (in-place)
84///
85/// Encrypts the data in `buffer` in-place and returns the authentication tag.
86/// The buffer contains plaintext on input and ciphertext on output.
87#[must_use]
88pub fn encrypt_in_place(
89    key: &[u8; KEY_SIZE],
90    nonce: &[u8; NONCE_SIZE],
91    associated_data: &[u8],
92    buffer: &mut [u8],
93) -> Tag {
94    let mut state = initialize(key, nonce);
95
96    // Process associated data.
97    process_aad(&mut state, associated_data);
98
99    // Process plaintext in-place.
100    let mut iter = buffer.chunks_exact_mut(RATE);
101
102    // Process full blocks.
103    for chunk in &mut iter {
104        let state_bytes = state.as_bytes_mut();
105
106        for i in 0..RATE {
107            state_bytes[i] ^= chunk[i];
108        }
109        chunk.copy_from_slice(&state_bytes[..16]);
110
111        gimli(&mut state);
112    }
113
114    // Process remainder with domain separation.
115    let remainder = iter.into_remainder();
116    let state_bytes = state.as_bytes_mut();
117    for i in 0..remainder.len() {
118        state_bytes[i] ^= remainder[i];
119    }
120    remainder.copy_from_slice(&state_bytes[..remainder.len()]);
121
122    state_bytes[remainder.len()] ^= 1;
123    state_bytes[STATE_LAST_BYTE] ^= 1;
124
125    gimli(&mut state);
126
127    // Generate tag.
128    let mut tag = [0u8; TAG_SIZE];
129    tag.copy_from_slice(&state.as_bytes()[..TAG_SIZE]);
130    tag
131}
132
133/// Decrypt ciphertext using Gimli AEAD (in-place)
134///
135/// Decrypts the data in `buffer` in-place if authentication succeeds.
136/// The buffer contains ciphertext on input and plaintext on output.
137pub fn decrypt_in_place(
138    key: &[u8; KEY_SIZE],
139    nonce: &[u8; NONCE_SIZE],
140    associated_data: &[u8],
141    buffer: &mut [u8],
142    tag: &Tag,
143) -> Result<(), AuthenticationFailed> {
144    let mut state = initialize(key, nonce);
145
146    // Process associated data.
147    process_aad(&mut state, associated_data);
148
149    // Process full blocks.
150    let mut iter = buffer.chunks_exact_mut(RATE);
151    for chunk in &mut iter {
152        let state_bytes = state.as_bytes_mut();
153
154        for i in 0..RATE {
155            let ciphertext_byte = chunk[i];
156            chunk[i] = state_bytes[i] ^ ciphertext_byte;
157            state_bytes[i] = ciphertext_byte;
158        }
159
160        gimli(&mut state);
161    }
162
163    // Process remainder with domain separation.
164    let state_bytes = state.as_bytes_mut();
165    let remainder = iter.into_remainder();
166    for i in 0..remainder.len() {
167        let ciphertext_byte = remainder[i];
168        remainder[i] = state_bytes[i] ^ ciphertext_byte;
169        state_bytes[i] = ciphertext_byte;
170    }
171
172    state_bytes[remainder.len()] ^= 1;
173    state_bytes[STATE_LAST_BYTE] ^= 1;
174
175    gimli(&mut state);
176
177    // Verify tag using constant-time comparison.
178    let computed_tag = &state.as_bytes()[..TAG_SIZE];
179    if computed_tag.ct_eq(tag).into() {
180        Ok(())
181    } else {
182        Err(AuthenticationFailed)
183    }
184}
185
186#[cfg(test)]
187mod tests;