multirandom 0.0.7__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {multirandom-0.0.7 → multirandom-0.1.5}/PKG-INFO +80 -15
  2. {multirandom-0.0.7 → multirandom-0.1.5}/README.md +78 -14
  3. {multirandom-0.0.7 → multirandom-0.1.5}/pyproject.toml +4 -3
  4. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/__init__.py +9 -3
  5. multirandom-0.1.5/src/multirandom/csprng/__init__.py +104 -0
  6. multirandom-0.1.5/src/multirandom/csprng/_aes.py +148 -0
  7. multirandom-0.1.5/src/multirandom/csprng/_drbg_base.py +166 -0
  8. multirandom-0.1.5/src/multirandom/csprng/ansi_x931.py +183 -0
  9. multirandom-0.1.5/src/multirandom/csprng/blum_blum_shub.py +283 -0
  10. multirandom-0.1.5/src/multirandom/csprng/chacha20_drbg.py +167 -0
  11. multirandom-0.1.5/src/multirandom/csprng/ctr_drbg.py +191 -0
  12. multirandom-0.1.5/src/multirandom/csprng/dual_ec_drbg.py +354 -0
  13. multirandom-0.1.5/src/multirandom/csprng/fortuna.py +225 -0
  14. multirandom-0.1.5/src/multirandom/csprng/hash_drbg.py +181 -0
  15. multirandom-0.1.5/src/multirandom/csprng/hmac_drbg.py +145 -0
  16. multirandom-0.1.5/src/multirandom/csprng/isaac.py +237 -0
  17. multirandom-0.1.5/src/multirandom/csprng/original_random.py +11 -0
  18. multirandom-0.1.5/src/multirandom/csprng/os_random.py +238 -0
  19. multirandom-0.1.5/src/multirandom/csprng/rdrand_hardware.py +377 -0
  20. multirandom-0.1.5/src/multirandom/csprng/salsa20_drbg.py +152 -0
  21. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/csprng/secure_rand.py +7 -1
  22. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/csprng/sha_rand.py +173 -161
  23. multirandom-0.1.5/src/multirandom/csprng/sponge_drbg.py +253 -0
  24. multirandom-0.1.5/src/multirandom/csprng/yarrow.py +195 -0
  25. multirandom-0.1.5/src/multirandom/prng_unsecure/__init__.py +32 -0
  26. multirandom-0.1.5/src/multirandom/prng_unsecure/doom_prng.py +339 -0
  27. multirandom-0.1.5/src/multirandom/prng_unsecure/flash_player_prng.py +484 -0
  28. multirandom-0.1.5/src/multirandom/prng_unsecure/lagged_fibonacci.py +352 -0
  29. multirandom-0.1.5/src/multirandom/prng_unsecure/lcg.py +433 -0
  30. multirandom-0.1.5/src/multirandom/prng_unsecure/mersenne_twister.py +475 -0
  31. multirandom-0.1.5/src/multirandom/prng_unsecure/middle_square.py +412 -0
  32. multirandom-0.1.5/src/multirandom/prng_unsecure/pcg.py +327 -0
  33. multirandom-0.1.5/src/multirandom/prng_unsecure/randu.py +254 -0
  34. multirandom-0.1.5/src/multirandom/prng_unsecure/splitmix64.py +326 -0
  35. multirandom-0.1.5/src/multirandom/prng_unsecure/v8_xorshift128plus.py +459 -0
  36. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/prng_unsecure/xor_shift.py +635 -605
  37. multirandom-0.1.5/src/multirandom/trng/__init__.py +12 -0
  38. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/trng/rand_using_online_api.py +14 -0
  39. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/trng/rand_using_real_random.py +7 -2
  40. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom/trng/rand_via_clicks.py +16 -0
  41. multirandom-0.1.5/src/multirandom/trng/weather_rand.py +544 -0
  42. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom.egg-info/PKG-INFO +80 -15
  43. multirandom-0.1.5/src/multirandom.egg-info/SOURCES.txt +46 -0
  44. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom.egg-info/requires.txt +1 -0
  45. multirandom-0.0.7/src/multirandom/csprng/__init__.py +0 -0
  46. multirandom-0.0.7/src/multirandom/csprng/original_random.py +0 -1
  47. multirandom-0.0.7/src/multirandom/prng_unsecure/__init__.py +0 -0
  48. multirandom-0.0.7/src/multirandom/prng_unsecure/flash_rand_and_reverse.py +0 -1
  49. multirandom-0.0.7/src/multirandom/prng_unsecure/rand_using_virt_lcg.py +0 -109
  50. multirandom-0.0.7/src/multirandom/trng/__init__.py +0 -0
  51. multirandom-0.0.7/src/multirandom/trng/weather_rand.py +0 -281
  52. multirandom-0.0.7/src/multirandom.egg-info/SOURCES.txt +0 -22
  53. {multirandom-0.0.7 → multirandom-0.1.5}/LICENSE +0 -0
  54. {multirandom-0.0.7 → multirandom-0.1.5}/setup.cfg +0 -0
  55. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom.egg-info/dependency_links.txt +0 -0
  56. {multirandom-0.0.7 → multirandom-0.1.5}/src/multirandom.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multirandom
3
- Version: 0.0.7
3
+ Version: 0.1.5
4
4
  Summary: 🎲 The truth external PyPI random package, have trng (true randomness), prng_unsecure, csprng, ...
5
5
  Author-email: BlackCatOfficialytb <quanvm0501@gmail.com>
6
6
  License: Apache-2.0
