Skip to main content

cryptography/cprng/
ctr_drbg.rs

1//! `CTR_DRBG` from NIST SP 800-90A Rev. 1 using AES-256 without a derivation
2//! function.
3//!
4//! This implementation intentionally starts with the simplest approved AES
5//! instantiation:
6//!
7//! - block cipher: AES-256
8//! - derivation function: disabled
9//! - seed length: `keylen + outlen = 32 + 16 = 48` bytes
10//!
11//! Because the derivation function is omitted, callers must supply exactly
12//! 48 bytes of already-conditioned seed material for instantiation, reseeding,
13//! and optional additional input. That keeps the implementation small and easy
14//! to audit, but it also means this module intentionally does not try to be a
15//! generic entropy conditioner.
16
17use crate::ct::zeroize_slice;
18use crate::{Aes256, BlockCipher, Csprng};
19
20const KEY_LEN: usize = 32;
21const BLOCK_LEN: usize = 16;
22const SEED_LEN: usize = KEY_LEN + BLOCK_LEN;
23const MAX_REQUEST_BYTES: usize = 1 << 16; // 2^19 bits
24const RESEED_INTERVAL: u64 = 1 << 48;
25
26#[inline]
27fn increment_be(counter: &mut [u8; BLOCK_LEN]) {
28    for b in counter.iter_mut().rev() {
29        let (next, carry) = b.overflowing_add(1);
30        *b = next;
31        if !carry {
32            break;
33        }
34    }
35}
36
37/// `CTR_DRBG` with AES-256 and no derivation function.
38pub struct CtrDrbgAes256 {
39    key: [u8; KEY_LEN],
40    v: [u8; BLOCK_LEN],
41    reseed_counter: u64,
42}
43
44impl CtrDrbgAes256 {
45    /// Instantiate from exactly 48 bytes of seed material.
46    ///
47    /// This is the SP 800-90A "no derivation function" form, so the seed
48    /// material must already be fully conditioned to the required seed length.
49    #[must_use]
50    pub fn new(seed_material: &[u8; SEED_LEN]) -> Self {
51        let mut out = Self {
52            key: [0u8; KEY_LEN],
53            v: [0u8; BLOCK_LEN],
54            reseed_counter: 1,
55        };
56        out.update(Some(seed_material));
57        out
58    }
59
60    /// Instantiate and wipe the caller-provided seed buffer.
61    ///
62    /// This is useful when the caller already has seed material in a mutable
63    /// staging buffer and does not want that seed to remain live after the
64    /// DRBG has absorbed it.
65    pub fn new_wiping(seed_material: &mut [u8; SEED_LEN]) -> Self {
66        let out = Self::new(seed_material);
67        zeroize_slice(seed_material.as_mut_slice());
68        out
69    }
70
71    /// Reseed from fresh 48-byte seed material.
72    ///
73    /// In the no-derivation-function profile, reseeding uses the same exact
74    /// seed length and update path as instantiation.
75    pub fn reseed(&mut self, seed_material: &[u8; SEED_LEN]) {
76        self.update(Some(seed_material));
77        self.reseed_counter = 1;
78    }
79
80    /// Reseed and wipe the caller-provided seed buffer.
81    pub fn reseed_wiping(&mut self, seed_material: &mut [u8; SEED_LEN]) {
82        self.reseed(seed_material);
83        zeroize_slice(seed_material.as_mut_slice());
84    }
85
86    /// Generate output, optionally mixing in 48 bytes of additional input.
87    ///
88    /// The optional additional input uses the same "no derivation function"
89    /// rule as instantiate and reseed: if present, it must already be exactly
90    /// one seed-length block of conditioned material.
91    ///
92    /// # Panics
93    ///
94    /// Panics if the request exceeds the SP 800-90A per-call limit or if the
95    /// reseed counter has reached the mandated reseed interval.
96    pub fn generate(&mut self, out: &mut [u8], additional_input: Option<&[u8; SEED_LEN]>) {
97        assert!(
98            self.reseed_counter <= RESEED_INTERVAL,
99            "CTR_DRBG reseed required"
100        );
101        assert!(out.len() <= MAX_REQUEST_BYTES, "CTR_DRBG request too large");
102
103        if let Some(additional_input) = additional_input {
104            self.update(Some(additional_input));
105        }
106
107        let cipher = Aes256::new(&self.key);
108        let mut offset = 0usize;
109        while offset < out.len() {
110            increment_be(&mut self.v);
111            let mut block = self.v;
112            cipher.encrypt(&mut block);
113            let take = (out.len() - offset).min(BLOCK_LEN);
114            out[offset..offset + take].copy_from_slice(&block[..take]);
115            offset += take;
116        }
117
118        self.update(additional_input);
119        self.reseed_counter += 1;
120    }
121
122    /// Current reseed counter.
123    ///
124    /// This is primarily exposed so callers can inspect how close a long-lived
125    /// instance is to the SP 800-90A reseed limit.
126    #[must_use]
127    pub fn reseed_counter(&self) -> u64 {
128        self.reseed_counter
129    }
130
131    fn update(&mut self, provided_data: Option<&[u8; SEED_LEN]>) {
132        // SP 800-90A Rev.1 CTR_DRBG Update:
133        // 1. temp = Block_Encrypt(Key, V+1) || ... until `seedlen` bytes
134        // 2. temp ^= provided_data (if any)
135        // 3. Key = leftmost keylen bits, V = rightmost outlen bits
136        let cipher = Aes256::new(&self.key);
137        let mut temp = [0u8; SEED_LEN];
138        let mut offset = 0usize;
139
140        while offset < SEED_LEN {
141            increment_be(&mut self.v);
142            let mut block = self.v;
143            cipher.encrypt(&mut block);
144            temp[offset..offset + BLOCK_LEN].copy_from_slice(&block);
145            offset += BLOCK_LEN;
146        }
147
148        if let Some(data) = provided_data {
149            for (t, d) in temp.iter_mut().zip(data.iter()) {
150                *t ^= *d;
151            }
152        }
153
154        self.key.copy_from_slice(&temp[..KEY_LEN]);
155        self.v.copy_from_slice(&temp[KEY_LEN..]);
156        zeroize_slice(temp.as_mut_slice());
157    }
158}
159
160impl Csprng for CtrDrbgAes256 {
161    fn fill_bytes(&mut self, out: &mut [u8]) {
162        self.generate(out, None);
163    }
164}
165
166impl Drop for CtrDrbgAes256 {
167    fn drop(&mut self) {
168        zeroize_slice(self.key.as_mut_slice());
169        zeroize_slice(self.v.as_mut_slice());
170        self.reseed_counter = 0;
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn same_seed_same_stream() {
180        let seed = core::array::from_fn::<u8, SEED_LEN, _>(|i| {
181            u8::try_from(i).expect("seed byte index fits in u8")
182        });
183        let mut a = CtrDrbgAes256::new(&seed);
184        let mut b = CtrDrbgAes256::new(&seed);
185
186        let mut out_a = [0u8; 64];
187        let mut out_b = [0u8; 64];
188        a.fill_bytes(&mut out_a);
189        b.fill_bytes(&mut out_b);
190
191        assert_eq!(out_a, out_b);
192    }
193
194    #[test]
195    fn additional_input_changes_stream() {
196        let seed = core::array::from_fn::<u8, SEED_LEN, _>(|i| {
197            u8::try_from(i).expect("seed byte index fits in u8")
198        });
199        let add = core::array::from_fn::<u8, SEED_LEN, _>(|i| {
200            u8::try_from(255usize - i).expect("masked additional-input byte fits in u8")
201        });
202
203        let mut plain = CtrDrbgAes256::new(&seed);
204        let mut mixed = CtrDrbgAes256::new(&seed);
205
206        let mut out_plain = [0u8; 32];
207        let mut out_mixed = [0u8; 32];
208        plain.generate(&mut out_plain, None);
209        mixed.generate(&mut out_mixed, Some(&add));
210
211        assert_ne!(out_plain, out_mixed);
212    }
213
214    #[test]
215    fn nist_cavs_count0_no_df_kat() {
216        let seed = [
217            0xdf, 0x5d, 0x73, 0xfa, 0xa4, 0x68, 0x64, 0x9e, 0xdd, 0xa3, 0x3b, 0x5c, 0xca, 0x79,
218            0xb0, 0xb0, 0x56, 0x00, 0x41, 0x9c, 0xcb, 0x7a, 0x87, 0x9d, 0xdf, 0xec, 0x9d, 0xb3,
219            0x2e, 0xe4, 0x94, 0xe5, 0x53, 0x1b, 0x51, 0xde, 0x16, 0xa3, 0x0f, 0x76, 0x92, 0x62,
220            0x47, 0x4c, 0x73, 0xbe, 0xc0, 0x10,
221        ];
222        let mut drbg = CtrDrbgAes256::new(&seed);
223        let mut discard = [0u8; 64];
224        drbg.fill_bytes(&mut discard);
225        let mut out = [0u8; 64];
226        drbg.fill_bytes(&mut out);
227
228        assert_eq!(
229            out,
230            [
231                0xd1, 0xc0, 0x7c, 0xd9, 0x5a, 0xf8, 0xa7, 0xf1, 0x10, 0x12, 0xc8, 0x4c, 0xe4, 0x8b,
232                0xb8, 0xcb, 0x87, 0x18, 0x9e, 0x99, 0xd4, 0x0f, 0xcc, 0xb1, 0x77, 0x1c, 0x61, 0x9b,
233                0xdf, 0x82, 0xab, 0x22, 0x80, 0xb1, 0xdc, 0x2f, 0x25, 0x81, 0xf3, 0x91, 0x64, 0xf7,
234                0xac, 0x0c, 0x51, 0x04, 0x94, 0xb3, 0xa4, 0x3c, 0x41, 0xb7, 0xdb, 0x17, 0x51, 0x4c,
235                0x87, 0xb1, 0x07, 0xae, 0x79, 0x3e, 0x01, 0xc5,
236            ]
237        );
238    }
239}