Skip to main content

entropy/rng/
c_stdlib.rs

1//! Historical Unix libc PRNGs.
2//!
3//! These are here as negative controls and compatibility probes, not as
4//! recommendations. None of them are cryptographically secure, and several are
5//! spectacularly weak even by non-cryptographic standards.
6//!
7//! Implemented from primary-source libc code:
8//! - System V / POSIX sample `rand()`: 15-bit LCG output
9//! - BSD `random()`: 31-word additive generator with TYPE_3 state
10//! - Linux glibc `rand()`: alias of glibc `random()`
11//! - FreeBSD 12 compatibility `rand_r()`: Park-Miller style single-word state
12//! - Windows MSVCRT/UCRT `rand()`: 15-bit LCG output
13//! - Windows VB6/VBA `Rnd`: 24-bit linear congruential state
14//! - Windows/.NET Framework `System.Random(seed)`: Knuth-style subtractive PRNG
15//! - System V / POSIX `mrand48()`: 48-bit LCG
16//!
17//! # References
18//! * K. Thompson and D. M. Ritchie, *Unix Programmer's Manual*, 7th Edition,
19//!   Bell Laboratories, 1979.  [`rand(3)` source of the 1103515245/12345 parameters
20//!   used in `SystemVRand` and `WindowsMsvcRand`.]
21//! * S. K. Park and K. W. Miller, "Random number generators: good ones are
22//!   hard to find," *Communications of the ACM* 31(10), pp. 1192–1201, 1988.
23//!   DOI: 10.1145/63039.63042.
24//!   [MINSTD; basis of `park_miller31` used in `BsdRandCompat`]
25//! * D. E. Knuth, *The Art of Computer Programming, Volume 2: Seminumerical
26//!   Algorithms*, 3rd edition, Addison-Wesley, 1997. §3.2.2.
27//!   [Subtractive generator used by Windows/.NET `System.Random`]
28//! * J. P. Hughes, "BADRANDOM: The Effect and Mitigations for Low Entropy
29//!   Random Numbers in TLS," PhD thesis, UC Santa Cruz, 2021.
30//!   [pubs/hughes-2022-badrandom-the-effect-and-mitigations-for-low-entropy-random-numbers-in-tls.pdf]
31//!   [§2 surveys historical weak libc generators of this class as prior failure modes]
32//! * IEEE Std 1003.1 (POSIX.1), *The Open Group Base Specifications*, Issue 8, 2024.
33//!   [Normative specification of `rand()`, `rand_r()`, and `mrand48()`]
34
35use super::Rng;
36
37/// Bit-packing accumulator used by the historical `rand()` adapters that
38/// produce fewer than 32 bits per call.  Shared with [`super::lcg::Lcg32`]
39/// so both code paths agree on a single packing rule.
40#[derive(Debug, Clone, Default)]
41pub(super) struct PackedBits {
42    pub(super) acc: u64,
43    pub(super) bits: u32,
44}
45
46impl PackedBits {
47    pub(super) fn push(&mut self, value: u32, width: u32) {
48        debug_assert!((1..=32).contains(&width));
49        let masked = if width == 32 {
50            u64::from(value)
51        } else {
52            u64::from(value) & ((1u64 << width) - 1)
53        };
54        self.acc |= masked << self.bits;
55        self.bits += width;
56    }
57
58    pub(super) fn pop_word(&mut self) -> u32 {
59        debug_assert!(self.bits >= 32);
60        let out = self.acc as u32;
61        self.acc >>= 32;
62        self.bits -= 32;
63        out
64    }
65}
66
67fn park_miller31(seed: u32) -> u32 {
68    let mut x = if seed == 0 { 1i64 } else { i64::from(seed) };
69    let hi = x / 127_773;
70    let lo = x % 127_773;
71    x = 16_807 * lo - 2_836 * hi;
72    if x < 0 {
73        x += 2_147_483_647;
74    }
75    x as u32
76}
77
78// ── System V rand() ──────────────────────────────────────────────────────────
79
80/// Faithful System V / old-POSIX-style `rand()`:
81///
82/// `next = next * 1103515245 + 12345; return (next >> 16) & 0x7fff;`
83///
84/// This is the classic 15-bit libc generator that many systems exposed before
85/// higher-quality interfaces were common. It is a bad RNG.
86#[derive(Debug, Clone)]
87pub struct SystemVRand {
88    state: u32,
89    bits: PackedBits,
90}
91
92impl SystemVRand {
93    pub fn new(seed: u32) -> Self {
94        Self {
95            state: seed,
96            bits: PackedBits::default(),
97        }
98    }
99
100    pub fn next_raw(&mut self) -> u32 {
101        self.state = self.state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
102        (self.state >> 16) & 0x7fff
103    }
104}
105
106impl Rng for SystemVRand {
107    fn next_u32(&mut self) -> u32 {
108        while self.bits.bits < 32 {
109            let raw = self.next_raw();
110            self.bits.push(raw, 15);
111        }
112        self.bits.pop_word()
113    }
114}
115
116/// Compatibility alias for existing internal users.
117pub type CRand = SystemVRand;
118
119// ── Windows CRT rand() ───────────────────────────────────────────────────────
120
121/// Faithful Windows CRT `rand()` as used by MSVCRT-family runtimes:
122///
123/// `state = state * 214013 + 2531011; return (state >> 16) & 0x7fff;`
124///
125/// This is the notoriously weak 15-bit generator associated with many classic
126/// Windows/MSVC-era programs. It is included as a bad historical control.
127#[derive(Debug, Clone)]
128pub struct WindowsMsvcRand {
129    state: u32,
130    bits: PackedBits,
131}
132
133impl WindowsMsvcRand {
134    pub fn new(seed: u32) -> Self {
135        Self {
136            state: seed,
137            bits: PackedBits::default(),
138        }
139    }
140
141    pub fn next_raw(&mut self) -> u32 {
142        self.state = self.state.wrapping_mul(214_013).wrapping_add(2_531_011);
143        (self.state >> 16) & 0x7fff
144    }
145}
146
147impl Rng for WindowsMsvcRand {
148    fn next_u32(&mut self) -> u32 {
149        while self.bits.bits < 32 {
150            let raw = self.next_raw();
151            self.bits.push(raw, 15);
152        }
153        self.bits.pop_word()
154    }
155}
156
157// ── Windows VB6 / VBA Rnd() ──────────────────────────────────────────────────
158
159/// Faithful VB6/VBA `Rnd` core state transition.
160///
161/// Microsoft still preserves this compatibility algorithm in `VBMath.Rnd`:
162/// `seed = (seed * 0x43FD43FD + 0x00C39EC3) & 0x00FF_FFFF`.
163///
164/// The public API returns a `Single` in `[0, 1)`, so we expose both the raw
165/// 24-bit state and a faithful `next_f64()` mapping. This is a tiny-state,
166/// trivially predictable historical Windows generator.
167#[derive(Debug, Clone)]
168pub struct WindowsVb6Rnd {
169    state: u32,
170    bits: PackedBits,
171}
172
173impl WindowsVb6Rnd {
174    pub fn new(seed: u32) -> Self {
175        Self {
176            state: seed & 0x00ff_ffff,
177            bits: PackedBits::default(),
178        }
179    }
180
181    pub fn next_raw(&mut self) -> u32 {
182        self.state = self
183            .state
184            .wrapping_mul(0x43fd_43fd)
185            .wrapping_add(0x00c3_9ec3)
186            & 0x00ff_ffff;
187        self.state
188    }
189
190    pub fn next_sample(&mut self) -> f64 {
191        self.next_raw() as f64 * (1.0 / 16_777_216.0)
192    }
193}
194
195impl Rng for WindowsVb6Rnd {
196    fn next_u32(&mut self) -> u32 {
197        while self.bits.bits < 32 {
198            let raw = self.next_raw();
199            self.bits.push(raw, 24);
200        }
201        self.bits.pop_word()
202    }
203
204    fn next_f64(&mut self) -> f64 {
205        self.next_sample()
206    }
207}
208
209// ── Windows/.NET Random(seed) compatibility ─────────────────────────────────
210
211/// Faithful `.NET Framework` / classic `System.Random(seed)` compatibility PRNG.
212///
213/// This is the long-lived subtractive generator preserved for seed-compatibility
214/// in modern .NET runtimes. It is widely deployed and very much not a CSPRNG.
215#[derive(Debug, Clone)]
216pub struct WindowsDotNetRandom {
217    seed_array: [i32; 56],
218    inext: usize,
219    inextp: usize,
220    bits: PackedBits,
221}
222
223impl WindowsDotNetRandom {
224    pub fn new(seed: i32) -> Self {
225        let mut seed_array = [0i32; 56];
226        let subtraction = if seed == i32::MIN {
227            i32::MAX
228        } else {
229            seed.abs()
230        };
231        let mut mj = 161_803_398 - subtraction;
232        seed_array[55] = mj;
233        let mut mk = 1i32;
234        let mut ii = 0usize;
235        for _i in 1..55 {
236            ii += 21;
237            if ii >= 55 {
238                ii -= 55;
239            }
240            seed_array[ii] = mk;
241            mk = mj - mk;
242            if mk < 0 {
243                mk += i32::MAX;
244            }
245            mj = seed_array[ii];
246        }
247
248        for _ in 1..5 {
249            for i in 1..56 {
250                let mut n = i + 30;
251                if n >= 55 {
252                    n -= 55;
253                }
254                seed_array[i] -= seed_array[1 + n];
255                if seed_array[i] < 0 {
256                    seed_array[i] += i32::MAX;
257                }
258            }
259        }
260
261        Self {
262            seed_array,
263            inext: 0,
264            inextp: 21,
265            bits: PackedBits::default(),
266        }
267    }
268
269    pub fn next_raw(&mut self) -> u32 {
270        self.inext += 1;
271        if self.inext >= 56 {
272            self.inext = 1;
273        }
274
275        self.inextp += 1;
276        if self.inextp >= 56 {
277            self.inextp = 1;
278        }
279
280        let mut ret = self.seed_array[self.inext] - self.seed_array[self.inextp];
281        if ret == i32::MAX {
282            ret -= 1;
283        }
284        if ret < 0 {
285            ret += i32::MAX;
286        }
287
288        self.seed_array[self.inext] = ret;
289        ret as u32
290    }
291
292    pub fn next_sample(&mut self) -> f64 {
293        self.next_raw() as f64 * (1.0 / i32::MAX as f64)
294    }
295}
296
297impl Rng for WindowsDotNetRandom {
298    fn next_u32(&mut self) -> u32 {
299        while self.bits.bits < 32 {
300            let raw = self.next_raw();
301            self.bits.push(raw, 31);
302        }
303        self.bits.pop_word()
304    }
305
306    fn next_f64(&mut self) -> f64 {
307        self.next_sample()
308    }
309}
310
311// ── BSD random() / glibc random() ────────────────────────────────────────────
312
313/// BSD `random()` with the default 128-byte TYPE_3 state (`deg=31`, `sep=3`).
314///
315/// This is the classic Berkeley additive generator carried into glibc's
316/// `random()` and therefore Linux glibc `rand()`. It is much better than the
317/// 15-bit System V LCG, but it is still a weak historical userspace PRNG.
318#[derive(Debug, Clone)]
319pub struct BsdRandom {
320    state: [u32; 31],
321    fptr: usize,
322    rptr: usize,
323    bits: PackedBits,
324}
325
326impl BsdRandom {
327    pub fn new(seed: u32) -> Self {
328        let seed = if seed == 0 { 1 } else { seed };
329        let mut state = [0u32; 31];
330        state[0] = seed;
331        for i in 1..31 {
332            state[i] = park_miller31(state[i - 1]);
333        }
334
335        let mut rng = Self {
336            state,
337            fptr: 3,
338            rptr: 0,
339            bits: PackedBits::default(),
340        };
341
342        for _ in 0..310 {
343            let _ = rng.next_raw();
344        }
345
346        rng
347    }
348
349    pub fn next_raw(&mut self) -> u32 {
350        let val = self.state[self.fptr].wrapping_add(self.state[self.rptr]);
351        self.state[self.fptr] = val;
352        let out = val >> 1;
353
354        self.fptr += 1;
355        if self.fptr == self.state.len() {
356            self.fptr = 0;
357        }
358
359        self.rptr += 1;
360        if self.rptr == self.state.len() {
361            self.rptr = 0;
362        }
363
364        out
365    }
366}
367
368impl Rng for BsdRandom {
369    fn next_u32(&mut self) -> u32 {
370        while self.bits.bits < 32 {
371            let raw = self.next_raw();
372            self.bits.push(raw, 31);
373        }
374        self.bits.pop_word()
375    }
376}
377
378/// Linux glibc `rand()`/`random()` compatibility wrapper.
379///
380/// glibc's `rand()` is just `random()` under the hood, so the Linux userspace
381/// generator many programs used before `/dev/random`, `/dev/urandom`, and
382/// `getrandom(2)` became the norm is this same Berkeley-derived TYPE_3 engine.
383pub type LinuxLibcRandom = BsdRandom;
384
385// ── FreeBSD compatibility rand_r() ───────────────────────────────────────────
386
387/// FreeBSD 12 compatibility `rand()` / current `rand_r()` core.
388///
389/// This is the single-word Park-Miller compatibility path kept around by
390/// FreeBSD for ABI reasons. FreeBSD's own source calls it garbage.
391#[derive(Debug, Clone)]
392pub struct BsdRandCompat {
393    state: u32,
394    bits: PackedBits,
395}
396
397impl BsdRandCompat {
398    pub fn new(seed: u32) -> Self {
399        Self {
400            state: seed,
401            bits: PackedBits::default(),
402        }
403    }
404
405    pub fn next_raw(&mut self) -> u32 {
406        let x = (u64::from(self.state) % 0x7fff_fffe) + 1;
407        let hi = x / 127_773;
408        let lo = x % 127_773;
409        let mut next = 16_807i64 * lo as i64 - 2_836i64 * hi as i64;
410        if next < 0 {
411            next += 2_147_483_647;
412        }
413        let out = (next as u32).wrapping_sub(1);
414        self.state = out;
415        out
416    }
417}
418
419impl Rng for BsdRandCompat {
420    fn next_u32(&mut self) -> u32 {
421        while self.bits.bits < 32 {
422            let raw = self.next_raw();
423            self.bits.push(raw, 31);
424        }
425        self.bits.pop_word()
426    }
427}
428
429// ── System V mrand48() ───────────────────────────────────────────────────────
430
431/// Pure-Rust implementation of POSIX / System V `mrand48()`.
432///
433/// 48-bit LCG with the mandated parameters:
434/// `a = 0x5DEECE66D`, `c = 0xB`, `m = 2^48`.
435/// Better than 15-bit `rand()`, but still linear and weak.
436#[derive(Debug, Clone)]
437pub struct Rand48 {
438    state: u64,
439}
440
441const RAND48_A: u64 = 0x5DEECE66D;
442const RAND48_C: u64 = 0xB;
443const RAND48_M: u64 = 1 << 48;
444
445impl Rand48 {
446    pub fn new(seed: u64) -> Self {
447        Self {
448            state: (seed << 16) | 0x330E,
449        }
450    }
451}
452
453impl Rng for Rand48 {
454    fn next_u32(&mut self) -> u32 {
455        self.state = (RAND48_A.wrapping_mul(self.state).wrapping_add(RAND48_C)) % RAND48_M;
456        (self.state >> 16) as u32
457    }
458}
459
460#[cfg(test)]
461mod tests {
462    use super::{
463        BsdRandCompat, BsdRandom, LinuxLibcRandom, Rand48, SystemVRand, WindowsDotNetRandom,
464        WindowsMsvcRand, WindowsVb6Rnd,
465    };
466    use crate::rng::Rng;
467
468    #[test]
469    fn system_v_rand_raw_matches_posix_sample() {
470        let mut rng = SystemVRand::new(1);
471        let expected = [16838, 5758, 10113, 17515, 31051];
472        for want in expected {
473            assert_eq!(rng.next_raw(), want);
474        }
475    }
476
477    #[test]
478    fn bsd_random_matches_well_known_seed_1_prefix() {
479        let mut rng = BsdRandom::new(1);
480        let expected = [
481            1_804_289_383,
482            846_930_886,
483            1_681_692_777,
484            1_714_636_915,
485            1_957_747_793,
486        ];
487        for want in expected {
488            assert_eq!(rng.next_raw(), want);
489        }
490    }
491
492    #[test]
493    fn linux_glibc_rand_alias_matches_random() {
494        let mut linux = LinuxLibcRandom::new(1);
495        let mut bsd = BsdRandom::new(1);
496        for _ in 0..8 {
497            assert_eq!(linux.next_u32(), bsd.next_u32());
498        }
499    }
500
501    #[test]
502    fn freebsd_compat_rand_r_prefix_matches_reference_math() {
503        let mut rng = BsdRandCompat::new(1);
504        let expected = [33_613, 564_950_497, 1_097_816_498, 1_969_887_315];
505        for want in expected {
506            assert_eq!(rng.next_raw(), want);
507        }
508    }
509
510    #[test]
511    fn rand48_produces_non_constant_output() {
512        let mut rng = Rand48::new(1);
513        let a = rng.next_u32();
514        let b = rng.next_u32();
515        assert_ne!(a, b);
516    }
517
518    #[test]
519    fn windows_msvc_rand_matches_known_seed_1_prefix() {
520        let mut rng = WindowsMsvcRand::new(1);
521        let expected = [41, 18_467, 6_334, 26_500, 19_169];
522        for want in expected {
523            assert_eq!(rng.next_raw(), want);
524        }
525    }
526
527    #[test]
528    fn windows_vb6_rnd_matches_known_seed_1_prefix() {
529        let mut rng = WindowsVb6Rnd::new(1);
530        let expected = [12_640_960, 8_124_035, 4_294_458, 3_961_109, 14_212_996];
531        for want in expected {
532            assert_eq!(rng.next_raw(), want);
533        }
534    }
535
536    #[test]
537    fn windows_dotnet_random_matches_seed_1_prefix() {
538        let mut rng = WindowsDotNetRandom::new(1);
539        let expected = [
540            534_011_718,
541            237_820_880,
542            1_002_897_798,
543            1_657_007_234,
544            1_412_011_072,
545        ];
546        for want in expected {
547            assert_eq!(rng.next_raw(), want);
548        }
549    }
550}