@@ -13,6 +13,7 @@ License-File: LICENSE
13
13
  Requires-Dist: aiohttp
14
14
  Requires-Dist: pyautogui
15
15
  Requires-Dist: geopy
16
+ Requires-Dist: pycryptodome>=3.0
16
17
  Provides-Extra: microphone
17
18
  Requires-Dist: real-random>=0.1.1; extra == "microphone"
18
19
  Dynamic: license-file
@@ -30,6 +31,20 @@ A comprehensive library for exploring and generating randomness across the spect
30
31
 
31
32
  ---
32
33
 
34
+ ## 📚 Full Documentation
35
+
36
+ Deep-dive docs live in [`docs/`](docs/):
37
+
38
+ | Page | Contents |
39
+ |---|---|
40
+ | [Getting started](docs/getting-started.md) | installation, quickstart, which generator to pick |
41
+ | [True randomness (`trng`)](docs/trng.md) | mouse clicks, quantum APIs, microphone, weather |
42
+ | [Cryptographic randomness (`csprng`)](docs/csprng.md) | OS CSPRNGs, NIST DRBGs, the Dual_EC backdoor demo, Keccak sponge, Blum Blum Shub, RDRAND |
43
+ | [Unsecure PRNGs (`prng_unsecure`)](docs/prng_unsecure.md) | XorShift/Xoshiro, LCGs, Doom, PCG32, V8's Math.random, and their reversal attacks |
44
+ | [Self-checks](docs/self-checks.md) | the assert batteries verifying every generator |
45
+
46
+ ---
47
+
33
48
  ## 🏗 Project Structure
34
49
 
35
50
  ```text
@@ -40,15 +55,32 @@ multirandom/
40
55
  │ ├── rand_via_clicks.py # Human-in-the-loop entropy
41
56
  │ ├── rand_using_online_api.py # Quantum & Hardware APIs (ANU QRNG, Roll-API)
42
57
  │ ├── rand_using_real_random.py # Microphone entropy (real-random)
43
- │ └── weather_rand.py # [Coming Soon] Weather-based entropy
58
+ │ └── weather_rand.py # Open-Meteo weather entropy (large unhashed seeds)
44
59
  ├── csprng/ # Cryptographically Strong Randomness
45
- │ ├── secure_rand.py # OS CSPRNGs, NIST DRBGs, Fortuna/Yarrow, ISAAC
60
+ │ ├── os_random.py # OS CSPRNGs (secrets, urandom, getrandom, BCrypt, SecRandom)
61
+ │ ├── ctr_drbg.py / hmac_drbg.py / hash_drbg.py # NIST SP 800-90A DRBGs
62
+ │ ├── dual_ec_drbg.py # the backdoored DRBG, live
63
+ │ ├── chacha20_drbg.py / salsa20_drbg.py # stream-cipher DRBGs
64
+ │ ├── fortuna.py / yarrow.py / isaac.py # pools and the classics
65
+ │ ├── sponge_drbg.py # Keccak-f[1600] sponge DRBG (from scratch)
66
+ │ ├── ansi_x931.py # ANS X9.17/X9.31, the banking generator
67
+ │ ├── blum_blum_shub.py # factoring-hard, one bit per squaring
68
+ │ ├── rdrand_hardware.py # RDRAND / RDSEED CPU instructions
69
+ │ ├── _aes.py # the library AES (FIPS-197 KATs)
46
70
  │ ├── sha_rand.py # MD5, SHA-1/2/3, BLAKE2, SHAKE PRNGs
47
71
  │ └── original_random.py # Wrapper for Python's built-in random
48
72
  └── prng_unsecure/ # Mathematical Randomness (UNSECURE PRNGs)
49
73
  ├── xor_shift.py # XorShift & Xoshiro Family
50
- ├── rand_using_virt_lcg.py # Linear Congruential Generators (LCGs)
51
- └── flash_rand_and_reverse.py # [Coming Soon] Specialized PRNGs
74
+ ├── mersenne_twister.py # MT19937 + the 624-output clone attack
75
+ ├── lcg.py # Linear Congruential Generators (LCGs)
76
+ ├── flash_player_prng.py # Flash Player Math.random() + its reversal attacks
77
+ ├── randu.py # IBM's RANDU, the 15-planes generator
78
+ ├── middle_square.py # von Neumann's middle square + MSWS
79
+ ├── doom_prng.py # Doom's 256-entry lookup table
80
+ ├── splitmix64.py # SplitMix64, inverted exactly
81
+ ├── lagged_fibonacci.py # BSD-style LF(31, 3); last j outputs = state
82
+ ├── pcg.py # O'Neill's PCG32, closed-form jump
83
+ └── v8_xorshift128plus.py # Math.random()'s engine + the GF(2) seed solve
52
84
  ```
53
85
 
54
86
  ---
@@ -95,7 +127,31 @@ Run `python -m multirandom.prng_unsecure.xor_shift` to check every generator aga
95
127
  ### 4. Mathematical LCGs (`prng_unsecure`)
96
128
  Classic Linear Congruential Generators with customizable shift parameters. Includes `reverse_roll()` methods to demonstrate the insecurity of simple modular arithmetic.
97
129
 
