Skip to main content

crypto/aes/
aes_ctr.rs

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