├── .github └── workflows │ ├── check.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── benchmark.rs └── src ├── cli.rs ├── lib.rs ├── log.rs ├── main.rs └── random.rs /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Testing 4 | 5 | jobs: 6 | check: 7 | name: Check (cargo check) 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: dtolnay/rust-toolchain@stable 12 | - run: cargo check 13 | 14 | test: 15 | name: Test (cargo test) 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: dtolnay/rust-toolchain@stable 20 | - run: cargo test --all-features 21 | 22 | fmt: 23 | name: Formatting (rustfmt) 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: rustfmt 30 | - run: cargo fmt --all -- --check 31 | 32 | clippy: 33 | name: Clippy 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v3 37 | - uses: dtolnay/rust-toolchain@stable 38 | with: 39 | components: clippy 40 | - run: cargo clippy -- -D warnings 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - "[0-9]+.[0-9]+.[0-9]+" 10 | 11 | jobs: 12 | create-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: taiki-e/create-gh-release-action@v1 17 | #with: 18 | # (optional) 19 | #changelog: CHANGELOG.md 20 | env: 21 | # (required) 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | upload-assets: 25 | strategy: 26 | matrix: 27 | os: 28 | - ubuntu-latest 29 | - macos-latest 30 | - windows-latest 31 | runs-on: ${{ matrix.os }} 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: taiki-e/upload-rust-binary-action@v1 35 | with: 36 | # (required) 37 | bin: bashrand 38 | # (optional) On which platform to distribute the `.tar.gz` file. 39 | # [default value: unix] 40 | # [possible values: all, unix, windows, none] 41 | tar: unix 42 | # (optional) On which platform to distribute the `.zip` file. 43 | # [default value: windows] 44 | # [possible values: all, unix, windows, none] 45 | zip: windows 46 | env: 47 | # (required) 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | test 3 | perf* 4 | flamegraph* 5 | out 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anes" 7 | version = "0.1.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 10 | 11 | [[package]] 12 | name = "anstream" 13 | version = "0.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 16 | dependencies = [ 17 | "anstyle", 18 | "anstyle-parse", 19 | "anstyle-query", 20 | "anstyle-wincon", 21 | "colorchoice", 22 | "is-terminal", 23 | "utf8parse", 24 | ] 25 | 26 | [[package]] 27 | name = "anstyle" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" 31 | 32 | [[package]] 33 | name = "anstyle-parse" 34 | version = "0.2.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 37 | dependencies = [ 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle-query" 43 | version = "1.0.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 46 | dependencies = [ 47 | "windows-sys", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-wincon" 52 | version = "1.0.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 55 | dependencies = [ 56 | "anstyle", 57 | "windows-sys", 58 | ] 59 | 60 | [[package]] 61 | name = "autocfg" 62 | version = "1.1.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 65 | 66 | [[package]] 67 | name = "bashrand" 68 | version = "0.2.1" 69 | dependencies = [ 70 | "clap", 71 | "criterion", 72 | "crossbeam-channel", 73 | "rayon", 74 | ] 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 81 | 82 | [[package]] 83 | name = "bumpalo" 84 | version = "3.13.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 87 | 88 | [[package]] 89 | name = "cast" 90 | version = "0.3.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 93 | 94 | [[package]] 95 | name = "cc" 96 | version = "1.0.79" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 99 | 100 | [[package]] 101 | name = "cfg-if" 102 | version = "1.0.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 105 | 106 | [[package]] 107 | name = "ciborium" 108 | version = "0.2.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" 111 | dependencies = [ 112 | "ciborium-io", 113 | "ciborium-ll", 114 | "serde", 115 | ] 116 | 117 | [[package]] 118 | name = "ciborium-io" 119 | version = "0.2.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" 122 | 123 | [[package]] 124 | name = "ciborium-ll" 125 | version = "0.2.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" 128 | dependencies = [ 129 | "ciborium-io", 130 | "half", 131 | ] 132 | 133 | [[package]] 134 | name = "clap" 135 | version = "4.3.2" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" 138 | dependencies = [ 139 | "clap_builder", 140 | "clap_derive", 141 | "once_cell", 142 | ] 143 | 144 | [[package]] 145 | name = "clap_builder" 146 | version = "4.3.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" 149 | dependencies = [ 150 | "anstream", 151 | "anstyle", 152 | "bitflags", 153 | "clap_lex", 154 | "strsim", 155 | ] 156 | 157 | [[package]] 158 | name = "clap_derive" 159 | version = "4.3.2" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" 162 | dependencies = [ 163 | "heck", 164 | "proc-macro2", 165 | "quote", 166 | "syn", 167 | ] 168 | 169 | [[package]] 170 | name = "clap_lex" 171 | version = "0.5.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 174 | 175 | [[package]] 176 | name = "colorchoice" 177 | version = "1.0.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 180 | 181 | [[package]] 182 | name = "criterion" 183 | version = "0.5.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 186 | dependencies = [ 187 | "anes", 188 | "cast", 189 | "ciborium", 190 | "clap", 191 | "criterion-plot", 192 | "is-terminal", 193 | "itertools", 194 | "num-traits", 195 | "once_cell", 196 | "oorandom", 197 | "plotters", 198 | "rayon", 199 | "regex", 200 | "serde", 201 | "serde_derive", 202 | "serde_json", 203 | "tinytemplate", 204 | "walkdir", 205 | ] 206 | 207 | [[package]] 208 | name = "criterion-plot" 209 | version = "0.5.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 212 | dependencies = [ 213 | "cast", 214 | "itertools", 215 | ] 216 | 217 | [[package]] 218 | name = "crossbeam-channel" 219 | version = "0.5.8" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 222 | dependencies = [ 223 | "cfg-if", 224 | "crossbeam-utils", 225 | ] 226 | 227 | [[package]] 228 | name = "crossbeam-deque" 229 | version = "0.8.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 232 | dependencies = [ 233 | "cfg-if", 234 | "crossbeam-epoch", 235 | "crossbeam-utils", 236 | ] 237 | 238 | [[package]] 239 | name = "crossbeam-epoch" 240 | version = "0.9.14" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" 243 | dependencies = [ 244 | "autocfg", 245 | "cfg-if", 246 | "crossbeam-utils", 247 | "memoffset", 248 | "scopeguard", 249 | ] 250 | 251 | [[package]] 252 | name = "crossbeam-utils" 253 | version = "0.8.15" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 256 | dependencies = [ 257 | "cfg-if", 258 | ] 259 | 260 | [[package]] 261 | name = "either" 262 | version = "1.8.1" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 265 | 266 | [[package]] 267 | name = "errno" 268 | version = "0.3.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 271 | dependencies = [ 272 | "errno-dragonfly", 273 | "libc", 274 | "windows-sys", 275 | ] 276 | 277 | [[package]] 278 | name = "errno-dragonfly" 279 | version = "0.1.2" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 282 | dependencies = [ 283 | "cc", 284 | "libc", 285 | ] 286 | 287 | [[package]] 288 | name = "half" 289 | version = "1.8.2" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 292 | 293 | [[package]] 294 | name = "heck" 295 | version = "0.4.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 298 | 299 | [[package]] 300 | name = "hermit-abi" 301 | version = "0.2.6" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 304 | dependencies = [ 305 | "libc", 306 | ] 307 | 308 | [[package]] 309 | name = "hermit-abi" 310 | version = "0.3.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 313 | 314 | [[package]] 315 | name = "io-lifetimes" 316 | version = "1.0.11" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 319 | dependencies = [ 320 | "hermit-abi 0.3.1", 321 | "libc", 322 | "windows-sys", 323 | ] 324 | 325 | [[package]] 326 | name = "is-terminal" 327 | version = "0.4.7" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 330 | dependencies = [ 331 | "hermit-abi 0.3.1", 332 | "io-lifetimes", 333 | "rustix", 334 | "windows-sys", 335 | ] 336 | 337 | [[package]] 338 | name = "itertools" 339 | version = "0.10.5" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 342 | dependencies = [ 343 | "either", 344 | ] 345 | 346 | [[package]] 347 | name = "itoa" 348 | version = "1.0.6" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 351 | 352 | [[package]] 353 | name = "js-sys" 354 | version = "0.3.63" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" 357 | dependencies = [ 358 | "wasm-bindgen", 359 | ] 360 | 361 | [[package]] 362 | name = "libc" 363 | version = "0.2.146" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" 366 | 367 | [[package]] 368 | name = "linux-raw-sys" 369 | version = "0.3.8" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 372 | 373 | [[package]] 374 | name = "log" 375 | version = "0.4.18" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" 378 | 379 | [[package]] 380 | name = "memoffset" 381 | version = "0.8.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" 384 | dependencies = [ 385 | "autocfg", 386 | ] 387 | 388 | [[package]] 389 | name = "num-traits" 390 | version = "0.2.15" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 393 | dependencies = [ 394 | "autocfg", 395 | ] 396 | 397 | [[package]] 398 | name = "num_cpus" 399 | version = "1.15.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 402 | dependencies = [ 403 | "hermit-abi 0.2.6", 404 | "libc", 405 | ] 406 | 407 | [[package]] 408 | name = "once_cell" 409 | version = "1.18.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 412 | 413 | [[package]] 414 | name = "oorandom" 415 | version = "11.1.3" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 418 | 419 | [[package]] 420 | name = "plotters" 421 | version = "0.3.4" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" 424 | dependencies = [ 425 | "num-traits", 426 | "plotters-backend", 427 | "plotters-svg", 428 | "wasm-bindgen", 429 | "web-sys", 430 | ] 431 | 432 | [[package]] 433 | name = "plotters-backend" 434 | version = "0.3.4" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" 437 | 438 | [[package]] 439 | name = "plotters-svg" 440 | version = "0.3.3" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" 443 | dependencies = [ 444 | "plotters-backend", 445 | ] 446 | 447 | [[package]] 448 | name = "proc-macro2" 449 | version = "1.0.60" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" 452 | dependencies = [ 453 | "unicode-ident", 454 | ] 455 | 456 | [[package]] 457 | name = "quote" 458 | version = "1.0.28" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 461 | dependencies = [ 462 | "proc-macro2", 463 | ] 464 | 465 | [[package]] 466 | name = "rayon" 467 | version = "1.7.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 470 | dependencies = [ 471 | "either", 472 | "rayon-core", 473 | ] 474 | 475 | [[package]] 476 | name = "rayon-core" 477 | version = "1.11.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 480 | dependencies = [ 481 | "crossbeam-channel", 482 | "crossbeam-deque", 483 | "crossbeam-utils", 484 | "num_cpus", 485 | ] 486 | 487 | [[package]] 488 | name = "regex" 489 | version = "1.8.4" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 492 | dependencies = [ 493 | "regex-syntax", 494 | ] 495 | 496 | [[package]] 497 | name = "regex-syntax" 498 | version = "0.7.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 501 | 502 | [[package]] 503 | name = "rustix" 504 | version = "0.37.19" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" 507 | dependencies = [ 508 | "bitflags", 509 | "errno", 510 | "io-lifetimes", 511 | "libc", 512 | "linux-raw-sys", 513 | "windows-sys", 514 | ] 515 | 516 | [[package]] 517 | name = "ryu" 518 | version = "1.0.13" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 521 | 522 | [[package]] 523 | name = "same-file" 524 | version = "1.0.6" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 527 | dependencies = [ 528 | "winapi-util", 529 | ] 530 | 531 | [[package]] 532 | name = "scopeguard" 533 | version = "1.1.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 536 | 537 | [[package]] 538 | name = "serde" 539 | version = "1.0.164" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 542 | dependencies = [ 543 | "serde_derive", 544 | ] 545 | 546 | [[package]] 547 | name = "serde_derive" 548 | version = "1.0.164" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" 551 | dependencies = [ 552 | "proc-macro2", 553 | "quote", 554 | "syn", 555 | ] 556 | 557 | [[package]] 558 | name = "serde_json" 559 | version = "1.0.96" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 562 | dependencies = [ 563 | "itoa", 564 | "ryu", 565 | "serde", 566 | ] 567 | 568 | [[package]] 569 | name = "strsim" 570 | version = "0.10.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 573 | 574 | [[package]] 575 | name = "syn" 576 | version = "2.0.18" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" 579 | dependencies = [ 580 | "proc-macro2", 581 | "quote", 582 | "unicode-ident", 583 | ] 584 | 585 | [[package]] 586 | name = "tinytemplate" 587 | version = "1.2.1" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 590 | dependencies = [ 591 | "serde", 592 | "serde_json", 593 | ] 594 | 595 | [[package]] 596 | name = "unicode-ident" 597 | version = "1.0.9" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 600 | 601 | [[package]] 602 | name = "utf8parse" 603 | version = "0.2.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 606 | 607 | [[package]] 608 | name = "walkdir" 609 | version = "2.3.3" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 612 | dependencies = [ 613 | "same-file", 614 | "winapi-util", 615 | ] 616 | 617 | [[package]] 618 | name = "wasm-bindgen" 619 | version = "0.2.86" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" 622 | dependencies = [ 623 | "cfg-if", 624 | "wasm-bindgen-macro", 625 | ] 626 | 627 | [[package]] 628 | name = "wasm-bindgen-backend" 629 | version = "0.2.86" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" 632 | dependencies = [ 633 | "bumpalo", 634 | "log", 635 | "once_cell", 636 | "proc-macro2", 637 | "quote", 638 | "syn", 639 | "wasm-bindgen-shared", 640 | ] 641 | 642 | [[package]] 643 | name = "wasm-bindgen-macro" 644 | version = "0.2.86" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" 647 | dependencies = [ 648 | "quote", 649 | "wasm-bindgen-macro-support", 650 | ] 651 | 652 | [[package]] 653 | name = "wasm-bindgen-macro-support" 654 | version = "0.2.86" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" 657 | dependencies = [ 658 | "proc-macro2", 659 | "quote", 660 | "syn", 661 | "wasm-bindgen-backend", 662 | "wasm-bindgen-shared", 663 | ] 664 | 665 | [[package]] 666 | name = "wasm-bindgen-shared" 667 | version = "0.2.86" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" 670 | 671 | [[package]] 672 | name = "web-sys" 673 | version = "0.3.63" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" 676 | dependencies = [ 677 | "js-sys", 678 | "wasm-bindgen", 679 | ] 680 | 681 | [[package]] 682 | name = "winapi" 683 | version = "0.3.9" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 686 | dependencies = [ 687 | "winapi-i686-pc-windows-gnu", 688 | "winapi-x86_64-pc-windows-gnu", 689 | ] 690 | 691 | [[package]] 692 | name = "winapi-i686-pc-windows-gnu" 693 | version = "0.4.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 696 | 697 | [[package]] 698 | name = "winapi-util" 699 | version = "0.1.5" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 702 | dependencies = [ 703 | "winapi", 704 | ] 705 | 706 | [[package]] 707 | name = "winapi-x86_64-pc-windows-gnu" 708 | version = "0.4.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 711 | 712 | [[package]] 713 | name = "windows-sys" 714 | version = "0.48.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 717 | dependencies = [ 718 | "windows-targets", 719 | ] 720 | 721 | [[package]] 722 | name = "windows-targets" 723 | version = "0.48.0" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 726 | dependencies = [ 727 | "windows_aarch64_gnullvm", 728 | "windows_aarch64_msvc", 729 | "windows_i686_gnu", 730 | "windows_i686_msvc", 731 | "windows_x86_64_gnu", 732 | "windows_x86_64_gnullvm", 733 | "windows_x86_64_msvc", 734 | ] 735 | 736 | [[package]] 737 | name = "windows_aarch64_gnullvm" 738 | version = "0.48.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 741 | 742 | [[package]] 743 | name = "windows_aarch64_msvc" 744 | version = "0.48.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 747 | 748 | [[package]] 749 | name = "windows_i686_gnu" 750 | version = "0.48.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 753 | 754 | [[package]] 755 | name = "windows_i686_msvc" 756 | version = "0.48.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 759 | 760 | [[package]] 761 | name = "windows_x86_64_gnu" 762 | version = "0.48.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 765 | 766 | [[package]] 767 | name = "windows_x86_64_gnullvm" 768 | version = "0.48.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 771 | 772 | [[package]] 773 | name = "windows_x86_64_msvc" 774 | version = "0.48.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 777 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bashrand" 3 | version = "0.2.1" 4 | edition = "2021" 5 | authors = ["Jorian Woltjer (J0R1AN)"] 6 | license = "MIT OR Apache-2.0" 7 | description = "Crack Bash's $RANDOM variable with 2-3 samples" 8 | readme = "README.md" 9 | homepage = "https://github.com/JorianWoltjer/BashRandomCracker" 10 | repository = "https://github.com/JorianWoltjer/BashRandomCracker" 11 | keywords = ["bash", "rng", "brute-force", "random", "algorithm"] 12 | categories = ["command-line-utilities"] 13 | 14 | [dependencies] 15 | clap = { version = "4.3.2", features = ["derive"] } 16 | crossbeam-channel = "0.5.8" 17 | rayon = "1.7.0" 18 | 19 | [dev-dependencies] 20 | criterion = "0.5.1" 21 | 22 | [[bench]] 23 | name = "benchmark" 24 | harness = false 25 | 26 | [profile.test] 27 | # Optimize tests, specifically for find_all...() which takes a few seconds 28 | opt-level = 3 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bash `$RANDOM` Cracker 2 | 3 | **Crack Bash's $RANDOM variable to get the internal seed and predict future values, after only 2-3 samples** 4 | 5 | This tool brute-forces the internal seed of Bash's `$RANDOM` variable after only 2-3 samples, in seconds. After doing so, you are able to predict all future values to break the randomness of this generator. This can be used to break password/token generation, or other systems that rely on this randomness for security. 6 | 7 | For context, the `bash` shell has a dynamic variable called `$RANDOM` you can access at any time to receive a random 15-bit number: 8 | 9 | ```Shell 10 | $ echo $RANDOM $RANDOM $RANDOM 11 | 3916 29151 6095 12 | ``` 13 | 14 | To seed this random number generator, you can set the variable directly to get the same values every time: 15 | 16 | ```Shell 17 | $ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM 18 | 24879 21848 15683 19 | $ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM 20 | 24879 21848 15683 21 | ``` 22 | 23 | There are **2 different calculations** depending on your **bash version**, which may make one seed give two different outputs. 24 | All versions *>= 5.1* will add a small extra step, and to this tool, are considered the "new" versions, while any lower versions are considered "old". This can be set explicitly using the `--version` (`-v`) argument in this tool, or otherwise, it will simply try both. 25 | 26 | ## Installation 27 | 28 | ```Bash 29 | cargo install bashrand 30 | ``` 31 | 32 | Or **download** and **extract** a pre-compiled binary from the [Releases](https://github.com/JorianWoltjer/BashRandomCracker/releases) page. 33 | 34 | ## Example 35 | 36 | ```Shell 37 | $ bashrand crack -n 3 $RANDOM $RANDOM $RANDOM 38 | Seed: 2137070299 +3 (old) 39 | Next 3 values: [22404, 16453, 2365] 40 | $ echo $RANDOM $RANDOM $RANDOM 41 | 22404 16453 2365 42 | ``` 43 | 44 | [![Bash $RANDOM Cracker - Showcase](https://asciinema.org/a/sa9iP4ZGtIMQdq2dl4Qv5Ga01.svg)](https://asciinema.org/a/sa9iP4ZGtIMQdq2dl4Qv5Ga01?autoplay=1) 45 | 46 | 59 | 60 | ## Usage 61 | 62 | Use `bashrand crack` and provide 2-3 `$RANDOM` variables for it to brute-force the seed. Afterward, you can use `bashrand get` to get an arbitrary part of the sequence in advance, providing the seed found in the first step. See example above as well. 63 | 64 | ```Shell 65 | $ bashrand --help 66 | Bash $RANDOM Cracker 67 | 68 | Usage: bashrand [OPTIONS] 69 | 70 | Commands: 71 | crack Provide random numbers to brute-force the seed 72 | get Get random numbers from a seed 73 | seeds Get next N seeds from a seed 74 | collide Find a seed where both old and new versions are the same 75 | help Print this message or the help of the given subcommand(s) 76 | 77 | Options: 78 | -v, --version 79 | Which bash version to use for generation (check with `bash --version`) 80 | 81 | [default: both] 82 | 83 | Possible values: 84 | - old: Bash versions 5.0 and older 85 | - new: Bash versions 5.1 and newer 86 | - both: Try both old and new versions if unsure 87 | 88 | -n, --number 89 | Number of values to generate 90 | 91 | [default: 10] 92 | ``` 93 | 94 | ```Shell 95 | $ bashrand crack --help 96 | Provide random numbers to brute-force the seed 97 | 98 | Usage: bashrand crack [OPTIONS] ... 99 | 100 | Arguments: 101 | ... 102 | 2-3 $RANDOM numbers as input for brute-forcing the seed 103 | 104 | 2 => multiple possible seeds, 3 => single seed 105 | ``` 106 | 107 | ```Shell 108 | $ bashrand get --help 109 | Get random numbers from a seed 110 | 111 | Usage: bashrand get [OPTIONS] 112 | 113 | Arguments: 114 | 115 | Seed to use for generating random numbers 116 | ``` 117 | 118 | The subcommands `seeds` and `collide` are for more advanced use, check them out if you want to. 119 | 120 | ## Reverse Engineering (How?!) 121 | 122 | To implement the `$RANDOM` algorithm, the first requirement is understanding the algorithm. Luckily Bash is open-source meaning all the clear and documented code is available. I used [this repository](https://github.com/bminor/bash) to look for anything related to the generation of this variable, and found the definition [here](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1914): 123 | 124 | ```C 125 | INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); 126 | ``` 127 | 128 | It assigns two functions to the variable: [`get_random()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1443-L1450) and [`assign_random`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1401-L1420). The first is when you access the variable like `echo $RANDOM`, and the second is for when you assign a value yourself to the variable, like `RANDOM=1337`. 129 | 130 | `get_random()` is the most interesting as we want to predict its output. It calls the [`get_random_number()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1422C1-L1440) function which itself calls [`brand()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L98C1-L112) inside the `/lib/sh/random.c` file. Here it starts to get interesting: 131 | 132 | ```C 133 | #define BASH_RAND_MAX 32767 /* 0x7fff - 16 bits */ 134 | 135 | /* Returns a pseudo-random number between 0 and 32767. */ 136 | int brand () { 137 | unsigned int ret; 138 | 139 | rseed = intrand32 (rseed); 140 | if (shell_compatibility_level > 50) 141 | ret = (rseed >> 16) ^ (rseed & 65535); 142 | else 143 | ret = rseed; 144 | return (ret & BASH_RAND_MAX); 145 | } 146 | ``` 147 | 148 | First, notice the `BASH_RAND_MAX` variable that is a 15-bit mask over the output. Also the `shell_compatibility_level` is the bash version, meaning if it is greater than version 50 (5.0) it will use a slightly different calculation. In both cases however it first gets a random number from [`intrand32()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L55-L84), and that already contains the core of the algorithm! 149 | 150 | ```C 151 | bits32_t h, l, t; 152 | u_bits32_t ret; 153 | 154 | /* Can't seed with 0. */ 155 | ret = (last == 0) ? 123459876 : last; 156 | h = ret / 127773; 157 | l = ret - (127773 * h); 158 | t = 16807 * l - 2836 * h; 159 | ret = (t < 0) ? t + 0x7fffffff : t; 160 | 161 | return (ret); 162 | ``` 163 | 164 | These are some simple calculations that we can recreate in any programming language. Importantly, it uses a `last` variable as its only argument in the calculation, which is given by `rseed = intrand32(rseed)` in the calling function. This means there is an internal seed that is iterated every time this function is called. If we can sync up with this seed, we will be able to predict any future values by copying the algorithm. 165 | 166 | The initial seed value is [complicated](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L87-L96), and is calculated with a lot of unpredictable data. If you remember it was also possible to *set* the seed, using [`assign_random()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1401-L1420). Looking at this function, it takes the value we set it to, and passes it to [`sbrand()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L115-L121), a very simple function that simply sets the seed directly to the provided value: 167 | 168 | ```C 169 | /* Set the random number generator seed to SEED. */ 170 | void sbrand (seed) unsigned long seed; { 171 | rseed = seed; 172 | last_random_value = 0; 173 | } 174 | ``` 175 | 176 | So in theory, if the seed was set manually, we could now simply try many seeds until we find one that matches the output. But what about seeds that aren't set manually? This case happens a lot more often. Luckily, the internal seed is an integer of only **32 bits**, easily brute-forcible with such a fast algorithm. After some testing, we can find the search space is actually only 30 bits for the newer bash versions and 31 bits for old bash versions. 177 | 178 | This program implements this brute-force method to search through the whole space in a few seconds, and shows the found seeds together with future values it predicts. 179 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use bashrand::{ 4 | CollisionCracker, MultiResultVersionCracker, New3Cracker, Old3Cracker, OneResultCracker, 5 | }; 6 | use crossbeam_channel::unbounded; 7 | 8 | fn new_bench(c: &mut Criterion) { 9 | c.bench_function("new", |b| { 10 | b.iter(|| { 11 | let cracker = New3Cracker::new(black_box([7195, 26887, 6346])); 12 | assert_eq!(cracker.find(), Some(black_box(31337))); 13 | }) 14 | }); 15 | } 16 | 17 | fn old_bench(c: &mut Criterion) { 18 | c.bench_function("old", |b| { 19 | b.iter(|| { 20 | let cracker = Old3Cracker::new(black_box([895, 5874, 11135])); 21 | assert_eq!(cracker.find(), Some(black_box(31337))); 22 | }) 23 | }); 24 | } 25 | 26 | fn collide_bench(c: &mut Criterion) { 27 | c.bench_function("collide", |b| { 28 | b.iter(|| { 29 | let (tx, rx) = unbounded(); 30 | let cracker = CollisionCracker::new(black_box(37)); 31 | cracker.find(&tx); 32 | let (res1, res2) = (rx.recv().unwrap(), rx.recv().unwrap()); 33 | assert!(res1 == 901656913 || res2 == 901656913); 34 | assert!(res1 == 910416221 || res2 == 910416221); 35 | }) 36 | }); 37 | } 38 | 39 | criterion_group!(benches, new_bench, old_bench, collide_bench); 40 | criterion_main!(benches); 41 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand, ValueEnum}; 2 | 3 | /// Bash $RANDOM Cracker 4 | #[derive(Parser, Debug)] 5 | #[command(name = "bashrand")] 6 | pub struct Args { 7 | #[command(subcommand)] 8 | pub command: SubCommands, 9 | 10 | /// Which bash version to use for generation (check with `bash --version`) 11 | #[arg(value_enum, global = true, short, long, default_value = "both")] 12 | pub version: Version, 13 | 14 | /// Number of values to generate 15 | #[arg(global = true, short, long, default_value = "10")] 16 | pub number: usize, 17 | } 18 | 19 | #[derive(Subcommand, Debug)] 20 | pub enum SubCommands { 21 | /// Provide random numbers to brute-force the seed 22 | Crack { 23 | /// 2-3 $RANDOM numbers as input for brute-forcing the seed 24 | /// 25 | /// 2 => multiple possible seeds, 3 => single seed 26 | #[clap(num_args = 2..=3, required = true)] 27 | numbers: Vec, 28 | }, 29 | 30 | /// Get random numbers from a seed 31 | Get { 32 | /// Seed to use for generating random numbers 33 | seed: u32, 34 | 35 | /// Skip the first n numbers 36 | #[arg(short, long, default_value = "0")] 37 | skip: usize, 38 | }, 39 | 40 | /// Get next N seeds from a seed 41 | Seeds { 42 | /// Seed to use for generating random numbers 43 | seed: u32, 44 | }, 45 | 46 | /// Find a seed where both old and new versions are the same 47 | Collide { 48 | /// Resulting number to target 49 | n: u16, 50 | }, 51 | } 52 | 53 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] 54 | pub enum Version { 55 | /// Bash versions 5.0 and older 56 | Old, 57 | 58 | /// Bash versions 5.1 and newer 59 | New, 60 | 61 | /// Try both old and new versions if unsure 62 | Both, 63 | } 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use crossbeam_channel::Sender; 4 | use rayon::prelude::*; 5 | 6 | use random::Random; 7 | 8 | pub mod cli; 9 | pub mod log; 10 | pub mod random; 11 | 12 | pub type Result = std::result::Result>; 13 | 14 | pub trait OneResultCracker { 15 | fn find(&self) -> Option; 16 | } 17 | // Split `old` boolean into separate structs for performance reasons 18 | pub struct New3Cracker { 19 | target: [u16; 3], 20 | } 21 | impl New3Cracker { 22 | pub fn new(target: [u16; 3]) -> Self { 23 | Self { target } 24 | } 25 | } 26 | impl OneResultCracker for New3Cracker { 27 | fn find(&self) -> Option { 28 | // 30 bits 29 | (0..=u32::MAX / 4).into_par_iter().find_any(|&i| { 30 | let mut rng = Random::new(i, false); 31 | 32 | rng.next_16() == self.target[0] 33 | && rng.next_16() == self.target[1] 34 | && rng.next_16() == self.target[2] 35 | }) 36 | } 37 | } 38 | 39 | pub struct Old3Cracker { 40 | target: [u16; 3], 41 | } 42 | impl Old3Cracker { 43 | pub fn new(target: [u16; 3]) -> Self { 44 | Self { target } 45 | } 46 | } 47 | impl OneResultCracker for Old3Cracker { 48 | fn find(&self) -> Option { 49 | // 31 bits 50 | (0..=u32::MAX / 2).into_par_iter().find_any(|&i| { 51 | let mut rng = Random::new(i, true); 52 | 53 | rng.next_16() == self.target[0] 54 | && rng.next_16() == self.target[1] 55 | && rng.next_16() == self.target[2] 56 | }) 57 | } 58 | } 59 | 60 | pub trait MultiResultCracker { 61 | fn find(&self, tx: &Sender<(u32, bool)>); 62 | } 63 | 64 | pub struct New2Cracker { 65 | target: [u16; 2], 66 | } 67 | impl New2Cracker { 68 | pub fn new(target: [u16; 2]) -> Self { 69 | Self { target } 70 | } 71 | } 72 | impl MultiResultCracker for New2Cracker { 73 | fn find(&self, tx: &Sender<(u32, bool)>) { 74 | (0..=u32::MAX / 4).into_par_iter().for_each(|i| { 75 | let mut rng = Random::new(i, false); 76 | 77 | if rng.next_16() == self.target[0] && rng.next_16() == self.target[1] { 78 | tx.send((i, false)).unwrap(); 79 | } 80 | }); 81 | } 82 | } 83 | 84 | pub struct Old2Cracker { 85 | target: [u16; 2], 86 | } 87 | impl Old2Cracker { 88 | pub fn new(target: [u16; 2]) -> Self { 89 | Self { target } 90 | } 91 | } 92 | impl MultiResultCracker for Old2Cracker { 93 | fn find(&self, tx: &Sender<(u32, bool)>) { 94 | // 31 bits 95 | (0..=u32::MAX / 2).into_par_iter().for_each(|i| { 96 | let mut rng = Random::new(i, true); 97 | 98 | if rng.next_16() == self.target[0] && rng.next_16() == self.target[1] { 99 | tx.send((i, true)).unwrap(); 100 | } 101 | }); 102 | } 103 | } 104 | 105 | pub trait MultiResultVersionCracker { 106 | fn find(&self, tx: &Sender); 107 | } 108 | 109 | pub struct CollisionCracker { 110 | target: u16, 111 | } 112 | impl CollisionCracker { 113 | pub fn new(target: u16) -> Self { 114 | Self { target } 115 | } 116 | } 117 | impl MultiResultVersionCracker for CollisionCracker { 118 | fn find(&self, tx: &Sender) { 119 | (0..=u32::MAX / 4).into_par_iter().for_each(|i| { 120 | let mut rng_old = Random::new(i, true); 121 | // Setting RANDOM= already iterates once 122 | rng_old.next_16(); 123 | 124 | if rng_old.next_16() == self.target { 125 | // Also check new version 126 | let mut rng_new = Random::new(i, false); 127 | rng_new.next_16(); 128 | 129 | if rng_new.next_16() == self.target { 130 | tx.send(i).unwrap(); 131 | } 132 | } 133 | }) 134 | } 135 | } 136 | 137 | pub struct New1Cracker { 138 | target: u16, 139 | } 140 | impl New1Cracker { 141 | pub fn new(target: u16) -> Self { 142 | Self { target } 143 | } 144 | } 145 | impl MultiResultVersionCracker for New1Cracker { 146 | fn find(&self, tx: &Sender) { 147 | (0..=u32::MAX / 4).into_par_iter().for_each(|i| { 148 | let mut rng = Random::new(i, false); 149 | // Setting RANDOM= already iterates once 150 | rng.next_16(); 151 | 152 | if rng.next_16() == self.target { 153 | tx.send(i).unwrap(); 154 | } 155 | }); 156 | } 157 | } 158 | 159 | pub struct Old1Cracker { 160 | target: u16, 161 | } 162 | impl Old1Cracker { 163 | pub fn new(target: u16) -> Self { 164 | Self { target } 165 | } 166 | } 167 | impl MultiResultVersionCracker for Old1Cracker { 168 | fn find(&self, tx: &Sender) { 169 | (0..=u32::MAX / 2).into_par_iter().for_each(|i| { 170 | let mut rng = Random::new(i, true); 171 | // Setting RANDOM= already iterates once 172 | rng.next_16(); 173 | 174 | if rng.next_16() == self.target { 175 | tx.send(i).unwrap(); 176 | } 177 | }); 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use std::thread; 184 | 185 | use super::*; 186 | 187 | #[test] 188 | fn find_new() { 189 | let cracker = New3Cracker::new([24697, 15233, 8710]); 190 | assert_eq!(cracker.find(), Some(1337)); 191 | } 192 | 193 | #[test] 194 | fn find_old() { 195 | let cracker = Old3Cracker::new([24879, 21848, 15683]); 196 | assert_eq!(cracker.find(), Some(1337)); 197 | } 198 | 199 | #[test] 200 | fn find_all_new() { 201 | let cracker = New2Cracker::new([20814, 24386]); 202 | let (tx, rx) = crossbeam_channel::unbounded(); 203 | 204 | thread::spawn(move || { 205 | cracker.find(&tx); 206 | }); 207 | 208 | let mut results = rx.into_iter().map(|(n, _old)| n).collect::>(); 209 | results.sort_unstable(); 210 | assert_eq!(results, vec![0, 123459876, 572750907]); 211 | } 212 | 213 | #[test] 214 | fn find_all_old() { 215 | let cracker = Old2Cracker::new([20034, 24315]); 216 | let (tx, rx) = crossbeam_channel::unbounded(); 217 | 218 | thread::spawn(move || { 219 | cracker.find(&tx); 220 | }); 221 | 222 | let mut results = rx.into_iter().map(|(n, _old)| n).collect::>(); 223 | results.sort_unstable(); 224 | assert_eq!( 225 | results, 226 | vec![0, 123459876, 852022490, 1082141963, 2040824050, 2147483647] 227 | ); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | /// Print a successfull message 4 | pub fn success(text: T) { 5 | eprintln!("[\x1B[92m+\x1B[0m] {}", text.to_string()); 6 | } 7 | 8 | /// Print an error message, and *exit* the program 9 | pub fn error(text: T) { 10 | eprintln!("[\x1B[91mFAIL\x1B[0m] {}", text.to_string()); 11 | exit(1); 12 | } 13 | 14 | /// Print a warning message 15 | pub fn warn(text: T) { 16 | eprintln!("[\x1B[93m!\x1B[0m] {}", text.to_string()); 17 | } 18 | 19 | /// Print an informational message 20 | pub fn info(text: T) { 21 | eprintln!("[\x1B[94m*\x1B[0m] {}", text.to_string()); 22 | } 23 | 24 | /// Print a progress message 25 | pub fn progress(text: T) { 26 | eprintln!("[\x1B[96m~\x1B[0m] {}", text.to_string()); 27 | } 28 | 29 | /// Print a debug message 30 | pub fn debug(text: T) { 31 | eprintln!("[\x1B[90mDEBUG\x1B[0m] {}", text.to_string()); 32 | } 33 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | use clap::Parser; 4 | use crossbeam_channel::unbounded; 5 | 6 | use bashrand::{ 7 | cli::{Args, SubCommands, Version}, 8 | log, 9 | random::{Random, BASH_RAND_MAX}, 10 | CollisionCracker, MultiResultCracker, MultiResultVersionCracker, New1Cracker, New2Cracker, 11 | New3Cracker, Old1Cracker, Old2Cracker, Old3Cracker, OneResultCracker, Result, 12 | }; 13 | 14 | fn main() { 15 | let args = Args::parse(); 16 | 17 | if let Err(e) = do_main(args) { 18 | log::error(e); 19 | } 20 | } 21 | 22 | fn print_seed_and_clone(seed: u32, skip: usize, is_old: bool, number: usize) { 23 | println!( 24 | "Seed: {seed}{} ({})", 25 | match skip { 26 | 0 => String::from(""), 27 | _ => format!(" +{skip}"), 28 | }, 29 | if is_old { "old" } else { "new" } 30 | ); 31 | 32 | let mut rng = Random::new(seed, is_old); 33 | rng.skip(skip); 34 | 35 | match number { 36 | 0 => (), 37 | 1 => println!(" Next value: {}", rng.next_16()), 38 | _ => println!(" Next {} values: {:?}", number, rng.next_16_n(number)), 39 | } 40 | } 41 | 42 | fn do_main(args: Args) -> Result<()> { 43 | let (version_old, version_new) = match args.version { 44 | Version::Old => (true, false), 45 | Version::New => (false, true), 46 | Version::Both => (true, true), 47 | }; 48 | 49 | match args.command { 50 | SubCommands::Crack { numbers } => { 51 | if numbers.iter().any(|n| *n > BASH_RAND_MAX) { 52 | return Err( 53 | format!("Numbers must be at most 15 bits (max: {})", BASH_RAND_MAX).into(), 54 | ); 55 | }; 56 | 57 | match numbers.len() { 58 | // Certain (one possible seed) 59 | 3 => { 60 | let numbers = [numbers[0], numbers[1], numbers[2]]; 61 | 62 | log::progress("Searching for seeds...".to_string()); 63 | 64 | let (mut seed, mut is_old) = (None, false); 65 | 66 | if version_new { 67 | let cracker = New3Cracker::new(numbers); 68 | seed = cracker.find(); 69 | } 70 | if version_old && seed.is_none() { 71 | let cracker = Old3Cracker::new(numbers); 72 | seed = cracker.find(); 73 | is_old = true; 74 | } 75 | 76 | print_seed_and_clone(seed.ok_or("Couldn't find seed")?, 3, is_old, args.number); 77 | 78 | log::success("Finished!"); 79 | } 80 | // Uncertain (multiple possible seeds) 81 | 2 => { 82 | let numbers = [numbers[0], numbers[1]]; 83 | 84 | let (tx, rx) = unbounded(); 85 | 86 | log::progress("Searching for seeds...".to_string()); 87 | 88 | thread::spawn(move || { 89 | if version_new { 90 | let cracker = New2Cracker::new(numbers); 91 | cracker.find(&tx); 92 | } 93 | if version_old { 94 | let cracker = Old2Cracker::new(numbers); 95 | cracker.find(&tx); 96 | } 97 | }); 98 | 99 | // Stream all found seeds 100 | let mut count = 0; 101 | for (seed, old) in rx { 102 | print_seed_and_clone(seed, 2, old, args.number); 103 | count += 1; 104 | } 105 | 106 | if count == 0 { 107 | return Err("Couldn't find seed".into()); 108 | } else { 109 | log::success(format!("Finished! ({count} seeds)")); 110 | } 111 | } 112 | _ => unreachable!(), 113 | } 114 | } 115 | SubCommands::Get { seed, skip } => { 116 | if version_new { 117 | print_seed_and_clone(seed, skip, false, args.number); 118 | } 119 | if version_old { 120 | print_seed_and_clone(seed, skip, true, args.number); 121 | } 122 | } 123 | SubCommands::Seeds { seed } => { 124 | // Seed generation is the same for both versions 125 | let mut rng = Random::new(seed, false); 126 | let seeds = rng.next_seed_n(args.number); 127 | println!("Next {} seeds: {:?}", args.number, seeds); 128 | } 129 | SubCommands::Collide { n } => { 130 | let (tx, rx) = unbounded(); 131 | 132 | log::progress("Searching for seeds...".to_string()); 133 | 134 | thread::spawn(move || match (version_new, version_old) { 135 | (true, true) => { 136 | let cracker = CollisionCracker::new(n); 137 | cracker.find(&tx); 138 | } 139 | (true, false) => { 140 | let cracker = New1Cracker::new(n); 141 | cracker.find(&tx); 142 | } 143 | (false, true) => { 144 | let cracker = Old1Cracker::new(n); 145 | cracker.find(&tx); 146 | } 147 | (_, _) => unreachable!("No version selected"), 148 | }); 149 | 150 | // Stream all found seeds 151 | let mut count = 0; 152 | for seed in rx { 153 | println!("Seed: {seed} = {n}"); 154 | count += 1; 155 | } 156 | 157 | if count == 0 { 158 | return Err("Couldn't find seed".into()); 159 | } else { 160 | log::success(format!("Finished! ({count} seeds)")); 161 | } 162 | } 163 | } 164 | Ok(()) 165 | } 166 | -------------------------------------------------------------------------------- /src/random.rs: -------------------------------------------------------------------------------- 1 | pub const BASH_RAND_MAX: u16 = 0x7fff; // 15 bits 2 | 3 | pub struct Random { 4 | pub seed: u32, 5 | last: u16, 6 | /// If true, use the old algorithm from bash 5.0 and earlier (check with `bash --version`) 7 | is_old: bool, 8 | } 9 | impl Random { 10 | pub fn new(seed: u32, is_old: bool) -> Self { 11 | // TODO: support `long` seed input 12 | Self { 13 | seed, 14 | last: 0, 15 | is_old, 16 | } 17 | } 18 | 19 | pub fn next_16(&mut self) -> u16 { 20 | self.next_seed(); 21 | 22 | let result = if self.is_old { 23 | // Bash 5.0 and earlier 24 | self.seed as u16 & BASH_RAND_MAX 25 | } else { 26 | // Bash 5.1 and later 27 | ((self.seed >> 16) ^ (self.seed & 0xffff)) as u16 & BASH_RAND_MAX 28 | }; 29 | // Skip if same as last 30 | if result == self.last { 31 | self.next_16() 32 | } else { 33 | self.last = result; 34 | result 35 | } 36 | } 37 | pub fn next_16_n(&mut self, n: usize) -> Vec { 38 | let mut result = Vec::with_capacity(n); 39 | for _ in 0..n { 40 | result.push(self.next_16()); 41 | } 42 | result 43 | } 44 | 45 | pub fn skip(&mut self, n: usize) { 46 | for _ in 0..n { 47 | self.next_16(); 48 | } 49 | } 50 | 51 | pub fn next_seed(&mut self) -> u32 { 52 | if self.seed == 0 { 53 | self.seed = 123459876; 54 | } 55 | let h: i32 = self.seed as i32 / 127773; 56 | let l: i32 = self.seed as i32 - (127773 * h); 57 | let t: i32 = 16807 * l - 2836 * h; 58 | self.seed = if t < 0 { t + 0x7fffffff } else { t } as u32; 59 | 60 | self.seed 61 | } 62 | 63 | pub fn next_seed_n(&mut self, n: usize) -> Vec { 64 | let mut result = Vec::with_capacity(n); 65 | for _ in 0..n { 66 | result.push(self.next_seed()); 67 | } 68 | result 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn bash_52_zero() { 78 | // $ bash5.2 -c 'RANDOM=0; echo $RANDOM $RANDOM $RANDOM' 79 | let mut rng = Random::new(0, false); 80 | assert_eq!(rng.next_16_n(3), vec![20814, 24386, 149]); 81 | } 82 | 83 | #[test] 84 | fn bash_52_n() { 85 | // $ bash5.2 -c 'RANDOM=1337; echo $RANDOM $RANDOM $RANDOM' 86 | let mut rng = Random::new(1337, false); 87 | assert_eq!(rng.next_16_n(3), vec![24697, 15233, 8710]); 88 | } 89 | 90 | #[test] 91 | fn bash_52_big() { 92 | // $ bash5.2 -c 'RANDOM=2147483646; echo $RANDOM $RANDOM $RANDOM' 93 | let mut rng = Random::new(2147483646, false); 94 | assert_eq!(rng.next_16_n(3), vec![16807, 10791, 19566]); 95 | } 96 | 97 | #[test] 98 | fn bash_50_zero() { 99 | // $ bash5.0 -c 'RANDOM=0; echo $RANDOM $RANDOM $RANDOM' 100 | let mut rng = Random::new(0, true); 101 | assert_eq!(rng.next_16_n(3), vec![20034, 24315, 12703]); 102 | } 103 | 104 | #[test] 105 | fn bash_50_n() { 106 | // $ bash5.0 -c 'RANDOM=1337; echo $RANDOM $RANDOM $RANDOM' 107 | let mut rng = Random::new(1337, true); 108 | assert_eq!(rng.next_16_n(3), vec![24879, 21848, 15683]); 109 | } 110 | 111 | #[test] 112 | fn bash_50_big() { 113 | // $ bash5.0 -c 'RANDOM=2147483646; echo $RANDOM $RANDOM $RANDOM' 114 | let mut rng = Random::new(2147483646, true); 115 | assert_eq!(rng.next_16_n(3), vec![15960, 17678, 21286]); 116 | } 117 | } 118 | --------------------------------------------------------------------------------