98
- ### 5. Hash-based Generators (`csprng`)
130
+ ### 5. Iconic & historic PRNGs (`prng_unsecure`)
131
+ The famous, the infamous, and the ones running inside your favourite games —
132
+ each module ships its reference vectors and its own reversal.
133
+
134
+ - **IBM's RANDU** (`randu`): the 15-planes generator; one output recovers the seed via the modular inverse of 65539.
135
+ - **Middle Square** (`middle_square`): von Neumann's 1949 idea, plus the MSWS variant.
136
+ - **Doom's RNG** (`doom_prng`): the 256-entry lookup table behind every shotgun spread, increment-then-index; one draw pins the index.
137
+ - **SplitMix64** (`splitmix64`): the seeding engine behind xoshiro; the mixer is inverted exactly from one output.
138
+ - **Lagged Fibonacci** (`lagged_fibonacci`): the BSD `random()`-style LF(31, 3); the last j outputs ARE the state.
139
+ - **PCG32** (`pcg`): O'Neill's permutation-on-an-LCG — the "good" end, with a closed-form `jump()` for a billion outputs in one call.
140
+ - **V8's XorShift128+** (`v8_xorshift128plus`): JavaScript's `Math.random()` engine; five consecutive doubles recover the exact 128-bit seed by GF(2) elimination.
141
+
142
+ ```python
143
+ from multirandom.prng_unsecure.doom_prng import DoomRandom
144
+ from multirandom.prng_unsecure.v8_xorshift128plus import V8XorShift128Plus
145
+
146
+ doom = DoomRandom()
147
+ doom.P_Random() # the same table the game rolls
148
+
149
+ v8 = V8XorShift128Plus(seed="demo")
150
+ clone = V8XorShift128Plus.from_outputs([v8.random() for _ in range(5)])
151
+ clone.getstate() == v8.getstate() # True: Math.random, predicted exactly
152
+ ```
153
+
154
+ ### 6. Hash-based Generators (`csprng`)
99
155
  Counter-mode hash DRBGs: `H(seed || counter)` drives the full `random.Random` API.
100
156
 
101
157
  - **SHA-2**: SHA-224, SHA-256, SHA-384, SHA-512
@@ -111,7 +167,7 @@ gen = BLAKE2bRandom("my seed")
111
167
  print(gen.randbytes(16).hex(), gen.random(), gen.choice(["a", "b"]))
112
168
  ```
113
169
 
114
- ### 6. Cryptographically Strong Generators (`csprng/secure_rand`)
170
+ ### 7. Cryptographically Strong Generators (`csprng`)
115
171
  The ones to actually guard things with, plus deliberately broken ones that teach.
116
172
 
117
173
  **OS-level CSPRNGs** (live entropy, behave like `random.SystemRandom` — seeding
@@ -136,14 +192,23 @@ the native API is not reachable on the running platform.
136
192
  - `Hash_DRBG` – SHA-256 with Hash_df (440-bit state)
137
193
  - `Dual_EC_DRBG` — *deliberately backdoored*, kept as a teaching exhibit
138
194
 
139
- **Stream / accumulator designs**: `ChaCha20DRBG`, `Salsa20DRBG` (counter-mode
140
- DRBGs on the two classic stream ciphers), `FortunaRandom` (32 seed pools +
141
- AES-256-CTR, rekey every 65536 blocks, `add_entropy(data, pool)`) and its
142
- legacy predecessor `YarrowRandom` (fast/slow pools, AES-192-CTR), plus
143
- `ISAACRandom` (Bob Jenkins' ISAAC, validated against his reference vectors).
195
+ **Stream / accumulator / sponge designs**: `ChaCha20DRBG`, `Salsa20DRBG`
196
+ (counter-mode DRBGs on the two classic stream ciphers), `FortunaRandom`
197
+ (32 seed pools + AES-256-CTR, rekey every 65536 blocks,
198
+ `add_entropy(data, pool)`) and its legacy predecessor `YarrowRandom`
199
+ (fast/slow pools, AES-192-CTR), `ISAACRandom` (Bob Jenkins' ISAAC, validated
200
+ against his reference vectors), `SpongeDRBG` (a sponge-mode DRBG on a
201
+ hand-transcribed Keccak-f[1600], pinned against `hashlib`), and
202
+ `AnsiX931DRBG` (the banking world's ANS X9.17/X9.31 generator).
203
+
204
+ **Factoring-hard and hardware**: `BlumBlumShub`
205
+ (x' = x² mod n — one provably factoring-hard bit per squaring, genuine Blum
206
+ primes enforced by a self-contained Miller-Rabin) and the CPU's own entropy
207
+ instructions `RDRANDRandom` / `RDSEEDRandom` (x86-64 shellcode through
208
+ ctypes, probed availability).
144
209
 
145
210
  ```python
146
- from multirandom.csprng.secure_rand import SecretsRandom, CTR_DRBG, Dual_EC_DRBG
211
+ from multirandom.csprng import SecretsRandom, CTR_DRBG, Dual_EC_DRBG
147
212
 
148
213
  gen = SecretsRandom()
149
214
  print(gen.token_hex(32)) # live kernel entropy
@@ -178,7 +243,7 @@ module ships with this demo running in its own self-checks at
178
243
  `truncate_bits=8`. It warns on first use. Never use it for real randomness.
179
244
 
180
245
  ```python
181
- # Run its assert battery: python -m multirandom.csprng.secure_rand
246
+ # Run the DRBG sweep: python -m multirandom.csprng._drbg_base
182
247
  ```
183
248
 
184
249
  ### The global unsecure-prng warning switch
@@ -200,7 +265,7 @@ multirandom.ignore_unsecure_prng_warning_do_not_set_to_true_unless_you_know_how_
200
265
 
201
266
  ### Requirements
