Skip to main content

rand_aes/
lib.rs

1//! # AES based pseudo-random number generator
2//!
3//! This crate implements a pseudo-random number generator (PRNG) using the AES block cipher in
4//! counter (CTR) mode. It provides four variants:
5//!
6//!  1. [`Aes128Ctr64`]: Utilizes AES-128 encryption with a 64-bit counter.
7//!  2. [`Aes128Ctr128`]: Utilizes AES-128 encryption with a 128-bit counter.
8//!  3. [`Aes256Ctr64`]: Utilizes AES-256 encryption with a 64-bit counter.
9//!  4. [`Aes256Ctr128`]: Utilizes AES-256 encryption with a 128-bit counter.
10//!
11//! Common functionality is provided using the [`Random`] trait or the optionally provided
12//! [`rand_core::RngCore`] and [`rand_core::SeedableRng`] traits.
13//!
14//! ## Optimal Performance
15//!
16//! We provide runtime detection for the hardware accelerated AES instruction set for all supported
17//! platforms. Should the executing CPU not support hardware accelerated AES, a software fallback
18//! is provided. But we highly recommend to enable the specific target feature on compile time,
19//! since the AES instruction sets is available on modern desktop CPU for at least 10 years.
20//! Enabling the target feature enables the compiler to more aggressively inline and provides
21//! much better performance. The runtime detection is not supported in `no_std`.
22//!
23//! Use the following target features for optimal performance:
24//!
25//! - aarch64: `aes` (using the cryptographic extension)
26//! - x86: `sse2` and `aes` (using AES-NI)
27//! - x86_64: `aes` (using AES-NI)
28//!
29//! There is experimental support for the RISC-V vector crypto extension. Please read the README.md
30//! for more information how to use it.
31//!
32//! ## Security Note
33//!
34//! While based on well-established cryptographic primitives, this PRNG is not intended for
35//! cryptographic key generation or other sensitive cryptographic operations, simply because safe,
36//! automatic re-seeding is not provided. We tested its statistical qualities by running versions
37//! with reduced rounds against `practrand` and `TESTu01`'s Big Crush. A version with just 3 rounds
38//! of AES encryption rounds passes the `practrand` tests with at least 16 TB. `TESTu01`'s Big Crush
39//! requires at least 5 rounds to be successfully cleared. AES-128 uses 10 rounds, whereas
40//! AES-256 uses 14 rounds.
41//!
42//! ## Parallel Stream Generation
43//!
44//! The 128-bit counter PRNG support efficient parallel stream generation through the [`Jump`] trait.
45//! The provided functions allow you to create multiple independent streams of random numbers, which
46//! is particularly useful for parallel or distributed computations. The API is designed to easily
47//! create new random number generators for child threads / tasks from a base instance.
48//!
49//! ### Jump Function
50//!
51//! The [`Jump::jump()`] function advances the PRNG counter by 2^64 steps. It can be used to create
52//! up to 2^64 non-overlapping subsequences.
53//!
54//! ### Long Jump Function
55//!
56//! The [`Jump::long_jump()`] function advances the PRNG counter by 2^96 steps. This allows for even
57//! larger separations between subsequences, useful for creating up to 2^32 independent streams.
58//!
59//! These functions are particularly useful in scenarios requiring multiple independent PRNG streams,
60//! such as parallel Monte Carlo simulations or distributed computing tasks.
61
62#![cfg_attr(docsrs, feature(doc_cfg))]
63#![cfg_attr(feature = "verification", allow(unused))]
64#![cfg_attr(not(feature = "std"), no_std)]
65
66pub mod seeds;
67
68#[cfg(all(feature = "tls", not(feature = "verification")))]
69#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
70pub mod tls;
71
72mod traits;
73
74mod backend;
75
76#[cfg(all(
77    feature = "force_runtime_detection",
78    not(any(
79        all(target_arch = "riscv64", feature = "experimental_riscv"),
80        target_arch = "aarch64",
81        target_arch = "x86_64",
82        target_arch = "x86",
83    ))
84))]
85compile_error!("The hardware AES implementation is not available for this platform. Remove the `force_runtime_detection` feature");
86
87#[cfg(all(feature = "force_runtime_detection", not(feature = "std")))]
88compile_error!("The runtime detection is not supported in `no_std`");
89
90#[cfg(all(
91    feature = "std",
92    any(
93        target_arch = "x86_64",
94        target_arch = "x86",
95        target_arch = "aarch64",
96        all(target_arch = "riscv64", feature = "experimental_riscv"),
97    ),
98    any(
99        not(any(
100            all(
101                any(target_arch = "x86_64", target_arch = "x86"),
102                target_feature = "sse2",
103                target_feature = "aes",
104            ),
105            all(
106                target_arch = "aarch64",
107                target_feature = "neon",
108                target_feature = "aes",
109            ),
110            all(target_arch = "riscv64", feature = "experimental_riscv"),
111        )),
112        feature = "force_runtime_detection",
113    ),
114))]
115pub(crate) mod runtime;
116
117#[cfg(all(
118    feature = "std",
119    any(
120        target_arch = "x86_64",
121        target_arch = "x86",
122        target_arch = "aarch64",
123        all(target_arch = "riscv64", feature = "experimental_riscv"),
124    ),
125    any(
126        not(any(
127            all(
128                any(target_arch = "x86_64", target_arch = "x86"),
129                target_feature = "sse2",
130                target_feature = "aes",
131            ),
132            all(
133                target_arch = "aarch64",
134                target_feature = "neon",
135                target_feature = "aes",
136            ),
137            all(target_arch = "riscv64", feature = "experimental_riscv"),
138        )),
139        feature = "force_runtime_detection",
140    ),
141))]
142pub use runtime::{Aes128Ctr128, Aes128Ctr64, Aes256Ctr128, Aes256Ctr64};
143
144#[cfg(all(
145    target_arch = "aarch64",
146    target_feature = "neon",
147    target_feature = "aes",
148    not(feature = "force_runtime_detection"),
149    not(feature = "force_software"),
150    not(feature = "verification"),
151))]
152pub use backend::aarch64::{Aes128Ctr128, Aes128Ctr64, Aes256Ctr128, Aes256Ctr64};
153
154#[cfg(all(
155    target_arch = "riscv64",
156    feature = "experimental_riscv",
157    not(feature = "force_runtime_detection"),
158    not(feature = "force_software"),
159    not(feature = "verification"),
160))]
161pub use backend::riscv64::{Aes128Ctr128, Aes128Ctr64, Aes256Ctr128, Aes256Ctr64};
162
163#[cfg(all(
164    any(target_arch = "x86_64", target_arch = "x86"),
165    target_feature = "sse2",
166    target_feature = "aes",
167    not(feature = "force_runtime_detection"),
168    not(feature = "force_software"),
169    not(feature = "verification"),
170))]
171pub use backend::x86::{Aes128Ctr128, Aes128Ctr64, Aes256Ctr128, Aes256Ctr64};
172
173#[cfg(all(
174    any(
175        not(any(
176            target_arch = "aarch64",
177            all(target_arch = "riscv64", feature = "experimental_riscv"),
178            any(target_arch = "x86_64", target_arch = "x86"),
179        )),
180        feature = "force_software",
181        all(
182            any(target_arch = "x86_64", target_arch = "x86"),
183            not(all(target_feature = "sse2", target_feature = "aes")),
184            not(feature = "std"),
185        ),
186        all(
187            target_arch = "aarch64",
188            not(all(target_feature = "neon", target_feature = "aes")),
189            not(feature = "std"),
190        ),
191        all(
192            target_arch = "riscv64",
193            feature = "experimental_riscv",
194            not(feature = "std"),
195        ),
196    ),
197    not(feature = "force_runtime_detection"),
198    not(feature = "verification"),
199))]
200pub use backend::soft::{Aes128Ctr128, Aes128Ctr64, Aes256Ctr128, Aes256Ctr64};
201
202#[cfg(not(feature = "verification"))]
203mod implementation;
204
205#[cfg(feature = "verification")]
206#[doc(hidden)]
207pub mod verification;
208
209pub use traits::{Jump, Random};
210
211#[allow(unused)]
212pub(crate) mod constants {
213    pub(crate) const AES_RCON: [u32; 10] =
214        [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
215    pub(crate) const AES_BLOCK_WORDS: usize = 4;
216    pub(crate) const AES_WORD_SIZE: usize = 4;
217    pub(crate) const AES_BLOCK_SIZE: usize = AES_WORD_SIZE * AES_BLOCK_WORDS;
218    pub(crate) const AES128_KEY_SIZE: usize = 16;
219    pub(crate) const AES256_KEY_SIZE: usize = 32;
220    pub(crate) const AES128_KEY_COUNT: usize = 11;
221    pub(crate) const AES256_KEY_COUNT: usize = 15;
222}
223
224#[cfg(feature = "getrandom")]
225pub(crate) fn secure_bytes<const N: usize>() -> [u8; N] {
226    let mut bytes = [0u8; N];
227    getrandom::fill(&mut bytes).expect("Can't get random bytes from OS");
228    bytes
229}
230
231#[cfg(all(test, not(feature = "verification")))]
232#[allow(unused)]
233mod tests {
234    use super::*;
235    use crate::constants::{
236        AES128_KEY_COUNT, AES128_KEY_SIZE, AES256_KEY_COUNT, AES256_KEY_SIZE, AES_BLOCK_SIZE,
237    };
238    use hex_literal::hex;
239
240    // From NIST FIPS 197
241    const TV_AES128_KEY: [u8; AES128_KEY_SIZE] = hex!("000102030405060708090a0b0c0d0e0f");
242    const TV_AES128_IV: [u8; AES_BLOCK_SIZE] = hex!("00112233445566778899aabbccddeeff");
243    const TV_AES128_ROUND_KEYS: [[u8; AES_BLOCK_SIZE]; AES128_KEY_COUNT] = [
244        hex!("000102030405060708090a0b0c0d0e0f"),
245        hex!("d6aa74fdd2af72fadaa678f1d6ab76fe"),
246        hex!("b692cf0b643dbdf1be9bc5006830b3fe"),
247        hex!("b6ff744ed2c2c9bf6c590cbf0469bf41"),
248        hex!("47f7f7bc95353e03f96c32bcfd058dfd"),
249        hex!("3caaa3e8a99f9deb50f3af57adf622aa"),
250        hex!("5e390f7df7a69296a7553dc10aa31f6b"),
251        hex!("14f9701ae35fe28c440adf4d4ea9c026"),
252        hex!("47438735a41c65b9e016baf4aebf7ad2"),
253        hex!("549932d1f08557681093ed9cbe2c974e"),
254        hex!("13111d7fe3944a17f307a78b4d2b30c5"),
255    ];
256    const TV_AES128_NEXT_0: [u8; AES_BLOCK_SIZE] = hex!("69c4e0d86a7b0430d8cdb78070b4c55a");
257    const TV_AES128_NEXT_1: [u8; AES_BLOCK_SIZE] = hex!("a556156c72876577f67f95a9d9e640a7");
258
259    // From NIST FIPS 197
260    const TV_AES256_KEY: [u8; AES256_KEY_SIZE] =
261        hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
262    const TV_AES256_IV: [u8; AES_BLOCK_SIZE] = hex!("00112233445566778899aabbccddeeff");
263    const TV_AES256_ROUND_KEYS: [[u8; AES_BLOCK_SIZE]; AES256_KEY_COUNT] = [
264        hex!("000102030405060708090a0b0c0d0e0f"),
265        hex!("101112131415161718191a1b1c1d1e1f"),
266        hex!("a573c29fa176c498a97fce93a572c09c"),
267        hex!("1651a8cd0244beda1a5da4c10640bade"),
268        hex!("ae87dff00ff11b68a68ed5fb03fc1567"),
269        hex!("6de1f1486fa54f9275f8eb5373b8518d"),
270        hex!("c656827fc9a799176f294cec6cd5598b"),
271        hex!("3de23a75524775e727bf9eb45407cf39"),
272        hex!("0bdc905fc27b0948ad5245a4c1871c2f"),
273        hex!("45f5a66017b2d387300d4d33640a820a"),
274        hex!("7ccff71cbeb4fe5413e6bbf0d261a7df"),
275        hex!("f01afafee7a82979d7a5644ab3afe640"),
276        hex!("2541fe719bf500258813bbd55a721c0a"),
277        hex!("4e5a6699a9f24fe07e572baacdf8cdea"),
278        hex!("24fc79ccbf0979e9371ac23c6d68de36"),
279    ];
280    const TV_AES256_NEXT_0: [u8; AES_BLOCK_SIZE] = hex!("8ea2b7ca516745bfeafc49904b496089");
281    const TV_AES256_NEXT_1: [u8; AES_BLOCK_SIZE] = hex!("81ae7d5e4138bf730d2a8871fec2cd0c");
282
283    pub(crate) fn aes128_key_expansion_test<F>(expansion: F)
284    where
285        F: FnOnce([u8; AES128_KEY_SIZE]) -> [[u8; AES_BLOCK_SIZE]; AES128_KEY_COUNT],
286    {
287        let expanded = expansion(TV_AES128_KEY);
288
289        for (exp, act) in TV_AES128_ROUND_KEYS.iter().zip(expanded.iter()) {
290            assert_eq!(exp, act);
291        }
292    }
293
294    pub(crate) fn aes256_key_expansion_test<F>(expansion: F)
295    where
296        F: FnOnce([u8; AES256_KEY_SIZE]) -> [[u8; AES_BLOCK_SIZE]; AES256_KEY_COUNT],
297    {
298        let expanded = expansion(TV_AES256_KEY);
299
300        for (exp, act) in TV_AES256_ROUND_KEYS.iter().zip(expanded.iter()) {
301            assert_eq!(exp, act);
302        }
303    }
304
305    #[test]
306    fn test_aes128_64_ctr() {
307        let mut ctr = [0u8; 8];
308        let mut nonce = [0u8; 8];
309        ctr.copy_from_slice(&TV_AES128_IV[0..8]);
310        nonce.copy_from_slice(&TV_AES128_IV[8..16]);
311
312        let prng = unsafe { Aes128Ctr64::from_seed_impl(TV_AES128_KEY, nonce, ctr) };
313
314        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES128_NEXT_0);
315        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES128_NEXT_1);
316    }
317
318    #[test]
319    fn test_aes128_128_ctr() {
320        let prng = unsafe { Aes128Ctr128::from_seed_impl(TV_AES128_KEY, TV_AES128_IV) };
321
322        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES128_NEXT_0);
323        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES128_NEXT_1);
324    }
325
326    #[test]
327    fn test_aes256_64_ctr() {
328        let mut ctr = [0u8; 8];
329        let mut nonce = [0u8; 8];
330        ctr.copy_from_slice(&TV_AES256_IV[0..8]);
331        nonce.copy_from_slice(&TV_AES256_IV[8..16]);
332
333        let prng = unsafe { Aes256Ctr64::from_seed_impl(TV_AES256_KEY, nonce, ctr) };
334
335        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES256_NEXT_0);
336        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES256_NEXT_1);
337    }
338
339    #[test]
340    fn test_aes256_128_ctr() {
341        let prng = unsafe { Aes256Ctr128::from_seed_impl(TV_AES256_KEY, TV_AES256_IV) };
342
343        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES256_NEXT_0);
344        assert_eq!(unsafe { prng.next_impl().to_le_bytes() }, TV_AES256_NEXT_1);
345    }
346}