Skip to main content

crypto/ascon/
ascon_aead128.rs

1use super::*;
2use crate::{Aead, AeadError, Bytes, Hash};
3
4/// Ascon-AEAD128 authenticated encryption with associated data (NIST SP 800-232).
5///
6/// # Parameters
7///
8/// - Key: 128 bits (16 bytes)
9/// - Nonce: 128 bits (16 bytes)
10/// - Tag: 128 bits (16 bytes)
11/// - Rate: 128 bits, capacity: 192 bits
12/// - Initialization/finalization rounds: 12
13/// - Data processing rounds: 8
14///
15/// # Example
16///
17/// ```ignore
18/// use crypto::{Aead, ascon::AsconAead128};
19///
20/// let key = [0x42u8; 16];
21/// let nonce = [0xABu8; 16];
22/// let aad = b"example metadata";
23/// let plaintext = b"hello, world!";
24///
25/// let cipher = AsconAead128::new(&key);
26///
27/// let mut ct = plaintext.to_vec();
28/// let tag = cipher.encrypt_in_place(&mut ct, &nonce, aad);
29///
30/// let mut pt = ct.clone();
31/// cipher.decrypt_in_place(&mut pt, &nonce, aad, tag.as_ref()).unwrap();
32/// assert_eq!(&pt, plaintext);
33/// ```
34///
35/// # Usage limits (NIST SP 800-232 ยง4.3)
36///
37/// - Max data per key: 2^54 bytes
38/// - Nonces must be distinct per key (up to 2^8 repetitions tolerated)
39/// - Max decryption failures: 2^(tag_len - 32) before key rotation
40#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
41pub struct AsconAead128 {
42    iv: u64,
43    k0: u64,
44    k1: u64,
45    key: [u8; 16],
46}
47
48impl AsconAead128 {
49    /// Creates a new Ascon-AEAD128 instance from a 16-byte key.
50    pub fn new(key: &[u8; 16]) -> Self {
51        let k0 = u64::from_le_bytes(key[0..8].try_into().unwrap());
52        let k1 = u64::from_le_bytes(key[8..16].try_into().unwrap());
53        AsconAead128 {
54            iv: 0x0000_1000_808c_0001,
55            k0,
56            k1,
57            key: *key,
58        }
59    }
60
61    /// Initialize the state with key + nonce + IV.
62    fn init_state(&self, nonce: &[u8; 16]) -> State {
63        let mut state = State::init_aead(&self.key, nonce, self.iv);
64        p12(&mut state);
65        state.xor_word(3, self.k0);
66        state.xor_word(4, self.k1);
67        state
68    }
69
70    /// Process associated data.
71    fn process_ad(state: &mut State, aad: &[u8]) {
72        if aad.is_empty() {
73            return;
74        }
75        let (chunks, remainder) = aad.as_chunks::<16>();
76        for chunk in chunks {
77            state.xor_rate128_bytes(chunk);
78            p8(state);
79        }
80        if !remainder.is_empty() {
81            state.xor_partial_rate(remainder);
82        }
83        state.apply_aead_pad(remainder.len());
84        p8(state);
85    }
86
87    /// Compute the authentication tag from the final state.
88    fn compute_tag(&self, state: &State) -> [u8; 16] {
89        let tag = state.tag_bytes();
90        let t0 = u64::from_le_bytes(tag[0..8].try_into().unwrap()) ^ self.k0;
91        let t1 = u64::from_le_bytes(tag[8..16].try_into().unwrap()) ^ self.k1;
92        let mut result = [0u8; 16];
93        result[..8].copy_from_slice(&t0.to_le_bytes());
94        result[8..].copy_from_slice(&t1.to_le_bytes());
95        result
96    }
97}
98
99impl Aead for AsconAead128 {
100    const KEY_SIZE: usize = 16;
101    const TAG_SIZE: usize = 16;
102    const NONCE_SIZE: usize = 16;
103
104    fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
105        let nonce: &[u8; 16] = nonce.try_into().expect("nonce must be 16 bytes");
106        let mut state = self.init_state(nonce);
107        Self::process_ad(&mut state, aad);
108        state.apply_domain_sep();
109
110        let rem_len = in_out.len() % 16;
111
112        let (chunks, remainder) = in_out.as_chunks_mut::<16>();
113        for chunk in chunks {
114            state.encrypt_in_place_block(chunk);
115            p8(&mut state);
116        }
117
118        if rem_len > 0 {
119            state.xor_partial_rate(remainder);
120            state.read_rate_bytes(remainder);
121        }
122        state.apply_aead_pad(rem_len);
123
124        state.xor_word(2, self.k0);
125        state.xor_word(3, self.k1);
126        p12(&mut state);
127
128        let tag_bytes = self.compute_tag(&state);
129        let mut tag = Hash(Bytes::<64>::with_length(16));
130        tag.as_mut().copy_from_slice(&tag_bytes);
131        tag
132    }
133
134    fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
135        if tag.len() != Self::TAG_SIZE {
136            return Err(AeadError::InvalidCiphertext);
137        }
138        let nonce: &[u8; 16] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
139
140        let mut state = self.init_state(nonce);
141        Self::process_ad(&mut state, aad);
142        state.apply_domain_sep();
143
144        let rem_len = in_out.len() % 16;
145
146        let (chunks, remainder) = in_out.as_chunks_mut::<16>();
147        for chunk in chunks {
148            state.decrypt_in_place_block(chunk);
149            p8(&mut state);
150        }
151
152        if rem_len > 0 {
153            let mut ct = [0u8; 16];
154            ct[..rem_len].copy_from_slice(remainder);
155            state.read_rate_bytes(remainder);
156            for j in 0..rem_len {
157                remainder[j] ^= ct[j];
158            }
159            state.apply_aead_pad(rem_len);
160            state.write_rate_bytes(&ct[..rem_len]);
161        } else {
162            state.apply_aead_pad(0);
163        }
164
165        state.xor_word(2, self.k0);
166        state.xor_word(3, self.k1);
167        p12(&mut state);
168
169        let computed = self.compute_tag(&state);
170
171        if !constant_time_eq::constant_time_eq(&computed, tag) {
172            in_out.fill(0);
173            return Err(AeadError::InvalidCiphertext);
174        }
175
176        Ok(())
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::Aead;
184
185    static KEY: [u8; 16] = [
186        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
187    ];
188    static NONCE: [u8; 16] = [
189        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
190    ];
191
192    #[test]
193    fn empty_pt_empty_ad() {
194        let ae = AsconAead128::new(&KEY);
195        let mut pt = vec![];
196        let tag = ae.encrypt_in_place(&mut pt, &NONCE, &[]);
197        let expected_ct = hex::decode("").unwrap();
198        let expected_tag = hex::decode("4F9C278211BEC9316BF68F46EE8B2EC6").unwrap();
199        assert_eq!(pt, expected_ct);
200        assert_eq!(tag.as_ref(), expected_tag.as_slice());
201    }
202
203    #[test]
204    fn one_byte_pt_empty_ad() {
205        let ae = AsconAead128::new(&KEY);
206        let mut pt = hex::decode("20").unwrap();
207        let tag = ae.encrypt_in_place(&mut pt, &NONCE, &[]);
208        // CT line: E8DD576ABA1CD3E6FC704DE02AEDB79588 (34 hex chars = 17 bytes)
209        // ciphertext = first byte = E8, tag = remaining 16 = DD576ABA1CD3E6FC704DE02AEDB79588
210        let expected_ct = hex::decode("E8").unwrap();
211        let expected_tag = hex::decode("DD576ABA1CD3E6FC704DE02AEDB79588").unwrap();
212        assert_eq!(pt, expected_ct);
213        assert_eq!(tag.as_ref(), expected_tag.as_slice());
214    }
215
216    #[test]
217    fn one_byte_pt_one_byte_ad() {
218        let ae = AsconAead128::new(&KEY);
219        let mut pt = hex::decode("20").unwrap();
220        let aad = hex::decode("30").unwrap();
221        let tag = ae.encrypt_in_place(&mut pt, &NONCE, &aad);
222        // ciphertext=96, tag=2B8016836C75A7D86866588CA245D886
223        let expected_ct = hex::decode("96").unwrap();
224        let expected_tag = hex::decode("2B8016836C75A7D86866588CA245D886").unwrap();
225        assert_eq!(pt, expected_ct);
226        assert_eq!(tag.as_ref(), expected_tag.as_slice());
227    }
228
229    #[test]
230    fn two_byte_pt_six_byte_ad() {
231        let ae = AsconAead128::new(&KEY);
232        let mut pt = hex::decode("2021").unwrap();
233        let aad = hex::decode("303132333435").unwrap();
234        let tag = ae.encrypt_in_place(&mut pt, &NONCE, &aad);
235        // ciphertext=9310, tag=6848C186CA92DCC20741A92F7AAFE673
236        let expected_ct = hex::decode("9310").unwrap();
237        let expected_tag = hex::decode("6848C186CA92DCC20741A92F7AAFE673").unwrap();
238        assert_eq!(pt, expected_ct);
239        assert_eq!(tag.as_ref(), expected_tag.as_slice());
240    }
241
242    #[test]
243    fn thirtytwo_byte_pt_twentysix_byte_ad() {
244        let ae = AsconAead128::new(&KEY);
245        let mut pt = hex::decode("202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F").unwrap();
246        let aad = hex::decode("303132333435363738393A3B3C3D3E3F40414243444546474849").unwrap();
247        let tag = ae.encrypt_in_place(&mut pt, &NONCE, &aad);
248        // ciphertext=32 bytes, tag=16 bytes
249        let expected_ct = hex::decode("A92EF70DF2EF0FAA74A21F9739FB89237DF62F9A2B4080B850046DDD386DED48").unwrap();
250        let expected_tag = hex::decode("E7833CF56F755945AEB70D2BAAAA361C").unwrap();
251        assert_eq!(pt, expected_ct);
252        assert_eq!(tag.as_ref(), expected_tag.as_slice());
253    }
254
255    #[test]
256    fn roundtrip() {
257        let key = [0x55u8; 16];
258        let nonce = [0xAAu8; 16];
259        let aad = b"associated data";
260        let plaintext = b"hello, world!";
261
262        let ae = AsconAead128::new(&key);
263        let mut ct = plaintext.to_vec();
264        let tag = ae.encrypt_in_place(&mut ct, &nonce, aad);
265
266        let mut decrypted = ct.clone();
267        ae.decrypt_in_place(&mut decrypted, &nonce, aad, tag.as_ref()).unwrap();
268        assert_eq!(decrypted, plaintext);
269    }
270
271    #[test]
272    fn roundtrip_empty() {
273        let key = [0x55u8; 16];
274        let nonce = [0xAAu8; 16];
275
276        let ae = AsconAead128::new(&key);
277        let mut ct = vec![];
278        let tag = ae.encrypt_in_place(&mut ct, &nonce, b"ad");
279
280        let mut decrypted = ct.clone();
281        ae.decrypt_in_place(&mut decrypted, &nonce, b"ad", tag.as_ref())
282            .unwrap();
283        assert!(decrypted.is_empty());
284    }
285
286    #[test]
287    fn tampered_tag_fails() {
288        let key = [0x55u8; 16];
289        let nonce = [0xAAu8; 16];
290
291        let ae = AsconAead128::new(&key);
292        let mut ct = b"secret".to_vec();
293        let mut tag = ae.encrypt_in_place(&mut ct, &nonce, b"");
294        tag.as_mut()[0] ^= 1;
295
296        let mut decrypted = ct.clone();
297        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
298        assert!(result.is_err());
299        assert!(decrypted.iter().all(|b| *b == 0));
300    }
301
302    #[test]
303    fn tampered_ciphertext_fails() {
304        let key = [0x55u8; 16];
305        let nonce = [0xAAu8; 16];
306
307        let ae = AsconAead128::new(&key);
308        let mut ct = b"secret".to_vec();
309        let tag = ae.encrypt_in_place(&mut ct, &nonce, b"");
310        ct[3] ^= 1;
311
312        let mut decrypted = ct.clone();
313        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
314        assert!(result.is_err());
315    }
316
317    #[test]
318    fn wrong_nonce_fails() {
319        let key = [0x55u8; 16];
320
321        let ae = AsconAead128::new(&key);
322        let mut ct = b"secret".to_vec();
323        let tag = ae.encrypt_in_place(&mut ct, &[0u8; 16], b"");
324        let mut decrypted = ct.clone();
325        let result = ae.decrypt_in_place(&mut decrypted, &[1u8; 16], b"", tag.as_ref());
326        assert!(result.is_err());
327    }
328
329    #[test]
330    fn wrong_ad_fails() {
331        let key = [0x55u8; 16];
332        let nonce = [0xAAu8; 16];
333
334        let ae = AsconAead128::new(&key);
335        let mut ct = b"secret".to_vec();
336        let tag = ae.encrypt_in_place(&mut ct, &nonce, b"correct ad");
337        let mut decrypted = ct.clone();
338        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"wrong ad", tag.as_ref());
339        assert!(result.is_err());
340    }
341
342    #[test]
343    fn wrong_key_fails() {
344        let nonce = [0xAAu8; 16];
345
346        let ae = AsconAead128::new(&[0x55u8; 16]);
347        let mut ct = b"secret".to_vec();
348        let tag = ae.encrypt_in_place(&mut ct, &nonce, b"");
349        let mut decrypted = ct.clone();
350        let ae2 = AsconAead128::new(&[0xAAu8; 16]);
351        let result = ae2.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
352        assert!(result.is_err());
353    }
354
355    #[test]
356    fn empty_ad_with_data() {
357        let key = [0x55u8; 16];
358        let nonce = [0xAAu8; 16];
359
360        let ae = AsconAead128::new(&key);
361        let mut ct = b"data with empty AD".to_vec();
362        let tag = ae.encrypt_in_place(&mut ct, &nonce, b"");
363
364        let mut decrypted = ct.clone();
365        ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref()).unwrap();
366        assert_eq!(decrypted, b"data with empty AD");
367    }
368
369    #[test]
370    fn kat_vectors() {
371        let data = include_str!("../../testdata/ascon/LWC_AEAD_KAT_128_128.txt");
372        let mut count = 0u64;
373        let mut key = None;
374        let mut nonce = None;
375        let mut pt_hex = String::new();
376        let mut ad_hex = String::new();
377        let mut ct_hex = String::new();
378
379        for line in data.lines() {
380            let line = line.trim();
381            if line.is_empty() {
382                if let (Some(k), Some(n)) = (&key, &nonce) {
383                    let key_bytes: [u8; 16] = hex::decode(k).unwrap().try_into().unwrap();
384                    let nonce_bytes: [u8; 16] = hex::decode(n).unwrap().try_into().unwrap();
385                    let pt = hex::decode(&pt_hex).unwrap();
386                    let ct_bytes = hex::decode(&ct_hex).unwrap();
387                    let aad = hex::decode(&ad_hex).unwrap();
388
389                    let expected_ct = &ct_bytes[..pt.len()];
390                    let expected_tag = &ct_bytes[pt.len()..];
391
392                    let ae = AsconAead128::new(&key_bytes);
393                    let mut enc = pt.clone();
394                    let tag = ae.encrypt_in_place(&mut enc, &nonce_bytes, &aad);
395                    assert_eq!(enc, expected_ct, "KAT AEAD Count={count} ct mismatch");
396                    assert_eq!(tag.as_ref(), expected_tag, "KAT AEAD Count={count} tag mismatch");
397
398                    let mut dec = expected_ct.to_vec();
399                    ae.decrypt_in_place(&mut dec, &nonce_bytes, &aad, expected_tag)
400                        .unwrap_or_else(|e| panic!("KAT AEAD Count={count} decrypt failed: {e:?}"));
401                    assert_eq!(dec, pt, "KAT AEAD Count={count} decrypt pt mismatch");
402                }
403                // Reset
404                key = None;
405                nonce = None;
406                pt_hex.clear();
407                ad_hex.clear();
408                ct_hex.clear();
409                continue;
410            }
411
412            if line.starts_with("Count = ") {
413                count = line["Count = ".len()..].parse().unwrap();
414                continue;
415            }
416            if line.starts_with("Key = ") {
417                key = Some(line[6..].to_string());
418                continue;
419            }
420            if line.starts_with("Nonce = ") {
421                nonce = Some(line[8..].to_string());
422                continue;
423            }
424            if line.starts_with("PT = ") {
425                pt_hex = line[5..].to_string();
426                continue;
427            }
428            if line.starts_with("AD = ") {
429                ad_hex = line[5..].to_string();
430                continue;
431            }
432            if line.starts_with("CT = ") {
433                ct_hex = line[5..].to_string();
434                continue;
435            }
436        }
437
438        // Process last entry
439        if let (Some(k), Some(n)) = (&key, &nonce) {
440            let key_bytes: [u8; 16] = hex::decode(k).unwrap().try_into().unwrap();
441            let nonce_bytes: [u8; 16] = hex::decode(n).unwrap().try_into().unwrap();
442            let pt = hex::decode(&pt_hex).unwrap();
443            let ct_bytes = hex::decode(&ct_hex).unwrap();
444            let aad = hex::decode(&ad_hex).unwrap();
445
446            let expected_ct = &ct_bytes[..pt.len()];
447            let expected_tag = &ct_bytes[pt.len()..];
448
449            let ae = AsconAead128::new(&key_bytes);
450            let mut enc = pt.clone();
451            let tag = ae.encrypt_in_place(&mut enc, &nonce_bytes, &aad);
452            assert_eq!(enc, expected_ct, "KAT AEAD Count={count} ct mismatch");
453            assert_eq!(tag.as_ref(), expected_tag, "KAT AEAD Count={count} tag mismatch");
454
455            let mut dec = expected_ct.to_vec();
456            ae.decrypt_in_place(&mut dec, &nonce_bytes, &aad, expected_tag)
457                .unwrap_or_else(|e| panic!("KAT AEAD Count={count} decrypt failed: {e:?}"));
458            assert_eq!(dec, pt, "KAT AEAD Count={count} decrypt pt mismatch");
459        }
460    }
461}