202
267
  - Python 3.9+
203
- - Dependencies listed in `requirements.txt` (e.g., `pyautogui`, `aiohttp`)
268
+ - Dependencies listed in `requirements.txt` (e.g., `pyautogui`, `aiohttp`, `pycryptodome`)
204
269
 
205
270
  ### Installation
206
271
  ```bash
@@ -11,6 +11,20 @@ A comprehensive library for exploring and generating randomness across the spect
11
11
 
12
12
  ---
13
13
 
14
+ ## 📚 Full Documentation
15
+
16
+ Deep-dive docs live in [`docs/`](docs/):
17
+
18
+ | Page | Contents |
19
+ |---|---|
20
+ | [Getting started](docs/getting-started.md) | installation, quickstart, which generator to pick |
21
+ | [True randomness (`trng`)](docs/trng.md) | mouse clicks, quantum APIs, microphone, weather |
22
+ | [Cryptographic randomness (`csprng`)](docs/csprng.md) | OS CSPRNGs, NIST DRBGs, the Dual_EC backdoor demo, Keccak sponge, Blum Blum Shub, RDRAND |
23
+ | [Unsecure PRNGs (`prng_unsecure`)](docs/prng_unsecure.md) | XorShift/Xoshiro, LCGs, Doom, PCG32, V8's Math.random, and their reversal attacks |
24
+ | [Self-checks](docs/self-checks.md) | the assert batteries verifying every generator |
25
+
26
+ ---
27
+
14
28
  ## 🏗 Project Structure
15
29
 
16
30
  ```text
@@ -21,15 +35,32 @@ multirandom/
21
35
  │ ├── rand_via_clicks.py # Human-in-the-loop entropy
22
36
  │ ├── rand_using_online_api.py # Quantum & Hardware APIs (ANU QRNG, Roll-API)
23
37
  │ ├── rand_using_real_random.py # Microphone entropy (real-random)
24
- │ └── weather_rand.py # [Coming Soon] Weather-based entropy
38
+ │ └── weather_rand.py # Open-Meteo weather entropy (large unhashed seeds)
25
39
  ├── csprng/ # Cryptographically Strong Randomness
26
- │ ├── secure_rand.py # OS CSPRNGs, NIST DRBGs, Fortuna/Yarrow, ISAAC
40
+ │ ├── os_random.py # OS CSPRNGs (secrets, urandom, getrandom, BCrypt, SecRandom)
41
+ │ ├── ctr_drbg.py / hmac_drbg.py / hash_drbg.py # NIST SP 800-90A DRBGs
42
+ │ ├── dual_ec_drbg.py # the backdoored DRBG, live
43
+ │ ├── chacha20_drbg.py / salsa20_drbg.py # stream-cipher DRBGs
44
+ │ ├── fortuna.py / yarrow.py / isaac.py # pools and the classics
45
+ │ ├── sponge_drbg.py # Keccak-f[1600] sponge DRBG (from scratch)
46
+ │ ├── ansi_x931.py # ANS X9.17/X9.31, the banking generator
47
+ │ ├── blum_blum_shub.py # factoring-hard, one bit per squaring
48
+ │ ├── rdrand_hardware.py # RDRAND / RDSEED CPU instructions
49
+ │ ├── _aes.py # the library AES (FIPS-197 KATs)
27
50
  │ ├── sha_rand.py # MD5, SHA-1/2/3, BLAKE2, SHAKE PRNGs
28
51
  │ └── original_random.py # Wrapper for Python's built-in random
29
52
  └── prng_unsecure/ # Mathematical Randomness (UNSECURE PRNGs)
30
53
  ├── xor_shift.py # XorShift & Xoshiro Family
