1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Counter (CTR) mode — turns a [`BlockCipher`] into a stream cipher.
//!
//! The keystream is `E(C₀) ‖ E(C₁) ‖ …` where the counter block is incremented
//! as a big-endian 128-bit integer between blocks. Encryption and decryption
//! are the same operation (XOR with the keystream), so a single
//! [`apply_keystream`](Ctr::apply_keystream) serves both.
//!
//! CTR provides confidentiality only — it is unauthenticated and malleable.
//! For integrity, use an AEAD such as AES-GCM. A given (key, counter) prefix
//! must never be reused.
use super::BlockCipher;
/// CTR-mode stream wrapper around a block cipher.
#[derive(Clone)]
pub struct Ctr<C: BlockCipher> {
cipher: C,
counter: [u8; 16],
keystream: [u8; 16],
/// Bytes of `keystream` already consumed (`16` ⇒ a fresh block is needed).
pos: usize,
}
impl<C: BlockCipher> Ctr<C> {
/// Creates a CTR stream from `cipher` and an initial 16-byte counter block
/// (nonce ‖ counter).
#[inline]
pub fn new(cipher: C, iv: &[u8; 16]) -> Self {
Ctr {
cipher,
counter: *iv,
keystream: [0u8; 16],
pos: 16, // force generation on first use
}
}
/// Generates the next keystream block and advances the counter.
#[inline]
fn refill(&mut self) {
self.keystream = self.counter;
self.cipher.encrypt_block(&mut self.keystream);
increment(&mut self.counter);
self.pos = 0;
}
/// XORs the keystream into `data` in place, encrypting or decrypting it.
///
/// May be called repeatedly; the keystream continues seamlessly across
/// calls regardless of how the data is chunked. Whole blocks are generated a
/// window at a time through the batched
/// [`encrypt_blocks`](BlockCipher::encrypt_blocks), so a hardware AES backend
/// pipelines them; the streamed keystream is byte-for-byte the scalar one.
pub fn apply_keystream(&mut self, data: &mut [u8]) {
let mut i = 0;
// 1. Consume any leftover keystream from a previous partial block.
while self.pos < 16 && i < data.len() {
data[i] ^= self.keystream[self.pos];
self.pos += 1;
i += 1;
}
// 2. Bulk whole-block windows (only reached with no pending keystream).
const W: usize = 64; // 1 KiB stack window
let mut ks = [0u8; 16 * W];
while data.len() - i >= 16 {
let nblk = ((data.len() - i) / 16).min(W);
for blk in ks[..nblk * 16].chunks_exact_mut(16) {
blk.copy_from_slice(&self.counter);
increment(&mut self.counter);
}
self.cipher.encrypt_blocks(&mut ks[..nblk * 16]);
for k in 0..nblk * 16 {
data[i + k] ^= ks[k];
}
i += nblk * 16;
}
// 3. Trailing partial block: generate one fresh keystream block and
// consume part of it, leaving `pos` for the next call.
if i < data.len() {
self.refill();
while i < data.len() {
data[i] ^= self.keystream[self.pos];
self.pos += 1;
i += 1;
}
}
}
}
impl<C: BlockCipher> Drop for Ctr<C> {
fn drop(&mut self) {
// Best-effort wipe of the residual key-stream block. Uses the same
// `core::hint::black_box`-guarded zeroing as the AES round-key drop
// (`cipher/aes/mod.rs`) so LLVM cannot elide the writes as dead stores.
for b in self.keystream.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&self.keystream);
}
}
/// Increments a 16-byte big-endian counter in place, wrapping at 2¹²⁸.
#[inline]
fn increment(counter: &mut [u8; 16]) {
for byte in counter.iter_mut().rev() {
*byte = byte.wrapping_add(1);
if *byte != 0 {
break;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cipher::{Aes128, Aes256};
use crate::test_util::from_hex;
// NIST SP 800-38A F.5.1 / F.5.5 CTR-AES vectors. The 64-byte message is the
// standard four-block plaintext; the counter starts at f0f1..ff.
const PLAINTEXT: &str = "6bc1bee22e409f96e93d7e117393172a\
ae2d8a571e03ac9c9eb76fac45af8e51\
30c81c46a35ce411e5fbc1191a0a52ef\
f69f2445df4f9b17ad2b417be66c3710";
const IV: &str = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
#[test]
fn ctr_aes128() {
let key = from_hex::<16>("2b7e151628aed2a6abf7158809cf4f3c");
let iv = from_hex::<16>(IV);
let mut buf = from_hex::<64>(PLAINTEXT);
let expected = from_hex::<64>(
"874d6191b620e3261bef6864990db6ce\
9806f66b7970fdff8617187bb9fffdff\
5ae4df3edbd5d35e5b4f09020db03eab\
1e031dda2fbe03d1792170a0f3009cee",
);
Ctr::new(Aes128::new(&key), &iv).apply_keystream(&mut buf);
assert_eq!(buf, expected);
// CTR is its own inverse.
Ctr::new(Aes128::new(&key), &iv).apply_keystream(&mut buf);
assert_eq!(buf, from_hex::<64>(PLAINTEXT));
}
#[test]
fn ctr_aes256() {
let key = from_hex::<32>(
"603deb1015ca71be2b73aef0857d7781\
1f352c073b6108d72d9810a30914dff4",
);
let iv = from_hex::<16>(IV);
let mut buf = from_hex::<64>(PLAINTEXT);
let expected = from_hex::<64>(
"601ec313775789a5b7a7f504bbf3d228\
f443e3ca4d62b59aca84e990cacaf5c5\
2b0930daa23de94ce87017ba2d84988d\
dfc9c58db67aada613c2dd08457941a6",
);
Ctr::new(Aes256::new(&key), &iv).apply_keystream(&mut buf);
assert_eq!(buf, expected);
}
#[test]
fn streaming_matches_oneshot() {
let key = from_hex::<16>("2b7e151628aed2a6abf7158809cf4f3c");
let iv = from_hex::<16>(IV);
let plaintext = from_hex::<64>(PLAINTEXT);
let mut oneshot = plaintext;
Ctr::new(Aes128::new(&key), &iv).apply_keystream(&mut oneshot);
// Encrypt in irregular chunks that straddle block boundaries.
let mut chunked = plaintext;
let mut ctr = Ctr::new(Aes128::new(&key), &iv);
let mut start = 0;
for len in [1usize, 14, 1, 30, 18] {
ctr.apply_keystream(&mut chunked[start..start + len]);
start += len;
}
assert_eq!(chunked, oneshot);
}
#[test]
fn counter_carry() {
let mut c = from_hex::<16>("000000000000000000000000000000ff");
increment(&mut c);
assert_eq!(c, from_hex::<16>("00000000000000000000000000000100"));
let mut all_ones = [0xffu8; 16];
increment(&mut all_ones);
assert_eq!(all_ones, [0u8; 16]);
}
}