Skip to main content

crypto/aes/
aes_ctr.rs

1#![allow(unsafe_op_in_unsafe_fn)]
2
3use super::aes::expand_key;
4use crate::{StreamCipher, aes::RoundKeys};
5
6/// AES-128 in CTR mode.
7///
8/// Create a new cipher with [`new`](Aes128Ctr::new).
9/// [`xor_keystream`](StreamCipher::xor_keystream) to encrypt or decrypt
10/// (CTR mode is symmetric).
11/// You can move in the keystream with [`set_counter`](Aes128Ctr::set_counter).
12///
13/// The counter is a full 128-bit big-endian block and is incremented as a
14/// whole (NIST SP 800-38A §5.1) after every block, so the keystream would only
15/// repeat after 2¹²⁸ blocks. Encrypting more than 2³² blocks under one key and
16/// counter is therefore not silently truncated at the 32-bit boundary.
17#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
18pub struct Aes128Ctr(pub(crate) AesCtr<11>);
19
20impl Aes128Ctr {
21    /// Create a new AES-128-CTR stream cipher from a 16-byte key.
22    ///
23    /// The initial counter is zeroed.
24    #[inline]
25    pub fn new(key: &[u8; 16]) -> Self {
26        Self(AesCtr::<11>::new(key))
27    }
28
29    /// Set the 16-byte counter block.
30    ///
31    /// For GCM this is `nonce || 0x00000002` (J₀ + 1).
32    #[inline]
33    pub fn set_counter(&mut self, counter: &[u8; 16]) {
34        self.0.set_counter(counter)
35    }
36}
37
38impl StreamCipher for Aes128Ctr {
39    #[inline]
40    fn xor_keystream(&mut self, in_out: &mut [u8]) {
41        self.0.xor_keystream(in_out)
42    }
43}
44
45/// AES-256 in CTR mode.
46///
47/// Create a new cipher with [`new`](Aes256Ctr::new).
48/// [`xor_keystream`](StreamCipher::xor_keystream) to encrypt or decrypt
49/// (CTR mode is symmetric).
50/// You can move in the keystream with [`set_counter`](Aes256Ctr::set_counter).
51///
52/// The counter is a full 128-bit big-endian block and is incremented as a
53/// whole (NIST SP 800-38A §5.1) after every block, so the keystream would only
54/// repeat after 2¹²⁸ blocks.
55#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
56pub struct Aes256Ctr(pub(crate) AesCtr<15>);
57
58impl Aes256Ctr {
59    /// Create a new AES-256-CTR stream cipher from a 32-byte key.
60    ///
61    /// The initial counter is zeroed.
62    #[inline]
63    pub fn new(key: &[u8; 32]) -> Self {
64        Self(AesCtr::<15>::new(key))
65    }
66
67    /// Set the 16-byte counter block.
68    ///
69    /// For GCM this is `nonce || 0x00000002` (J₀ + 1).
70    #[inline]
71    pub fn set_counter(&mut self, counter: &[u8; 16]) {
72        self.0.set_counter(counter)
73    }
74}
75
76impl StreamCipher for Aes256Ctr {
77    #[inline]
78    fn xor_keystream(&mut self, in_out: &mut [u8]) {
79        self.0.xor_keystream(in_out)
80    }
81}
82
83////////////////////////////////////////////////////////////////////////////////////////////////////
84////////////////////////////////////////////////////////////////////////////////////////////////////
85
86/// Generic implementation of AES in counter mode.
87/// `N` is the number of round keys. 11 for AES-128 and 15 for AES-256
88#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
89pub(crate) struct AesCtr<const N: usize> {
90    round_keys: RoundKeys<N>,
91    counter: [u8; 16],
92}
93
94impl AesCtr<11> {
95    pub fn new(key: &[u8; 16]) -> Self {
96        Self::new_inner(key)
97    }
98}
99
100impl AesCtr<15> {
101    pub fn new(key: &[u8; 32]) -> Self {
102        Self::new_inner(key)
103    }
104}
105
106impl<const N: usize> AesCtr<N> {
107    /// Create a new cipher from a 16-byte key.
108    ///
109    /// The initial counter is zeroed.
110    fn new_inner(key: &[u8]) -> Self {
111        const { assert!(N == 11 || N == 15) };
112
113        let round_keys_software = expand_key(key);
114
115        #[cfg(target_arch = "aarch64")]
116        {
117            #[cfg(feature = "std")]
118            if std::arch::is_aarch64_feature_detected!("aes") {
119                return AesCtr {
120                    round_keys: RoundKeys::Armv8(super::aes_arm64::expand_key_armv8(round_keys_software)),
121                    counter: [0u8; 16],
122                };
123            }
124
125            #[cfg(all(not(feature = "std"), target_feature = "aes"))]
126            return AesCtr {
127                round_keys: RoundKeys::Armv8(super::aes_arm64::expand_key_armv8(round_keys_software)),
128                counter: [0u8; 16],
129            };
130        }
131
132        #[cfg(target_arch = "x86_64")]
133        {
134            // NOTE: we don't dynamically check for sse2 as we assume it to be available if ssse3 is available
135            #[cfg(feature = "std")]
136            if std::arch::is_x86_feature_detected!("aes") && std::arch::is_x86_feature_detected!("ssse3") {
137                return AesCtr {
138                    round_keys: RoundKeys::X86_64(super::aes_amd64::expand_key_x86_64(round_keys_software)),
139                    counter: [0u8; 16],
140                };
141            }
142
143            #[cfg(all(
144                not(feature = "std"),
145                target_feature = "aes",
146                target_feature = "ssse3",
147                target_feature = "sse2"
148            ))]
149            return AesCtr {
150                round_keys: RoundKeys::X86_64(super::aes_amd64::expand_key_x86_64(round_keys_software)),
151                counter: [0u8; 16],
152            };
153        }
154
155        AesCtr {
156            round_keys: RoundKeys::Software(round_keys_software),
157            counter: [0u8; 16],
158        }
159    }
160
161    pub(crate) fn xor_keystream(&mut self, in_out: &mut [u8]) {
162        match &self.round_keys {
163            #[cfg(target_arch = "aarch64")]
164            RoundKeys::Armv8(round_keys) => unsafe {
165                use super::aes_ctr_arm64::xor_keystream_armv8;
166                xor_keystream_armv8(round_keys, &mut self.counter, in_out);
167            },
168            #[cfg(target_arch = "x86_64")]
169            RoundKeys::X86_64(round_keys) => unsafe {
170                use super::aes_ctr_amd64::xor_keystream_aesni;
171                xor_keystream_aesni(round_keys, &mut self.counter, in_out);
172            },
173            RoundKeys::Software(round_keys) => xor_keystream_soft(round_keys, &mut self.counter, in_out),
174        }
175    }
176
177    /// Set the 16-byte counter block.
178    ///
179    /// For GCM this is `nonce || 0x00000002` (J₀ + 1).
180    #[inline]
181    pub fn set_counter(&mut self, counter: &[u8; 16]) {
182        self.counter = *counter;
183    }
184}
185
186fn xor_keystream_soft<const N: usize>(round_keys: &[[u8; 16]; N], counter: &mut [u8; 16], in_out: &mut [u8]) {
187    super::aes_ct::xor_keystream_soft(round_keys, counter, in_out);
188}
189
190/// Increment the 16-byte big-endian counter block by one.
191///
192/// This is the NIST SP 800-38A §5.1 standard incrementing function applied to
193/// the full 128-bit block: a carry propagates through the whole block instead
194/// of wrapping the low 32 bits. The keystream therefore only repeats after
195/// 2¹²⁸ blocks. The counter is public data, so the data-dependent early exit
196/// is not a timing concern.
197#[cfg(test)]
198#[inline]
199fn increment_counter(counter: &mut [u8; 16]) {
200    for byte in counter.iter_mut().rev() {
201        let (value, carry) = byte.overflowing_add(1);
202        *byte = value;
203        if !carry {
204            break;
205        }
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use hex;
212
213    use super::*;
214
215    struct CtrVector {
216        key: &'static str,
217        counter: &'static str,
218        plaintext: &'static str,
219        ciphertext: &'static str,
220    }
221
222    // NIST SP 800-38A – Appendix F.5.1 CTR-AES128.Encrypt
223    // Each vector shows a single block with its corresponding counter block value
224    // (the counter is incremented in the last 4 bytes after each block).
225    const NIST_CTR_128_VECTORS: &[CtrVector] = &[
226        CtrVector {
227            key: "2b7e151628aed2a6abf7158809cf4f3c",
228            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
229            plaintext: "6bc1bee22e409f96e93d7e117393172a",
230            ciphertext: "874d6191b620e3261bef6864990db6ce",
231        },
232        CtrVector {
233            key: "2b7e151628aed2a6abf7158809cf4f3c",
234            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff00",
235            plaintext: "ae2d8a571e03ac9c9eb76fac45af8e51",
236            ciphertext: "9806f66b7970fdff8617187bb9fffdff",
237        },
238        CtrVector {
239            key: "2b7e151628aed2a6abf7158809cf4f3c",
240            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff01",
241            plaintext: "30c81c46a35ce411e5fbc1191a0a52ef",
242            ciphertext: "5ae4df3edbd5d35e5b4f09020db03eab",
243        },
244        CtrVector {
245            key: "2b7e151628aed2a6abf7158809cf4f3c",
246            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff02",
247            plaintext: "f69f2445df4f9b17ad2b417be66c3710",
248            ciphertext: "1e031dda2fbe03d1792170a0f3009cee",
249        },
250    ];
251
252    // NIST SP 800-38A – Appendix F.5.3 CTR-AES256.Encrypt
253    const NIST_CTR_256_VECTORS: &[CtrVector] = &[
254        CtrVector {
255            key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
256            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
257            plaintext: "6bc1bee22e409f96e93d7e117393172a",
258            ciphertext: "601ec313775789a5b7a7f504bbf3d228",
259        },
260        CtrVector {
261            key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
262            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff00",
263            plaintext: "ae2d8a571e03ac9c9eb76fac45af8e51",
264            ciphertext: "f443e3ca4d62b59aca84e990cacaf5c5",
265        },
266        CtrVector {
267            key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
268            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff01",
269            plaintext: "30c81c46a35ce411e5fbc1191a0a52ef",
270            ciphertext: "2b0930daa23de94ce87017ba2d84988d",
271        },
272        CtrVector {
273            key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
274            counter: "f0f1f2f3f4f5f6f7f8f9fafbfcfdff02",
275            plaintext: "f69f2445df4f9b17ad2b417be66c3710",
276            ciphertext: "dfc9c58db67aada613c2dd08457941a6",
277        },
278    ];
279
280    use super::super::aes::{encrypt_block, expand_key};
281
282    fn run_ctr_vector_128(v: &CtrVector) {
283        let key: [u8; 16] = hex::decode_array::<16>(v.key.as_bytes()).unwrap();
284        let counter: [u8; 16] = hex::decode_array::<16>(v.counter.as_bytes()).unwrap();
285        let pt = hex::decode(v.plaintext).unwrap();
286        let expected_ct = hex::decode(v.ciphertext).unwrap();
287
288        let mut buf = pt.clone();
289        let mut cipher = Aes128Ctr::new(&key);
290        cipher.set_counter(&counter);
291        cipher.xor_keystream(&mut buf);
292        assert_eq!(buf, expected_ct, "AES-128-CTR mismatch for key={}", v.key);
293
294        let mut buf_soft = pt.clone();
295        let mut ctr_soft = counter;
296        let rk: [[u8; 16]; 11] = expand_key::<11>(&key);
297        xor_keystream_soft(&rk, &mut ctr_soft, &mut buf_soft);
298        assert_eq!(buf_soft, expected_ct, "AES-128-CTR soft mismatch for key={}", v.key);
299
300        assert_eq!(buf, buf_soft, "dispatch and soft xor_keystream differ for key={}", v.key);
301    }
302
303    fn run_ctr_vector_256(v: &CtrVector) {
304        let key: [u8; 32] = hex::decode_array::<32>(v.key.as_bytes()).unwrap();
305        let counter: [u8; 16] = hex::decode_array::<16>(v.counter.as_bytes()).unwrap();
306        let pt = hex::decode(v.plaintext).unwrap();
307        let expected_ct = hex::decode(v.ciphertext).unwrap();
308
309        let mut buf = pt.clone();
310        let mut cipher = Aes256Ctr::new(&key);
311        cipher.set_counter(&counter);
312        cipher.xor_keystream(&mut buf);
313        assert_eq!(buf, expected_ct, "AES-256-CTR mismatch for key={}", v.key);
314
315        let mut buf_soft = pt.clone();
316        let mut ctr_soft = counter;
317        let rk: [[u8; 16]; 15] = expand_key::<15>(&key);
318        xor_keystream_soft(&rk, &mut ctr_soft, &mut buf_soft);
319        assert_eq!(buf_soft, expected_ct, "AES-256-CTR soft mismatch for key={}", v.key);
320
321        assert_eq!(buf, buf_soft, "dispatch and soft xor_keystream differ for key={}", v.key);
322    }
323
324    #[test]
325    fn nist_aes128_ctr_vectors() {
326        for v in NIST_CTR_128_VECTORS {
327            run_ctr_vector_128(v);
328        }
329    }
330
331    #[test]
332    fn nist_aes128_ctr_combined_blocks() {
333        // Encrypt all 4 blocks in one call to verify counter chaining
334        let key: [u8; 16] = hex::decode_array::<16>(b"2b7e151628aed2a6abf7158809cf4f3c").unwrap();
335        let counter: [u8; 16] = hex::decode_array::<16>(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
336        let pt = hex::decode(b"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710").unwrap();
337        let expected_ct = hex::decode(b"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee").unwrap();
338
339        let mut buf = pt.clone();
340        let mut cipher = Aes128Ctr::new(&key);
341        cipher.set_counter(&counter);
342        cipher.xor_keystream(&mut buf);
343        assert_eq!(buf, expected_ct);
344
345        let mut buf_soft = pt.clone();
346        let mut ctr_soft = counter;
347        let rk: [[u8; 16]; 11] = expand_key::<11>(&key);
348        xor_keystream_soft(&rk, &mut ctr_soft, &mut buf_soft);
349        assert_eq!(buf_soft, expected_ct, "AES-128-CTR soft multi-block mismatch");
350        assert_eq!(buf, buf_soft, "dispatch and soft xor_keystream differ on multi-block");
351    }
352
353    #[test]
354    fn nist_aes256_ctr_vectors() {
355        for v in NIST_CTR_256_VECTORS {
356            run_ctr_vector_256(v);
357        }
358    }
359
360    #[test]
361    fn nist_aes256_ctr_combined_blocks() {
362        let key: [u8; 32] =
363            hex::decode_array::<32>(b"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap();
364        let counter: [u8; 16] = hex::decode_array::<16>(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
365        let pt = hex::decode(b"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710").unwrap();
366        let expected_ct = hex::decode(b"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6").unwrap();
367
368        let mut buf = pt.clone();
369        let mut cipher = Aes256Ctr::new(&key);
370        cipher.set_counter(&counter);
371        cipher.xor_keystream(&mut buf);
372        assert_eq!(buf, expected_ct);
373
374        let mut buf_soft = pt.clone();
375        let mut ctr_soft = counter;
376        let rk: [[u8; 16]; 15] = expand_key::<15>(&key);
377        xor_keystream_soft(&rk, &mut ctr_soft, &mut buf_soft);
378        assert_eq!(buf_soft, expected_ct, "AES-256-CTR soft multi-block mismatch");
379        assert_eq!(buf, buf_soft, "dispatch and soft xor_keystream differ on multi-block");
380    }
381
382    #[test]
383    fn aes128_ctr_empty_plaintext() {
384        let key = [0xabu8; 16];
385        let counter = [0x01u8; 16];
386        let mut buf: Vec<u8> = vec![];
387        let mut cipher = Aes128Ctr::new(&key);
388        cipher.set_counter(&counter);
389        cipher.xor_keystream(&mut buf);
390        assert!(buf.is_empty());
391    }
392
393    #[test]
394    fn aes128_ctr_partial_block() {
395        let key: [u8; 16] = hex::decode_array::<16>(b"2b7e151628aed2a6abf7158809cf4f3c").unwrap();
396        let counter: [u8; 16] = hex::decode_array::<16>(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
397        // First 5 bytes of the first NIST block
398        let pt = hex::decode(b"6bc1bee22e").unwrap();
399        let expected_ct = hex::decode(b"874d6191b6").unwrap();
400
401        let mut buf = pt.clone();
402        let mut cipher = Aes128Ctr::new(&key);
403        cipher.set_counter(&counter);
404        cipher.xor_keystream(&mut buf);
405        assert_eq!(buf, expected_ct);
406    }
407
408    #[test]
409    fn aes128_ctr_cross_block_boundary() {
410        // 17 bytes – spans from block 1 into block 2
411        let key: [u8; 16] = hex::decode_array::<16>(b"2b7e151628aed2a6abf7158809cf4f3c").unwrap();
412        let counter: [u8; 16] = hex::decode_array::<16>(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
413        // Take first block + 1 byte of second block
414        let pt = hex::decode(b"6bc1bee22e409f96e93d7e117393172aae").unwrap();
415        let expected_ct = hex::decode(b"874d6191b620e3261bef6864990db6ce98").unwrap();
416
417        let mut buf = pt.clone();
418        let mut cipher = Aes128Ctr::new(&key);
419        cipher.set_counter(&counter);
420        cipher.xor_keystream(&mut buf);
421        assert_eq!(buf, expected_ct);
422    }
423
424    #[test]
425    fn aes128_ctr_roundtrip() {
426        let key = [0x42u8; 16];
427        let counter = [0x07u8; 16];
428        let pt: Vec<u8> = (0u8..=255u8).cycle().take(1024).collect();
429
430        let mut buf = pt.clone();
431        let mut cipher = Aes128Ctr::new(&key);
432        cipher.set_counter(&counter);
433        cipher.xor_keystream(&mut buf);
434        // XOR again with same keystream yields original plaintext
435        let mut cipher2 = Aes128Ctr::new(&key);
436        cipher2.set_counter(&counter);
437        cipher2.xor_keystream(&mut buf);
438        assert_eq!(buf, pt);
439    }
440
441    #[test]
442    fn aes256_ctr_roundtrip() {
443        let key = [0x42u8; 32];
444        let counter = [0x07u8; 16];
445        let pt: Vec<u8> = (0u8..=255u8).cycle().take(1024).collect();
446
447        let mut buf = pt.clone();
448        let mut cipher = Aes256Ctr::new(&key);
449        cipher.set_counter(&counter);
450        cipher.xor_keystream(&mut buf);
451        let mut cipher2 = Aes256Ctr::new(&key);
452        cipher2.set_counter(&counter);
453        cipher2.xor_keystream(&mut buf);
454        assert_eq!(buf, pt);
455    }
456
457    #[test]
458    fn aes128_ctr_zero_key_zero_counter() {
459        let key = [0u8; 16];
460        let counter = [0u8; 16];
461        let pt = [0u8; 16];
462        let expected_keystream: [u8; 16] = hex::decode_array::<16>(b"66e94bd4ef8a2c3b884cfa59ca342b2e").unwrap();
463
464        let mut buf = pt.to_vec();
465        let mut cipher = Aes128Ctr::new(&key);
466        cipher.set_counter(&counter);
467        cipher.xor_keystream(&mut buf);
468        assert_eq!(buf, expected_keystream.to_vec());
469
470        // decrypt (XOR again)
471        let mut cipher2 = Aes128Ctr::new(&key);
472        cipher2.set_counter(&counter);
473        cipher2.xor_keystream(&mut buf);
474        assert_eq!(buf, pt.to_vec());
475    }
476
477    #[test]
478    fn aes128_ctr_increment_counter() {
479        let mut ctr = [0u8; 16];
480        increment_counter(&mut ctr);
481        assert_eq!(ctr, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
482
483        // Full 128-bit wrap: all-ones becomes all-zeros (no 32-bit truncation).
484        let mut ctr2 = [0xffu8; 16];
485        increment_counter(&mut ctr2);
486        assert_eq!(ctr2, [0u8; 16]);
487
488        // Carry propagates out of the low 32-bit word.
489        let mut ctr3 = [0u8; 16];
490        ctr3[12..16].copy_from_slice(&u32::MAX.to_be_bytes());
491        increment_counter(&mut ctr3);
492        let mut expected3 = [0u8; 16];
493        expected3[11] = 1;
494        assert_eq!(ctr3, expected3);
495    }
496
497    #[test]
498    fn aes128_ctr_no_32bit_wrap() {
499        // Starting from low word 0xFFFFFFFF, the second block must use the
500        // counter with a carry into byte 11 instead of reusing counter 0.
501        let key: [u8; 16] = hex::decode_array::<16>(b"2b7e151628aed2a6abf7158809cf4f3c").unwrap();
502        let mut counter0 = [0u8; 16];
503        counter0[12..16].copy_from_slice(&u32::MAX.to_be_bytes());
504        let mut counter1 = [0u8; 16];
505        counter1[11] = 1;
506
507        let rk: [[u8; 16]; 11] = expand_key::<11>(&key);
508        let ks0 = encrypt_block(&rk, &counter0);
509        let ks1 = encrypt_block(&rk, &counter1);
510
511        let mut expected = [0u8; 32];
512        expected[..16].copy_from_slice(&ks0);
513        expected[16..].copy_from_slice(&ks1);
514
515        let mut buf = [0u8; 32];
516        let mut cipher = Aes128Ctr::new(&key);
517        cipher.set_counter(&counter0);
518        cipher.xor_keystream(&mut buf);
519        assert_eq!(buf, expected, "dispatch must carry the counter into byte 11");
520
521        let mut buf_soft = [0u8; 32];
522        let mut ctr_soft = counter0;
523        xor_keystream_soft(&rk, &mut ctr_soft, &mut buf_soft);
524        assert_eq!(buf_soft, expected, "soft path must carry the counter into byte 11");
525    }
526}