31
- ├── rand_using_virt_lcg.py # Linear Congruential Generators (LCGs)
32
- └── flash_rand_and_reverse.py # [Coming Soon] Specialized PRNGs
54
+ ├── mersenne_twister.py # MT19937 + the 624-output clone attack
55
+ ├── lcg.py # Linear Congruential Generators (LCGs)
56
+ ├── flash_player_prng.py # Flash Player Math.random() + its reversal attacks
57
+ ├── randu.py # IBM's RANDU, the 15-planes generator
58
+ ├── middle_square.py # von Neumann's middle square + MSWS
59
+ ├── doom_prng.py # Doom's 256-entry lookup table
60
+ ├── splitmix64.py # SplitMix64, inverted exactly
61
+ ├── lagged_fibonacci.py # BSD-style LF(31, 3); last j outputs = state
62
+ ├── pcg.py # O'Neill's PCG32, closed-form jump
63
+ └── v8_xorshift128plus.py # Math.random()'s engine + the GF(2) seed solve
33
64
  ```
34
65
 
35
66
  ---
@@ -76,7 +107,31 @@ Run `python -m multirandom.prng_unsecure.xor_shift` to check every generator aga
76
107
  ### 4. Mathematical LCGs (`prng_unsecure`)
77
108
  Classic Linear Congruential Generators with customizable shift parameters. Includes `reverse_roll()` methods to demonstrate the insecurity of simple modular arithmetic.
78
109
 
79
- ### 5. Hash-based Generators (`csprng`)
110
+ ### 5. Iconic & historic PRNGs (`prng_unsecure`)
111
+ The famous, the infamous, and the ones running inside your favourite games —
112
+ each module ships its reference vectors and its own reversal.
113
+
114
+ - **IBM's RANDU** (`randu`): the 15-planes generator; one output recovers the seed via the modular inverse of 65539.
115
+ - **Middle Square** (`middle_square`): von Neumann's 1949 idea, plus the MSWS variant.
116
+ - **Doom's RNG** (`doom_prng`): the 256-entry lookup table behind every shotgun spread, increment-then-index; one draw pins the index.
117
+ - **SplitMix64** (`splitmix64`): the seeding engine behind xoshiro; the mixer is inverted exactly from one output.
118
+ - **Lagged Fibonacci** (`lagged_fibonacci`): the BSD `random()`-style LF(31, 3); the last j outputs ARE the state.
119
+ - **PCG32** (`pcg`): O'Neill's permutation-on-an-LCG — the "good" end, with a closed-form `jump()` for a billion outputs in one call.
120
+ - **V8's XorShift128+** (`v8_xorshift128plus`): JavaScript's `Math.random()` engine; five consecutive doubles recover the exact 128-bit seed by GF(2) elimination.
121
+
122
+ ```python
123
+ from multirandom.prng_unsecure.doom_prng import DoomRandom
124
+ from multirandom.prng_unsecure.v8_xorshift128plus import V8XorShift128Plus
125
+
126
+ doom = DoomRandom()
127
+ doom.P_Random() # the same table the game rolls
128
+
129
+ v8 = V8XorShift128Plus(seed="demo")
130
+ clone = V8XorShift128Plus.from_outputs([v8.random() for _ in range(5)])
131
+ clone.getstate() == v8.getstate() # True: Math.random, predicted exactly
132
+ ```
133
+
134
+ ### 6. Hash-based Generators (`csprng`)
80
135
  Counter-mode hash DRBGs: `H(seed || counter)` drives the full `random.Random` API.
81
136
 
82
137
  - **SHA-2**: SHA-224, SHA-256, SHA-384, SHA-512
@@ -92,7 +147,7 @@ gen = BLAKE2bRandom("my seed")
92
147
  print(gen.randbytes(16).hex(), gen.random(), gen.choice(["a", "b"]))
93
148
  ```
94
149
 
95
- ### 6. Cryptographically Strong Generators (`csprng/secure_rand`)
150
+ ### 7. Cryptographically Strong Generators (`csprng`)
96
151
  The ones to actually guard things with, plus deliberately broken ones that teach.
97
152
 
98
153
  **OS-level CSPRNGs** (live entropy, behave like `random.SystemRandom` — seeding
@@ -117,14 +172,23 @@ the native API is not reachable on the running platform.
117
172
  - `Hash_DRBG` – SHA-256 with Hash_df (440-bit state)
118
173
  - `Dual_EC_DRBG` — *deliberately backdoored*, kept as a teaching exhibit
119
174
 
120
- **Stream / accumulator designs**: `ChaCha20DRBG`, `Salsa20DRBG` (counter-mode
121
- DRBGs on the two classic stream ciphers), `FortunaRandom` (32 seed pools +
122
- AES-256-CTR, rekey every 65536 blocks, `add_entropy(data, pool)`) and its
123
- legacy predecessor `YarrowRandom` (fast/slow pools, AES-192-CTR), plus
124
- `ISAACRandom` (Bob Jenkins' ISAAC, validated against his reference vectors).
175
+ **Stream / accumulator / sponge designs**: `ChaCha20DRBG`, `Salsa20DRBG`
176
+ (counter-mode DRBGs on the two classic stream ciphers), `FortunaRandom`
177
+ (32 seed pools + AES-256-CTR, rekey every 65536 blocks,
178
+ `add_entropy(data, pool)`) and its legacy predecessor `YarrowRandom`
179
+ (fast/slow pools, AES-192-CTR), `ISAACRandom` (Bob Jenkins' ISAAC, validated
180
+ against his reference vectors), `SpongeDRBG` (a sponge-mode DRBG on a
181
+ hand-transcribed Keccak-f[1600], pinned against `hashlib`), and
182
+ `AnsiX931DRBG` (the banking world's ANS X9.17/X9.31 generator).
183
+
184
+ **Factoring-hard and hardware**: `BlumBlumShub`
185
+ (x' = x² mod n — one provably factoring-hard bit per squaring, genuine Blum
186
+ primes enforced by a self-contained Miller-Rabin) and the CPU's own entropy
187
+ instructions `RDRANDRandom` / `RDSEEDRandom` (x86-64 shellcode through
188
+ ctypes, probed availability).
125
189
 
126
190
  ```python
127
- from multirandom.csprng.secure_rand import SecretsRandom, CTR_DRBG, Dual_EC_DRBG
191
+ from multirandom.csprng import SecretsRandom, CTR_DRBG, Dual_EC_DRBG
128
192
 
129
193
  gen = SecretsRandom()
130
194
  print(gen.token_hex(32)) # live kernel entropy
@@ -159,7 +223,7 @@ module ships with this demo running in its own self-checks at
159
223
  `truncate_bits=8`. It warns on first use. Never use it for real randomness.
160
224
 
161
225
  ```python
162
- # Run its assert battery: python -m multirandom.csprng.secure_rand
226
+ # Run the DRBG sweep: python -m multirandom.csprng._drbg_base
163
227
  ```
164
228
 
165
229
  ### The global unsecure-prng warning switch
@@ -181,7 +245,7 @@ multirandom.ignore_unsecure_prng_warning_do_not_set_to_true_unless_you_know_how_
181
245
 
182
246
  ### Requirements
183
247
  - Python 3.9+
184
- - Dependencies listed in `requirements.txt` (e.g., `pyautogui`, `aiohttp`)
248
+ - Dependencies listed in `requirements.txt` (e.g., `pyautogui`, `aiohttp`, `pycryptodome`)
185
249
 
186
250
  ### Installation
187
251
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "multirandom"
7
- version = "0.0.7"
7
+ version = "0.1.5"
8
8
  authors = [
9
9
  { name="BlackCatOfficialytb", email="quanvm0501@gmail.com" },
10
10
  ]
@@ -18,8 +18,9 @@ classifiers = [
18
18
  ]
19
19
  dependencies = [
20
20
  "aiohttp",
21
- "pyautogui",
22
- "geopy"
21
+ "pyautogui",
22
+ "geopy",
23
+ "pycryptodome>=3.0"
23
24
  ]
24
25
 
25
26
  [project.optional-dependencies]
@@ -1,8 +1,14 @@
1
- """multirandom - one random-number package, several philosophies of chance.
1
+ """
2
+ Project: https://pypi.org/project/multirandom/
3
+ Repo: https://github.com/BlackCatOfficialytb/multirandom
4
+ License: Apache 2.0
5
+ Module: multirandom
6
+ Description:
7
+ One random-number package, several philosophies of chance:
2
8
 
3
- * ``prng_unsecure/`` - educational PRNGs that must never guard anything (LCGs, xorshift).
4
- * ``csprng/`` - cryptographically strong generators (OS entropy, NIST DRBGs, stream ciphers).
5
9
  * ``trng/`` - true/hardware entropy sources (microphones, weather, clicks, online APIs).
10
+ * ``csprng/`` - cryptographically strong generators (OS entropy, NIST DRBGs, stream ciphers).
11
+ * ``prng_unsecure/`` - educational PRNGs that must never guard anything (LCGs, xorshift).
6
12
 
7
13
  Everything in ``prng_unsecure/`` and the deliberately broken ``Dual_EC_DRBG`` warns the
8
14
  first time it is used. Those warnings are controlled by ONE global variable::
@@ -0,0 +1,104 @@
1
+ """
2
+ Project: https://pypi.org/project/multirandom/
3
+ Repo: https://github.com/BlackCatOfficialytb/multirandom
4
+ License: Apache 2.0
5
+ Module: multirandom.csprng
6
+ Description:
7
+ Cryptographically strong randomness ("csprng") -- one module per method:
8
+
9
+ 1. OS-level CSPRNGs - the sources you should actually use
10
+ (:mod:`multirandom.csprng.os_random`): :class:`SecretsRandom` (the
11
+ ``secrets`` module), :class:`OSURandom` (``os.urandom``),
12
+ :class:`GetRandomRandom` (Linux ``getrandom(2)``),
13
+ :class:`BCryptGenRandomRandom` (Windows ``BCryptGenRandom``) and
14
+ :class:`SecRandomCopyBytesRandom` (Apple ``SecRandomCopyBytes``). The
15
+ ctypes ones fall back gracefully to ``os.urandom`` (the very same kernel
16
+ entropy pool) when the native API is not reachable on the running
17
+ platform.
18
+
19
+ 2. NIST SP 800-90A DRBGs: :class:`CTR_DRBG` (AES-128, via the crypto
20
+ library), :class:`HMAC_DRBG` and :class:`Hash_DRBG` (SHA-256) - plus the
21
+ infamous *backdoored* :class:`Dual_EC_DRBG`, kept deliberately: it warns
22
+ loudly and shows how its backdoor is exploited with
23
+ ``Dual_EC_DRBG.reveal`` (its own module: ``dual_ec_drbg``).
24
+
25
+ 3. Stream/accumulator designs: :class:`ChaCha20DRBG`
26
+ (``chacha20_drbg``), :class:`Salsa20DRBG` (``salsa20_drbg``),
27
+ :class:`FortunaRandom` (``fortuna``) and its legacy predecessor
28
+ :class:`YarrowRandom` (``yarrow``), plus :class:`ISAACRandom`
29
+ (``isaac`` - Bob Jenkins' ISAAC, transcribed from the author's
30
+ ``readable.c``) and :class:`SpongeDRBG` (``sponge_drbg`` - a sponge-mode
31
+ DRBG on a from-scratch Keccak-f[1600], KAT-pinned against ``hashlib``).
32
+
33
+ 4. The classics: :class:`AnsiX931DRBG` (``ansi_x931`` - ANS X9.17/X9.31,
34
+ the banking-world generator) and :class:`BlumBlumShub`
35
+ (``blum_blum_shub`` - one provably factoring-hard bit per squaring).
36
+
37
+ 5. The CPU's own entropy instructions (``rdrand_hardware``): the
38
+ :class:`RDRANDRandom` and :class:`RDSEEDRandom` hardware TRNGs, run
39
+ through x64 shellcode stubs - probed, never assumed.
40
+
41
+ Every DRBG class is a ``random.Random`` subclass, so ``.random()``,
42
+ ``.randint()``, ``.choice()``, ``.shuffle()`` ... all work on top of the
43
+ cryptographic core. Everything without a restorable state (the OS-level
44
+ sources) behaves like ``random.SystemRandom``: seeding is a no-op and
45
+ ``getstate()``/``setstate()`` raise ``NotImplementedError``.
46
+
47
+ The raw AES used by CTR_DRBG / Fortuna / Yarrow comes from an installed
48
+ crypto library (pycryptodome / pycryptodomex, with ``cryptography`` as a
49
+ fallback) - see :mod:`multirandom.csprng._aes`; the shared ``random.Random``
50
+ plumbing of the seedable DRBGs lives in
51
+ :mod:`multirandom.csprng._drbg_base`.
52
+
53
+ Unsecure- or backdoor-related warnings here are governed by one global
54
+ switch in the package root::
55
+
56
+ import multirandom
57
+ multirandom.ignore_unsecure_prng_warning_do_not_set_to_true_unless_you_know_how_risky_it_is
58
+
59
+ ``False`` (the default) sends those warnings to the console; ``True``
60
+ silences them.
61
+ """
62
+ from .os_random import (
63
+ SecretsRandom, OSURandom, GetRandomRandom,
64
+ BCryptGenRandomRandom, SecRandomCopyBytesRandom,
65
+ )
66
+ from .ctr_drbg import CTR_DRBG
67
+ from .hmac_drbg import HMAC_DRBG
68
+ from .hash_drbg import Hash_DRBG
69
+ from .dual_ec_drbg import Dual_EC_DRBG
70
+ from .chacha20_drbg import ChaCha20DRBG
71
+ from .salsa20_drbg import Salsa20DRBG
72
+ from .fortuna import FortunaRandom
73
+ from .yarrow import YarrowRandom
74
+ from .isaac import ISAACRandom
75
+ from .sponge_drbg import SpongeDRBG
76
+ from .ansi_x931 import AnsiX931DRBG
77
+ from .blum_blum_shub import BlumBlumShub
78
+ from .rdrand_hardware import RDRANDRandom, RDSEEDRandom
79
+
80
+ DRBG_GENERATORS = [
81
+ CTR_DRBG, HMAC_DRBG, Hash_DRBG, Dual_EC_DRBG,
82
+ ChaCha20DRBG, Salsa20DRBG, FortunaRandom, YarrowRandom, ISAACRandom,
83
+ SpongeDRBG, AnsiX931DRBG,
84
+ ]
85
+ # BlumBlumShub is deliberately absent: its constructor demands the Blum
86
+ # prime pair (it is not a seed-only DRBG), so the generic seedable-DRBG
87
+ # sweep cannot construct it. It ships its own battery instead.
88
+
89
+ OS_GENERATORS = [
90
+ SecretsRandom, OSURandom, GetRandomRandom,
91
+ BCryptGenRandomRandom, SecRandomCopyBytesRandom,
92
+ ]
93
+
94
+ HW_GENERATORS = [RDRANDRandom, RDSEEDRandom]
95
+
96
+ __all__ = [
97
+ "SecretsRandom", "OSURandom", "GetRandomRandom",
98
+ "BCryptGenRandomRandom", "SecRandomCopyBytesRandom",
99
+ "CTR_DRBG", "HMAC_DRBG", "Hash_DRBG", "Dual_EC_DRBG",
100
+ "ChaCha20DRBG", "Salsa20DRBG", "FortunaRandom", "YarrowRandom", "ISAACRandom",
101
+ "SpongeDRBG", "AnsiX931DRBG", "BlumBlumShub",
102
+ "RDRANDRandom", "RDSEEDRandom",
103
+ "DRBG_GENERATORS", "OS_GENERATORS", "HW_GENERATORS",
104
+ ]
@@ -0,0 +1,148 @@
1
+ """
2
+ Project: https://pypi.org/project/multirandom/
3
+ Repo: https://github.com/BlackCatOfficialytb/multirandom
4
+ License: Apache 2.0
5
+ Module: multirandom.csprng._aes
6
+ Description:
7
+ The raw AES core shared by the csprng modules that need block-level AES
8
+ (``CTR_DRBG``, ``FortunaRandom``, ``YarrowRandom``, and ANSI X9.31).
9
+
10
+ The arithmetic -- key schedule, SubBytes/MixColumns, the ECB mode -- is
11
+ delegated to an audited library implementation (``pycryptodome``, with
12
+ ``pycryptodomex`` or ``cryptography`` accepted as drop-in alternates),
13
+ replacing the hand-rolled AES that used to live in ``secure_rand.py``.
14
+ What survives here is only the *shape* the DRBG modules are specced
15
+ against: ``encrypt_block(block)`` (one 16-byte ECB encryption -- the
16
+ primitive CTR_DRBG's (K, V) iterator and ANSI X9.31 run on) and
17
+ ``ctr(counter, n)`` (big-endian-counter keystream, Fortuna's generator).
18
+
19
+ FIPS-197 known-answer tests still run in ``__main__`` to prove the wiring::
20
+
21
+ py -3 -m multirandom.csprng._aes
22
+ """
23
+ from __future__ import annotations
24
+
25
+ # --- pick the AES library: pycryptodome > pycryptodomex > cryptography ------
26
+ try:
27
+ from Crypto.Cipher import AES as _pyc_aes # pycryptodome
28
+ except ImportError:
29
+ try:
30
+ from Cryptodome.Cipher import AES as _pyc_aes # pycryptodomex
31
+ except ImportError:
32
+ _pyc_aes = None
33
+
34
+ if _pyc_aes is None:
35
+ try: # the cryptography package as the last resort
36
+ from cryptography.hazmat.primitives.ciphers import (
37
+ Cipher as _cipher, algorithms as _algorithms, modes as _modes)
38
+ except ImportError:
39
+ raise ImportError(
40
+ "multirandom.csprng needs an AES implementation: install "
41
+ "pycryptodome (pip install pycryptodome) or the cryptography "
42
+ "package") from None
43
+
44
+
45
+ class _Aes:
46
+ """AES-128/192/256 single-block encryptor + CTR keystream helper.
47
+
48
+ Interface kept from the legacy hand-rolled AES so the DRBG modules
49
+ read exactly as before; the one-round-of-AES-per-call arithmetic is
50
+ now the installed library's (FIPS-validated implementation, no hand
51
+ transcription to get wrong).
52
+ """
53
+
54
+ def __init__(self, key):
55
+ key = bytes(key)
56
+ if len(key) not in (16, 24, 32):
57
+ raise ValueError("AES key must be 16, 24 or 32 bytes")
58
+ self._key = key
59
+ if _pyc_aes is not None:
60
+ # ECB is stateless: one cipher object serves every block.
61
+ self._ecb = _pyc_aes.new(key, _pyc_aes.MODE_ECB)
62
+ self._backend = "pycryptodome"
63
+ else:
64
+ self._ecb = _cipher(
65
+ _algorithms.AES(key), _modes.ECB()).encryptor()
66
+ self._backend = "cryptography"
67
+
68
+ def encrypt_block(self, block):
69
+ """AES-ECB of exactly one 16-byte block."""
70
+ block = bytes(block)
71
+ if len(block) != 16:
72
+ raise ValueError("AES blocks are 16 bytes")
73
+ if self._backend == "pycryptodome":
74
+ return self._ecb.encrypt(block)
75
+ return self._ecb.update(block)
76
+
77
+ def ctr(self, counter, n):
78
+ """``n`` bytes of AES-CTR keystream for a 128-bit big-endian counter."""
79
+ out = bytearray()
80
+ while len(out) < n:
81
+ out += self.encrypt_block(counter.to_bytes(16, "big"))
82
+ counter = (counter + 1) % (1 << 128)
83
+ return bytes(out[:n])
84
+
85
+
86
+
87
+ def main():
88
+ print("=" * 70)
89
+ print("_aes - FIPS-197 wiring self-check battery")
90
+ print("=" * 70)
91
+
92
+ # --- 1. FIPS-197 appendix C known-answer tests ---------------------------
93
+ fips_pt = bytes.fromhex("00112233445566778899aabbccddeeff")
94
+ fips_key = bytes(range(16))
95
+ aes = _Aes(fips_key)
96
+ assert aes.encrypt_block(fips_pt) == \
97
+ bytes.fromhex("69c4e0d86a7b0430d8cdb78070b4c55a"), "AES-128 KAT failed"
98
+ assert _Aes(bytes(range(24))).encrypt_block(fips_pt) == \
99
+ bytes.fromhex("dda97ca4864cdfe06eaf70a0ec0d7191"), "AES-192 KAT failed"
100
+ assert _Aes(bytes(range(32))).encrypt_block(fips_pt) == \
101
+ bytes.fromhex("8ea2b7ca516745bfeafc49904b496089"), "AES-256 KAT failed"
102
+ print(f"[1] FIPS-197 AES-128/192/256 KATs ({aes._backend} core): OK")
103
+
104
+ # --- 2. ECB eats complete, and only complete, blocks ----------------------
105
+ for bad_len in (0, 1, 15, 17, 32):
106
+ try:
107
+ aes.encrypt_block(b"\x00" * bad_len)
108
+ except ValueError:
109
+ pass
110
+ else:
111
+ raise AssertionError(f"block of {bad_len} bytes accepted")
112
+ for bad_key_len in (0, 8, 17, 33):
113
+ try:
114
+ _Aes(b"\x00" * bad_key_len)
115
+ except ValueError:
116
+ pass
117
+ else:
118
+ raise AssertionError(f"key of {bad_key_len} bytes accepted")
119
+ print("[2] only 16-byte blocks and 16/24/32-byte keys accepted: OK")
120
+
121
+ # --- 3. the CTR keystream is the concatenation of ECB(counter + i) --------
122
+ key = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
123
+ counter = int.from_bytes(
124
+ bytes.fromhex("00112233445566778899aabbccddeeff"), "big")
125
+ runs = _Aes(key)
126
+ manual = (runs.encrypt_block(counter.to_bytes(16, "big"))
127
+ + runs.encrypt_block(((counter + 1) % (1 << 128)).to_bytes(16, "big"))
128
+ + runs.encrypt_block(((counter + 2) % (1 << 128)).to_bytes(16, "big")))
129
+ assert runs.ctr(counter, 40) == manual[:40], "CTR keystream drifted"
130
+ assert runs.ctr(counter, 48) == manual, "CTR keystream wrong at block edges"
131
+ assert runs.ctr(counter, 1) == manual[:1], "CTR short request wrong"
132
+ block4 = runs.encrypt_block(((counter + 3) % (1 << 128)).to_bytes(16, "big"))
133
+ assert runs.ctr(counter, 49) == manual + block4[:1], "CTR carry-over wrong"
134
+ print("[3] ctr() == ECB on counter, counter+1, ... (block edges exact): OK")
135
+
136
+ # --- 4. CTR round trip through XOR (the way the DRBGs use it) -------------
137
+ stream = runs.ctr(counter, 64)
138
+ assert bytes(x ^ y for x, y in zip(stream, stream)) == bytes(64)
139
+ assert len({runs.ctr(counter, 32)[i] for i in range(32)}) > 12, \
140
+ "flat keystream?! the library AES looks broken"
141
+ print("[4] keystream XORs back to zero, byte spread sane: OK")
142
+
143
+ print("-" * 70)
144
+ print("All _aes checks passed!")
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()