├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── decrypt.rs └── encrypt.rs ├── src ├── constants.rs ├── data │ ├── bigrams.txt │ ├── monograms.txt │ ├── quadgrams.txt │ └── trigrams.txt ├── decrypt.rs ├── enigma.rs ├── fitness.rs ├── lib.rs ├── main.rs ├── plugboard.rs ├── reflector.rs └── rotor.rs └── tests ├── decrypt.rs ├── encrypt.rs └── texts.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Generated by Cargo 8 | /target/ 9 | 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "atty" 14 | version = "0.2.14" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 17 | dependencies = [ 18 | "hermit-abi", 19 | "libc", 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.0.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.2.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 34 | 35 | [[package]] 36 | name = "bstr" 37 | version = "0.2.15" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" 40 | dependencies = [ 41 | "lazy_static", 42 | "memchr", 43 | "regex-automata", 44 | "serde", 45 | ] 46 | 47 | [[package]] 48 | name = "bumpalo" 49 | version = "3.6.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" 52 | 53 | [[package]] 54 | name = "byteorder" 55 | version = "1.4.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 58 | 59 | [[package]] 60 | name = "cast" 61 | version = "0.2.5" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75" 64 | dependencies = [ 65 | "rustc_version", 66 | ] 67 | 68 | [[package]] 69 | name = "cfg-if" 70 | version = "1.0.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 73 | 74 | [[package]] 75 | name = "clap" 76 | version = "2.33.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 79 | dependencies = [ 80 | "ansi_term", 81 | "atty", 82 | "bitflags", 83 | "strsim", 84 | "textwrap", 85 | "unicode-width", 86 | "vec_map", 87 | ] 88 | 89 | [[package]] 90 | name = "criterion" 91 | version = "0.3.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" 94 | dependencies = [ 95 | "atty", 96 | "cast", 97 | "clap", 98 | "criterion-plot", 99 | "csv", 100 | "itertools 0.10.0", 101 | "lazy_static", 102 | "num-traits", 103 | "oorandom", 104 | "plotters", 105 | "rayon", 106 | "regex", 107 | "serde", 108 | "serde_cbor", 109 | "serde_derive", 110 | "serde_json", 111 | "tinytemplate", 112 | "walkdir", 113 | ] 114 | 115 | [[package]] 116 | name = "criterion-plot" 117 | version = "0.4.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" 120 | dependencies = [ 121 | "cast", 122 | "itertools 0.9.0", 123 | ] 124 | 125 | [[package]] 126 | name = "crossbeam-channel" 127 | version = "0.5.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 130 | dependencies = [ 131 | "cfg-if", 132 | "crossbeam-utils", 133 | ] 134 | 135 | [[package]] 136 | name = "crossbeam-deque" 137 | version = "0.8.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 140 | dependencies = [ 141 | "cfg-if", 142 | "crossbeam-epoch", 143 | "crossbeam-utils", 144 | ] 145 | 146 | [[package]] 147 | name = "crossbeam-epoch" 148 | version = "0.9.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" 151 | dependencies = [ 152 | "cfg-if", 153 | "crossbeam-utils", 154 | "lazy_static", 155 | "memoffset", 156 | "scopeguard", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-utils" 161 | version = "0.8.3" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" 164 | dependencies = [ 165 | "autocfg", 166 | "cfg-if", 167 | "lazy_static", 168 | ] 169 | 170 | [[package]] 171 | name = "csv" 172 | version = "1.1.6" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 175 | dependencies = [ 176 | "bstr", 177 | "csv-core", 178 | "itoa", 179 | "ryu", 180 | "serde", 181 | ] 182 | 183 | [[package]] 184 | name = "csv-core" 185 | version = "0.1.10" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 188 | dependencies = [ 189 | "memchr", 190 | ] 191 | 192 | [[package]] 193 | name = "either" 194 | version = "1.6.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 197 | 198 | [[package]] 199 | name = "getrandom" 200 | version = "0.2.2" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 203 | dependencies = [ 204 | "cfg-if", 205 | "libc", 206 | "wasi", 207 | ] 208 | 209 | [[package]] 210 | name = "half" 211 | version = "1.7.1" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" 214 | 215 | [[package]] 216 | name = "hermit-abi" 217 | version = "0.1.18" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 220 | dependencies = [ 221 | "libc", 222 | ] 223 | 224 | [[package]] 225 | name = "itertools" 226 | version = "0.9.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 229 | dependencies = [ 230 | "either", 231 | ] 232 | 233 | [[package]] 234 | name = "itertools" 235 | version = "0.10.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" 238 | dependencies = [ 239 | "either", 240 | ] 241 | 242 | [[package]] 243 | name = "itoa" 244 | version = "0.4.7" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 247 | 248 | [[package]] 249 | name = "js-sys" 250 | version = "0.3.50" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 253 | dependencies = [ 254 | "wasm-bindgen", 255 | ] 256 | 257 | [[package]] 258 | name = "lazy_static" 259 | version = "1.4.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 262 | 263 | [[package]] 264 | name = "libc" 265 | version = "0.2.93" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" 268 | 269 | [[package]] 270 | name = "log" 271 | version = "0.4.14" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 274 | dependencies = [ 275 | "cfg-if", 276 | ] 277 | 278 | [[package]] 279 | name = "memchr" 280 | version = "2.3.4" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 283 | 284 | [[package]] 285 | name = "memoffset" 286 | version = "0.6.3" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" 289 | dependencies = [ 290 | "autocfg", 291 | ] 292 | 293 | [[package]] 294 | name = "num-traits" 295 | version = "0.2.14" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 298 | dependencies = [ 299 | "autocfg", 300 | ] 301 | 302 | [[package]] 303 | name = "num_cpus" 304 | version = "1.13.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 307 | dependencies = [ 308 | "hermit-abi", 309 | "libc", 310 | ] 311 | 312 | [[package]] 313 | name = "oorandom" 314 | version = "11.1.3" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 317 | 318 | [[package]] 319 | name = "ordered-float" 320 | version = "2.1.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" 323 | dependencies = [ 324 | "num-traits", 325 | ] 326 | 327 | [[package]] 328 | name = "plotters" 329 | version = "0.3.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" 332 | dependencies = [ 333 | "num-traits", 334 | "plotters-backend", 335 | "plotters-svg", 336 | "wasm-bindgen", 337 | "web-sys", 338 | ] 339 | 340 | [[package]] 341 | name = "plotters-backend" 342 | version = "0.3.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" 345 | 346 | [[package]] 347 | name = "plotters-svg" 348 | version = "0.3.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" 351 | dependencies = [ 352 | "plotters-backend", 353 | ] 354 | 355 | [[package]] 356 | name = "ppv-lite86" 357 | version = "0.2.10" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 360 | 361 | [[package]] 362 | name = "proc-macro2" 363 | version = "1.0.26" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 366 | dependencies = [ 367 | "unicode-xid", 368 | ] 369 | 370 | [[package]] 371 | name = "quote" 372 | version = "1.0.9" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 375 | dependencies = [ 376 | "proc-macro2", 377 | ] 378 | 379 | [[package]] 380 | name = "rand" 381 | version = "0.8.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 384 | dependencies = [ 385 | "libc", 386 | "rand_chacha", 387 | "rand_core", 388 | "rand_hc", 389 | ] 390 | 391 | [[package]] 392 | name = "rand_chacha" 393 | version = "0.3.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 396 | dependencies = [ 397 | "ppv-lite86", 398 | "rand_core", 399 | ] 400 | 401 | [[package]] 402 | name = "rand_core" 403 | version = "0.6.2" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 406 | dependencies = [ 407 | "getrandom", 408 | ] 409 | 410 | [[package]] 411 | name = "rand_hc" 412 | version = "0.3.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 415 | dependencies = [ 416 | "rand_core", 417 | ] 418 | 419 | [[package]] 420 | name = "rayon" 421 | version = "1.5.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 424 | dependencies = [ 425 | "autocfg", 426 | "crossbeam-deque", 427 | "either", 428 | "rayon-core", 429 | ] 430 | 431 | [[package]] 432 | name = "rayon-core" 433 | version = "1.9.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 436 | dependencies = [ 437 | "crossbeam-channel", 438 | "crossbeam-deque", 439 | "crossbeam-utils", 440 | "lazy_static", 441 | "num_cpus", 442 | ] 443 | 444 | [[package]] 445 | name = "regex" 446 | version = "1.4.5" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" 449 | dependencies = [ 450 | "regex-syntax", 451 | ] 452 | 453 | [[package]] 454 | name = "regex-automata" 455 | version = "0.1.9" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 458 | dependencies = [ 459 | "byteorder", 460 | ] 461 | 462 | [[package]] 463 | name = "regex-syntax" 464 | version = "0.6.23" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" 467 | 468 | [[package]] 469 | name = "rustc_version" 470 | version = "0.2.3" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 473 | dependencies = [ 474 | "semver", 475 | ] 476 | 477 | [[package]] 478 | name = "ryu" 479 | version = "1.0.5" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 482 | 483 | [[package]] 484 | name = "same-file" 485 | version = "1.0.6" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 488 | dependencies = [ 489 | "winapi-util", 490 | ] 491 | 492 | [[package]] 493 | name = "scopeguard" 494 | version = "1.1.0" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 497 | 498 | [[package]] 499 | name = "semver" 500 | version = "0.9.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 503 | dependencies = [ 504 | "semver-parser", 505 | ] 506 | 507 | [[package]] 508 | name = "semver-parser" 509 | version = "0.7.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 512 | 513 | [[package]] 514 | name = "serde" 515 | version = "1.0.125" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 518 | 519 | [[package]] 520 | name = "serde_cbor" 521 | version = "0.11.1" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" 524 | dependencies = [ 525 | "half", 526 | "serde", 527 | ] 528 | 529 | [[package]] 530 | name = "serde_derive" 531 | version = "1.0.125" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 534 | dependencies = [ 535 | "proc-macro2", 536 | "quote", 537 | "syn", 538 | ] 539 | 540 | [[package]] 541 | name = "serde_json" 542 | version = "1.0.64" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 545 | dependencies = [ 546 | "itoa", 547 | "ryu", 548 | "serde", 549 | ] 550 | 551 | [[package]] 552 | name = "strsim" 553 | version = "0.8.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 556 | 557 | [[package]] 558 | name = "syn" 559 | version = "1.0.69" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" 562 | dependencies = [ 563 | "proc-macro2", 564 | "quote", 565 | "unicode-xid", 566 | ] 567 | 568 | [[package]] 569 | name = "textwrap" 570 | version = "0.11.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 573 | dependencies = [ 574 | "unicode-width", 575 | ] 576 | 577 | [[package]] 578 | name = "tinytemplate" 579 | version = "1.2.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 582 | dependencies = [ 583 | "serde", 584 | "serde_json", 585 | ] 586 | 587 | [[package]] 588 | name = "ultra" 589 | version = "0.6.1" 590 | dependencies = [ 591 | "clap", 592 | "criterion", 593 | "itertools 0.10.0", 594 | "lazy_static", 595 | "ordered-float", 596 | "rand", 597 | "rayon", 598 | ] 599 | 600 | [[package]] 601 | name = "unicode-width" 602 | version = "0.1.8" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 605 | 606 | [[package]] 607 | name = "unicode-xid" 608 | version = "0.2.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 611 | 612 | [[package]] 613 | name = "vec_map" 614 | version = "0.8.2" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 617 | 618 | [[package]] 619 | name = "walkdir" 620 | version = "2.3.2" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 623 | dependencies = [ 624 | "same-file", 625 | "winapi", 626 | "winapi-util", 627 | ] 628 | 629 | [[package]] 630 | name = "wasi" 631 | version = "0.10.2+wasi-snapshot-preview1" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 634 | 635 | [[package]] 636 | name = "wasm-bindgen" 637 | version = "0.2.73" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 640 | dependencies = [ 641 | "cfg-if", 642 | "wasm-bindgen-macro", 643 | ] 644 | 645 | [[package]] 646 | name = "wasm-bindgen-backend" 647 | version = "0.2.73" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 650 | dependencies = [ 651 | "bumpalo", 652 | "lazy_static", 653 | "log", 654 | "proc-macro2", 655 | "quote", 656 | "syn", 657 | "wasm-bindgen-shared", 658 | ] 659 | 660 | [[package]] 661 | name = "wasm-bindgen-macro" 662 | version = "0.2.73" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 665 | dependencies = [ 666 | "quote", 667 | "wasm-bindgen-macro-support", 668 | ] 669 | 670 | [[package]] 671 | name = "wasm-bindgen-macro-support" 672 | version = "0.2.73" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 675 | dependencies = [ 676 | "proc-macro2", 677 | "quote", 678 | "syn", 679 | "wasm-bindgen-backend", 680 | "wasm-bindgen-shared", 681 | ] 682 | 683 | [[package]] 684 | name = "wasm-bindgen-shared" 685 | version = "0.2.73" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 688 | 689 | [[package]] 690 | name = "web-sys" 691 | version = "0.3.50" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" 694 | dependencies = [ 695 | "js-sys", 696 | "wasm-bindgen", 697 | ] 698 | 699 | [[package]] 700 | name = "winapi" 701 | version = "0.3.9" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 704 | dependencies = [ 705 | "winapi-i686-pc-windows-gnu", 706 | "winapi-x86_64-pc-windows-gnu", 707 | ] 708 | 709 | [[package]] 710 | name = "winapi-i686-pc-windows-gnu" 711 | version = "0.4.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 714 | 715 | [[package]] 716 | name = "winapi-util" 717 | version = "0.1.5" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 720 | dependencies = [ 721 | "winapi", 722 | ] 723 | 724 | [[package]] 725 | name = "winapi-x86_64-pc-windows-gnu" 726 | version = "0.4.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 729 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ultra" 3 | version = "0.6.1" 4 | authors = ["Kevin Yap "] 5 | description = "Cryptanalysis of the Engima." 6 | keywords = ["enigma", "cryptanalysis"] 7 | homepage = "https://github.com/iKevinY/ultra" 8 | repository = "https://github.com/iKevinY/ultra" 9 | documentation = "https://docs.rs/ultra" 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | [badges] 14 | travis-ci = { repository = "iKevinY/ultra" } 15 | 16 | [[bin]] 17 | name = "ultra" 18 | path = "src/main.rs" 19 | test = false 20 | doc = false 21 | 22 | [lib] 23 | path = "src/lib.rs" 24 | 25 | [profile.dev] 26 | opt-level = 2 27 | 28 | [profile.test] 29 | opt-level = 2 30 | 31 | [dependencies] 32 | clap = "2.25" 33 | itertools = "0.10" 34 | lazy_static = "1.0" 35 | ordered-float = "2.1.1" 36 | rand = "0.8" 37 | rayon = "1.5" 38 | 39 | [dev-dependencies] 40 | criterion = "0.3" 41 | 42 | [[bench]] 43 | name = "encrypt" 44 | harness = false 45 | 46 | [[bench]] 47 | name = "decrypt" 48 | harness = false 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kevin Yap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ultra [![Build Status][Travis Badge]][Build Status] [![crates.io][crates.io Badge]][crates.io] [![docs.rs][docs.rs Badge]][docs.rs] [![License][License Badge]](LICENSE) 2 | 3 | `ultra` is a Rust implementation of the [Enigma machine] that includes the 4 | ability to decrypt ciphertext. 5 | 6 | 7 | ## Installation 8 | 9 | `ultra` can be installed from [crates.io] using Cargo: 10 | 11 | ``` 12 | $ cargo install ultra 13 | ``` 14 | 15 | 16 | ## Usage 17 | 18 | Encrypt a message with rotors `1-4-2`, key setting `D-O-G`, and ring setting `C-A-T`: 19 | 20 | ```bash 21 | $ ultra --rotor=142 --key=DOG --ring=CAT "The quick brown fox jumps over the lazy dog." 22 | Ntz ntqlz jmwll art bbnow wzqk keq ievk lzo. 23 | ``` 24 | 25 | Encrypt a message using random Enigma settings: 26 | 27 | ```bash 28 | $ ultra --randomize "The quick brown fox jumps over the lazy dog." 29 | Kxj mcwzf oqgmz pwr vnfqq iwhv wcr qqgt lgd. 30 | > Rotors: 5-2-3 / Key: A-A-G / Ring: N-W-Q / Plugs: CG EZ HW IJ MP TY 31 | ``` 32 | 33 | Attempt to decrypt a piece of ciphertext: 34 | 35 | ```bash 36 | $ ultra --decrypt "$(cat ciphertext.txt)" 37 | ... 38 | ``` 39 | 40 | Decryption relies on a combination of [index of coincidence], bigram, and 41 | quadgram frequencies to infer the original Enigma machine settings, and as a 42 | result, it is quite likely that messages shorter than 500 characters will not 43 | come anywhere close to being decrypted correctly. 44 | 45 | 46 | ## References 47 | 48 | The original version of this project was based on [James Lyons'] articles about 49 | the Enigma machine (see [this blog post] for a brief overview). As of version 50 | 0.6.0, the decryption algorithm was updated, inspired by [this Computerphile 51 | video]. 52 | 53 | 54 | ## License 55 | 56 | `ultra` is licensed under the [MIT License](LICENSE). 57 | 58 | 59 | [Travis Badge]: https://travis-ci.org/iKevinY/ultra.svg?branch=master 60 | [Build Status]: https://travis-ci.org/iKevinY/ultra 61 | [crates.io Badge]: https://img.shields.io/crates/v/ultra.svg 62 | [crates.io]: https://crates.io/crates/ultra 63 | [docs.rs Badge]: https://docs.rs/ultra/badge.svg 64 | [docs.rs]: https://docs.rs/ultra 65 | [License Badge]: https://img.shields.io/crates/l/ultra.svg 66 | 67 | [Enigma machine]: https://en.wikipedia.org/wiki/Enigma_machine 68 | [index of coincidence]: https://en.wikipedia.org/wiki/Index_of_coincidence 69 | [this blog post]: http://kevinyap.ca/2017/04/breaking-the-enigma-code-with-rust/ 70 | [James Lyons']: http://practicalcryptography.com/ciphers/mechanical-era/enigma/ 71 | [this Computerphile video]: https://www.youtube.com/watch?v=RzWB5jL5RX0 72 | -------------------------------------------------------------------------------- /benches/decrypt.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | extern crate criterion; 4 | extern crate ultra; 5 | 6 | use ultra::decrypt; 7 | 8 | 9 | fn bench_decrypt(c: &mut Criterion) { 10 | let msg = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; 11 | c.bench_function( 12 | "decrypt", 13 | |b| b.iter(|| decrypt(msg)) 14 | ); 15 | } 16 | 17 | criterion_group!{ 18 | name = decrypt_benches; 19 | config = Criterion::default().sample_size(10); 20 | targets = bench_decrypt 21 | } 22 | 23 | criterion_main!(decrypt_benches); 24 | -------------------------------------------------------------------------------- /benches/encrypt.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | extern crate criterion; 4 | extern crate ultra; 5 | 6 | use ultra::Enigma; 7 | 8 | 9 | fn bench_encrypt_msg(c: &mut Criterion) { 10 | let mut enigma = Enigma::new("123", "AAA", "AAA", 'B', "AB CD"); 11 | c.bench_function( 12 | "encrypt_msg", 13 | |b| b.iter(|| enigma.encrypt("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG")) 14 | ); 15 | } 16 | 17 | criterion_group!(encrypt_benches, bench_encrypt_msg); 18 | criterion_main!(encrypt_benches); 19 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | /// Enigma rotor and reflector information from Wikipedia: 2 | /// https://en.wikipedia.org/wiki/Enigma_rotor_details 3 | 4 | pub const ROTORS: &[&str; 8] = &[ 5 | "EKMFLGDQVZNTOWYHXUSPAIBRCJ", 6 | "AJDKSIRUXBLHWTMCQGZNPYFVOE", 7 | "BDFHJLCPRTXVZNYEIWGAKMUSQO", 8 | "ESOVPZJAYQUIRHXLNFTGKDCMWB", 9 | "VZBRGITYUPSDNHLXAWMJQOFECK", 10 | "JPGVOUMFYQBENHZRDKASXLICTW", 11 | "NZJHGRCXMYSWBOUFAIVLPEKQDT", 12 | "FKQHTLXOCBJSPDZRAMEWNIUYGV", 13 | ]; 14 | 15 | pub const NOTCHES: &[&str; 8] = &[ 16 | "Q", "E", "V", "J", "Z", "ZM", "ZM", "ZM" 17 | ]; 18 | 19 | pub const REFLECTORS: &[&str; 3] = &[ 20 | "EJMZALYXVBWFCRQUONTSPIKHGD", 21 | "YRUHQSLDPXNGOKMIEBFZCWVJAT", 22 | "FVPJIAOYEDRZXWGCTKUQSBNMHL", 23 | ]; 24 | 25 | pub const MAX_PLUGS: usize = 10; 26 | -------------------------------------------------------------------------------- /src/data/bigrams.txt: -------------------------------------------------------------------------------- 1 | TH 116997844 2 | HE 100689263 3 | IN 87674002 4 | ER 77134382 5 | AN 69775179 6 | RE 60923600 7 | ES 57070453 8 | ON 56915252 9 | ST 54018399 10 | NT 50701084 11 | EN 48991276 12 | AT 48274564 13 | ED 46647960 14 | ND 46194306 15 | TO 46115188 16 | OR 45725191 17 | EA 43329810 18 | TI 42888666 19 | AR 42353262 20 | TE 42295813 21 | NG 38567365 22 | AL 38211584 23 | IT 37938534 24 | AS 37773878 25 | IS 37349981 26 | HA 35971841 27 | ET 32872552 28 | SE 31532272 29 | OU 31112284 30 | OF 30540904 31 | LE 30383262 32 | SA 30080131 33 | VE 29320973 34 | RO 29230770 35 | RA 28645577 36 | RI 27634643 37 | HI 27495342 38 | NE 27331675 39 | ME 27237733 40 | DE 27029835 41 | CO 26737101 42 | TA 26147593 43 | EC 25775798 44 | SI 25758841 45 | LL 24636875 46 | SO 23903631 47 | NA 23547524 48 | LI 23291169 49 | LA 23178317 50 | EL 23092248 51 | MA 21828378 52 | DI 21673998 53 | IC 21468412 54 | RT 21456059 55 | NS 21306421 56 | RS 21237259 57 | IO 21210160 58 | OM 21066156 59 | CH 20132750 60 | OT 20088048 61 | CA 19930754 62 | CE 19803619 63 | HO 19729026 64 | BE 19468489 65 | TT 19367472 66 | FO 18923772 67 | TS 18922522 68 | SS 18915696 69 | NO 18894111 70 | EE 18497942 71 | EM 18145294 72 | AC 17904683 73 | IL 17877600 74 | DA 17584055 75 | NI 17452104 76 | UR 17341717 77 | WA 16838794 78 | SH 16773127 79 | EI 16026915 80 | AM 15975981 81 | TR 15821226 82 | DT 15759673 83 | US 15699353 84 | LO 15596310 85 | PE 15573318 86 | UN 15237699 87 | NC 15214623 88 | WI 15213018 89 | UT 15137169 90 | AD 14877234 91 | EW 14776406 92 | OW 14610429 93 | GE 14425023 94 | EP 14024377 95 | AI 13974919 96 | LY 13742031 97 | OL 13726491 98 | FT 13696078 99 | OS 13596265 100 | EO 13524186 101 | EF 13252227 102 | PR 13191182 103 | WE 13185116 104 | DO 13120322 105 | MO 12950768 106 | ID 12896787 107 | IE 12505546 108 | MI 12168944 109 | PA 12068709 110 | FI 11993833 111 | PO 11917535 112 | CT 11888752 113 | WH 11852909 114 | IR 11681353 115 | AY 11523416 116 | GA 11239788 117 | SC 10800636 118 | KE 10650670 119 | EV 10574011 120 | SP 10570626 121 | IM 10544422 122 | OP 10459455 123 | DS 10429887 124 | LD 10245579 125 | UL 10173468 126 | OO 10168856 127 | SU 10031005 128 | IA 10002012 129 | GH 9880399 130 | PL 9812226 131 | EB 9738798 132 | IG 9530574 133 | VI 9380037 134 | IV 9129232 135 | WO 9106647 136 | YO 9088497 137 | RD 9025637 138 | TW 8910254 139 | BA 8867461 140 | AG 8809266 141 | RY 8788539 142 | AB 8775582 143 | LS 8675452 144 | SW 8673234 145 | AP 8553911 146 | FE 8529289 147 | TU 8477495 148 | CI 8446084 149 | FA 8357929 150 | HT 8351551 151 | FR 8339376 152 | AV 8288885 153 | EG 8286463 154 | GO 8188708 155 | BO 8172395 156 | BU 8113271 157 | TY 8008918 158 | MP 7835172 159 | OC 7646952 160 | OD 7610214 161 | EH 7559141 162 | YS 7539621 163 | EY 7528342 164 | RM 7377989 165 | OV 7350014 166 | GT 7347990 167 | YA 7239548 168 | CK 7205091 169 | GI 7103140 170 | RN 7064635 171 | GR 6989963 172 | RC 6974063 173 | BL 6941044 174 | LT 6817273 175 | YT 6714151 176 | OA 6554221 177 | YE 6499305 178 | OB 6212512 179 | DB 6106719 180 | FF 6085519 181 | SF 6073995 182 | RR 5896212 183 | DU 5861311 184 | KI 5814357 185 | UC 5742385 186 | IF 5740414 187 | AF 5702567 188 | DR 5701879 189 | CL 5683204 190 | EX 5649363 191 | SM 5580755 192 | PI 5559210 193 | SB 5553684 194 | CR 5514347 195 | TL 5403137 196 | OI 5336616 197 | RU 5330557 198 | UP 5306948 199 | BY 5232074 200 | TC 5196817 201 | NN 5180899 202 | AK 5137311 203 | SL 4965012 204 | NF 4950333 205 | UE 4927837 206 | DW 4906814 207 | AU 4884168 208 | PP 4873393 209 | UG 4832325 210 | RL 4803246 211 | RG 4645938 212 | BR 4621080 213 | CU 4604045 214 | UA 4589997 215 | DH 4585765 216 | RK 4491400 217 | YI 4461214 218 | LU 4402940 219 | UM 4389720 220 | BI 4356462 221 | NY 4343290 222 | NW 4215967 223 | QU 4169424 224 | OG 4163126 225 | SN 4157990 226 | MB 4121764 227 | VA 4111375 228 | DF 4033878 229 | DD 4001275 230 | MS 3922855 231 | GS 3920675 232 | AW 3918960 233 | NH 3915410 234 | PU 3858148 235 | HR 3843001 236 | SD 3842250 237 | TB 3815459 238 | PT 3812475 239 | NM 3796928 240 | DC 3782481 241 | GU 3768430 242 | TM 3759861 243 | MU 3755834 244 | NU 3732602 245 | MM 3730508 246 | NL 3692985 247 | EU 3674130 248 | WN 3649615 249 | NB 3602692 250 | RP 3588188 251 | DM 3544905 252 | SR 3513808 253 | UD 3499535 254 | UI 3481482 255 | RF 3436232 256 | OK 3397570 257 | YW 3379064 258 | TF 3368452 259 | IP 3348621 260 | RW 3348005 261 | RB 3346212 262 | OH 3254659 263 | KS 3227333 264 | DP 3145043 265 | FU 3138900 266 | YC 3128053 267 | TP 3070427 268 | MT 3055946 269 | DL 3050945 270 | NK 3043200 271 | CC 3026492 272 | UB 2990868 273 | RH 2968706 274 | NP 2968126 275 | JU 2924815 276 | FL 2890839 277 | DN 2840522 278 | KA 2833038 279 | PH 2825344 280 | HU 2771830 281 | JO 2721345 282 | LF 2702522 283 | YB 2696786 284 | RV 2692445 285 | OE 2616308 286 | IB 2598444 287 | IK 2585124 288 | YP 2581863 289 | GL 2576787 290 | LP 2543957 291 | YM 2516273 292 | LB 2463693 293 | HS 2462026 294 | DG 2442139 295 | GN 2426429 296 | EK 2411639 297 | NR 2393580 298 | PS 2377036 299 | TD 2346516 300 | LC 2328063 301 | SK 2321888 302 | YF 2305244 303 | YH 2291273 304 | VO 2253292 305 | AH 2225270 306 | DY 2218040 307 | LM 2216514 308 | SY 2214270 309 | NV 2194534 310 | YD 2122337 311 | FS 2047416 312 | SG 2043770 313 | YR 2021939 314 | YL 2013939 315 | WS 1988727 316 | MY 1949129 317 | OY 1932892 318 | KN 1903836 319 | IZ 1865802 320 | XP 1840696 321 | LW 1836811 322 | TN 1782119 323 | KO 1758001 324 | AA 1721143 325 | JA 1712763 326 | ZE 1709871 327 | FC 1570791 328 | GW 1567991 329 | TG 1530045 330 | XT 1509969 331 | FH 1507604 332 | LR 1505092 333 | JE 1487348 334 | YN 1485655 335 | GG 1468286 336 | GF 1465290 337 | EQ 1461436 338 | HY 1446451 339 | KT 1443985 340 | HC 1441057 341 | BS 1409672 342 | HW 1403223 343 | HN 1383958 344 | CS 1381608 345 | HM 1353001 346 | NJ 1342735 347 | HH 1329998 348 | WT 1301293 349 | GC 1299541 350 | LH 1274048 351 | EJ 1256993 352 | FM 1251312 353 | DV 1238565 354 | LV 1238287 355 | WR 1226755 356 | GP 1215204 357 | FP 1199845 358 | GB 1184377 359 | GM 1178511 360 | HL 1169468 361 | LK 1164186 362 | CY 1145316 363 | MC 1101727 364 | YG 1049082 365 | XI 1024736 366 | HB 1014004 367 | FW 1005903 368 | GY 979804 369 | HP 978649 370 | MW 937621 371 | PM 931225 372 | ZA 929119 373 | LG 926472 374 | IW 922059 375 | XA 904148 376 | FB 888155 377 | SV 882083 378 | GD 879792 379 | IX 879360 380 | AJ 870262 381 | KL 846309 382 | HF 834284 383 | HD 828755 384 | AE 815963 385 | SQ 800346 386 | DJ 799366 387 | FY 789961 388 | AZ 768359 389 | LN 752316 390 | AO 749566 391 | FD 748027 392 | KW 719633 393 | MF 715087 394 | MH 710864 395 | SJ 704442 396 | UF 701892 397 | TV 698150 398 | XC 697995 399 | YU 695512 400 | BB 689158 401 | WW 674610 402 | OJ 661082 403 | AX 660826 404 | MR 660619 405 | WL 657782 406 | XE 653947 407 | KH 650095 408 | OX 650078 409 | UO 649906 410 | ZI 644035 411 | FG 637758 412 | IH 610683 413 | TK 610333 414 | II 607124 415 | IU 576683 416 | TJ 559473 417 | MN 558397 418 | WY 553647 419 | KY 553296 420 | KF 537342 421 | FN 534362 422 | UY 531960 423 | PW 530411 424 | DK 525744 425 | RJ 518157 426 | UK 514873 427 | KR 507020 428 | KU 506618 429 | WM 505687 430 | KM 485617 431 | MD 481126 432 | ML 478528 433 | EZ 465466 434 | KB 457860 435 | WC 448394 436 | WD 432646 437 | HG 429607 438 | BT 428276 439 | ZO 424016 440 | KC 420017 441 | PF 418168 442 | YV 411487 443 | PC 400308 444 | PY 396147 445 | WB 394820 446 | YK 391953 447 | CP 382923 448 | YJ 378679 449 | KP 375653 450 | PB 369336 451 | CD 358435 452 | JI 357577 453 | UW 352732 454 | UH 339341 455 | WF 336213 456 | YY 332973 457 | WP 321746 458 | BC 320380 459 | AQ 315068 460 | CB 298053 461 | IQ 291635 462 | CM 285942 463 | MG 285133 464 | DQ 283314 465 | BJ 282608 466 | TZ 280007 467 | KD 277982 468 | PD 273162 469 | FJ 269865 470 | CF 267630 471 | NZ 266461 472 | CW 257253 473 | FV 244685 474 | VY 233082 475 | FK 228905 476 | OZ 228556 477 | ZZ 221275 478 | IJ 219128 479 | LJ 218362 480 | NQ 217422 481 | UV 212051 482 | XO 211173 483 | PG 211133 484 | HK 210385 485 | KG 209266 486 | VS 204093 487 | HV 197539 488 | BM 191807 489 | HJ 189906 490 | CN 188046 491 | GV 186777 492 | CG 181590 493 | WU 180884 494 | GJ 176947 495 | XH 166599 496 | GK 163830 497 | TQ 159111 498 | CQ 157546 499 | RQ 156933 500 | BH 154489 501 | XS 154347 502 | UZ 153736 503 | WK 148964 504 | XU 147533 505 | UX 144814 506 | BD 141752 507 | BW 140189 508 | WG 139890 509 | MV 136314 510 | MJ 134263 511 | PN 131645 512 | XM 127492 513 | OQ 122677 514 | BV 120081 515 | XW 119322 516 | KK 118811 517 | BP 115161 518 | ZU 113538 519 | RZ 113432 520 | XF 113031 521 | MK 111041 522 | ZH 107639 523 | BN 106125 524 | ZY 105871 525 | HQ 101241 526 | WJ 99435 527 | IY 98361 528 | DZ 98038 529 | VR 96416 530 | ZS 94993 531 | XY 94329 532 | CV 94224 533 | XB 94041 534 | XR 90046 535 | UJ 88168 536 | YQ 87953 537 | VD 85611 538 | PK 83017 539 | VU 82830 540 | JR 80471 541 | ZL 80039 542 | SZ 79840 543 | YZ 78281 544 | LQ 77148 545 | KJ 76816 546 | BF 75352 547 | NX 74844 548 | QA 73527 549 | QI 73387 550 | KV 73184 551 | ZW 68865 552 | WV 63930 553 | UU 63043 554 | VT 62912 555 | VP 62577 556 | XD 60101 557 | GQ 59750 558 | XL 59585 559 | VC 59024 560 | CZ 57914 561 | LZ 57314 562 | ZT 56955 563 | WZ 52836 564 | SX 50975 565 | ZB 50652 566 | VL 49032 567 | PV 48105 568 | FQ 47504 569 | PJ 47043 570 | ZM 46034 571 | VW 45608 572 | CJ 41526 573 | ZC 41037 574 | BG 40516 575 | JS 39326 576 | XG 39289 577 | RX 38654 578 | HZ 37066 579 | XX 35052 580 | VM 35024 581 | XN 34734 582 | QW 34669 583 | JP 34520 584 | VN 33082 585 | ZD 32906 586 | ZR 32685 587 | FZ 31186 588 | XV 31117 589 | ZP 30389 590 | VH 30203 591 | VB 29192 592 | ZF 28658 593 | GZ 28514 594 | TX 28156 595 | VF 28090 596 | DX 27413 597 | QB 27307 598 | BK 26993 599 | ZG 26369 600 | VG 25585 601 | JC 24770 602 | ZK 24262 603 | ZN 24241 604 | UQ 23386 605 | JM 22338 606 | VV 22329 607 | JD 21903 608 | MQ 21358 609 | JH 20960 610 | QS 20847 611 | JT 20408 612 | JB 19380 613 | FX 19313 614 | PQ 18607 615 | MZ 18271 616 | YX 16945 617 | QT 16914 618 | WQ 16245 619 | JJ 16085 620 | JW 16083 621 | LX 15467 622 | GX 14778 623 | JN 14452 624 | ZV 14339 625 | MX 14250 626 | JK 13967 627 | KQ 13905 628 | XK 13651 629 | JF 12640 630 | QM 12315 631 | QH 12273 632 | JL 12149 633 | JG 12023 634 | VK 11469 635 | VJ 11432 636 | KZ 11192 637 | QC 10667 638 | XJ 10629 639 | PZ 9697 640 | QL 9603 641 | QO 9394 642 | JV 8925 643 | QF 8778 644 | QD 8678 645 | BZ 8132 646 | HX 7526 647 | ZJ 7167 648 | PX 6814 649 | QP 6062 650 | QE 6020 651 | QR 5975 652 | ZQ 5773 653 | JY 5723 654 | BQ 5513 655 | XQ 5416 656 | CX 5300 657 | KX 5083 658 | WX 4678 659 | QY 4557 660 | QV 4212 661 | QN 3808 662 | VX 3192 663 | BX 3021 664 | JZ 2859 665 | VZ 2633 666 | QG 2567 667 | QQ 2499 668 | ZX 2463 669 | XZ 2082 670 | QK 2023 671 | VQ 1488 672 | QJ 1342 673 | QX 765 674 | JX 747 675 | JQ 722 676 | QZ 280 677 | -------------------------------------------------------------------------------- /src/data/monograms.txt: -------------------------------------------------------------------------------- 1 | E 529117365 2 | T 390965105 3 | A 374061888 4 | O 326627740 5 | I 320410057 6 | N 313720540 7 | S 294300210 8 | R 277000841 9 | H 216768975 10 | L 183996130 11 | D 169330528 12 | C 138416451 13 | U 117295780 14 | M 110504544 15 | F 95422055 16 | G 91258980 17 | P 90376747 18 | W 79843664 19 | Y 75294515 20 | B 70195826 21 | V 46337161 22 | K 35373464 23 | J 9613410 24 | X 8369915 25 | Z 4975847 26 | Q 4550166 27 | -------------------------------------------------------------------------------- /src/decrypt.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | 3 | use itertools::Itertools; 4 | use ordered_float::OrderedFloat; 5 | use rayon::prelude::*; 6 | 7 | use super::CharIndex; 8 | use super::enigma::Enigma; 9 | 10 | use constants::MAX_PLUGS; 11 | use fitness::{IoC, Bigram, Quadgram, FitnessFn}; 12 | 13 | lazy_static! { 14 | // The vector of key/ring settings ["AAA", "AAB", ..., "ZZY", "ZZZ"]. 15 | static ref ALPHAS: Vec = { 16 | iproduct!('A'..='Z', 'A'..='Z', 'A'..='Z') 17 | .map(|(a, b, c)| [a, b, c].iter().collect()) 18 | .collect() 19 | }; 20 | 21 | // The vector of permutations ["123", "124", "125", ..., "542", "543"]. 22 | static ref ROTORS: Vec = { 23 | ('1'..='5').permutations(3) 24 | .map(|p| p.iter().collect()) 25 | .collect() 26 | }; 27 | } 28 | 29 | 30 | /// Decrypts the given string by iterating through multiple possible Enigma 31 | /// configurations, returning `(plaintext, Enigma)` corresponding to the most 32 | /// probable decryption. 33 | /// 34 | /// The decryption algorithm works in three steps: 35 | /// 36 | /// 1. Guesses the rotors and first key setting. 37 | /// 2. Guesses the remaining key settings and ring settings. 38 | /// 3. Incrementally adds the best plug until no improvement is made. 39 | /// 40 | /// Assumes `msg` contains only uppercase ASCII characters. 41 | pub fn decrypt(msg: &str) -> (String, Enigma) { 42 | let mut enigma; 43 | 44 | enigma = guess_rotor_and_first_key::(msg); 45 | enigma = guess_key_and_ring::(msg, enigma); 46 | enigma = guess_plugboard::(msg, enigma); 47 | 48 | (enigma.encrypt(msg), enigma) 49 | } 50 | 51 | 52 | /// Given a piece of ciphertext and a fitness function, tries all valid rotor 53 | /// and key combinations, and returns an `Enigma` with rotor settings and 54 | /// first key setting that best decrypt the ciphertext. Note that the other 55 | /// key settings and ring settings will be meaningless at this point. 56 | /// 57 | /// This method checks 60 * 26^3 == 1,054,560 settings in parallel. 58 | fn guess_rotor_and_first_key(msg: &str) -> Enigma { 59 | let (rotor, key) = iproduct!(ROTORS.iter(), ALPHAS.iter()) 60 | .collect::>() 61 | .into_par_iter() 62 | .max_by_key(|&(rotor, key)| { 63 | let mut enigma = Enigma::new(rotor, key, "AAA", 'B', ""); 64 | OrderedFloat(F::score(&enigma.encrypt(msg))) 65 | }).unwrap(); 66 | 67 | Enigma::new(rotor, key, "AAA", 'B', "") 68 | } 69 | 70 | /// Given a piece of ciphertext, fitness function, and an `Enigma` instance 71 | /// with the correct rotors and first key setting, tries all key and ring 72 | /// settings for the mid and fast rotors, and returns an `Enigma` with the 73 | /// best rotors, key, and ring settings assigned. 74 | /// 75 | /// This method checks 26^4 == 456,976 settings in parallel. 76 | fn guess_key_and_ring(msg: &str, enigma: Enigma) -> Enigma { 77 | let rotor = enigma.rotor_list(); 78 | let first_key = enigma.key_settings().chars().next().unwrap(); 79 | 80 | // Compute the key offset based on the first key setting, so that we only 81 | // iterate over the next 676 key settings (this implicitly fixes the slow 82 | // rotor's key setting at 'A', which is intentional because this setting 83 | // does not affect the overall decryption). 84 | let key_offset = first_key.index() * 676; 85 | 86 | let keys = &ALPHAS[key_offset..(key_offset + 676)]; 87 | let rings = &ALPHAS[0..676]; 88 | 89 | let (key, ring) = iproduct!(keys, rings) 90 | .collect::>() 91 | .into_par_iter() 92 | .max_by_key(|&(key, ring)| { 93 | let mut enigma = Enigma::new(&rotor, key, ring, 'B', ""); 94 | OrderedFloat(F::score(&enigma.encrypt(msg))) 95 | }).unwrap(); 96 | 97 | Enigma::new(&rotor, key, ring, 'B', "") 98 | } 99 | 100 | /// Given a piece of ciphertext, fitness function, and an `Enigma` instance 101 | /// with the correct rotors, key, and ring settings, attempts to add plugs 102 | /// to the plugboard until adding one does not increase the overall fitness 103 | /// of the decryption (or the number of plugs equals `MAX_PLUGS`). Returns the 104 | /// resulting `Enigma`. 105 | /// 106 | /// At most, this is MAX_PLUGS * 26^2 == 6,760 tests. 107 | fn guess_plugboard(msg: &str, enigma: Enigma) -> Enigma { 108 | let rotor = enigma.rotor_list(); 109 | let key = enigma.key_settings(); 110 | let ring = enigma.ring_settings(); 111 | 112 | let mut curr_plugboard = Vec::new(); 113 | let mut plug_pool: Vec = ('A'..='Z').collect(); 114 | let mut best_score = F::score(&msg); 115 | 116 | for _ in 0..MAX_PLUGS { 117 | let plugs: Vec = plug_pool 118 | .iter() 119 | .combinations(2) 120 | .map(|p| p.into_iter().collect()) 121 | .collect(); 122 | 123 | let (score, plug) = plugs.par_iter() 124 | .map(|plug| { 125 | let plugboard = curr_plugboard 126 | .iter() 127 | .chain(iter::once(plug)) 128 | .join(" "); 129 | 130 | let mut enigma = Enigma::new(&rotor, &key, &ring, 'B', &plugboard); 131 | (OrderedFloat(F::score(&enigma.encrypt(&msg))), plug) 132 | }).max().unwrap(); 133 | 134 | if *score > best_score { 135 | best_score = *score; 136 | curr_plugboard.push(plug.to_string()); 137 | plug_pool.retain(|&c| !plug.chars().any(|p| p == c)); 138 | } else { 139 | break; 140 | } 141 | } 142 | 143 | Enigma::new(&rotor, &key, &ring, 'B', &curr_plugboard.iter().join(" ")) 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | 150 | #[test] 151 | fn static_vars() { 152 | assert_eq!(ALPHAS.len(), 26 * 26 * 26); 153 | assert_eq!(ROTORS.len(), 120 / 2); // n! / (n - k)! 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /src/enigma.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use itertools::Itertools; 4 | use rand::{Rng, SeedableRng}; 5 | use rand::rngs::StdRng; 6 | use rand::seq::SliceRandom; 7 | 8 | use constants::MAX_PLUGS; 9 | use plugboard::Plugboard; 10 | use reflector::Reflector; 11 | use rotor::Rotor; 12 | 13 | /// Represents an Enigma machine with rotor, key, and ring settings. 14 | #[derive(Clone, Debug)] 15 | pub struct Enigma { 16 | slow: Rotor, 17 | mid: Rotor, 18 | fast: Rotor, 19 | reflector: Reflector, 20 | plugboard: Plugboard, 21 | } 22 | 23 | impl Enigma { 24 | /// Creates a new `Enigma`, where `rotors` is a string of three digits 25 | /// ranging from 1-8 (corresponding to rotors I through VIII of the real 26 | /// Enigma machine), `keys` and `rings` are three character strings 27 | /// containing the key and ring settings, `reflector` is one of `'A'`, 28 | /// `'B'`, or `'C'`, and `plugboard` is a string of whitespace-delimited 29 | /// pairs of characters. 30 | /// 31 | /// # Examples 32 | /// 33 | /// `Enigma` with rotors I, II, and III, key setting `ABC`, ring setting 34 | /// `DEF`, reflector B, and a plugboard connection between 'P' and 'Y'. 35 | /// 36 | /// ``` 37 | /// use ultra::Enigma; 38 | /// 39 | /// let mut enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 40 | /// ``` 41 | pub fn new(rotors: &str, keys: &str, rings: &str, reflector: char, plugboard: &str) -> Enigma { 42 | let rotors: Vec = rotors.chars() 43 | .filter_map(|c| c.to_digit(10)) 44 | .map(|n| n as usize) 45 | .collect(); 46 | 47 | if rotors.len() != 3 { 48 | panic!("Exactly 3 rotors must be given."); 49 | } 50 | 51 | let keys: Vec = keys.chars().collect(); 52 | let rings: Vec = rings.chars().collect(); 53 | 54 | Enigma { 55 | slow: Rotor::from_enigma(rotors[0], keys[0], rings[0]), 56 | mid: Rotor::from_enigma(rotors[1], keys[1], rings[1]), 57 | fast: Rotor::from_enigma(rotors[2], keys[2], rings[2]), 58 | reflector: Reflector::from_enigma(reflector), 59 | plugboard: Plugboard::new(plugboard), 60 | } 61 | } 62 | 63 | /// Creates a new random `Enigma` with random settings based on 64 | /// thread-local RNG. 65 | /// 66 | /// # Examples 67 | /// 68 | /// ``` 69 | /// use ultra::Enigma; 70 | /// 71 | /// let mut enigma_1 = Enigma::random(); 72 | /// let mut enigma_2 = Enigma::random(); 73 | /// assert!(enigma_1.encrypt("ENIGMA") != enigma_2.encrypt("ENIGMA")); 74 | /// ``` 75 | pub fn random() -> Enigma { 76 | Enigma::random_from_rng(&mut rand::thread_rng()) 77 | } 78 | 79 | /// Creates a new random `Enigma` from a given u64 seed. 80 | /// 81 | /// # Examples 82 | /// 83 | /// ``` 84 | /// use ultra::Enigma; 85 | /// 86 | /// let mut enigma_1 = Enigma::random_from_u64_seed(42); 87 | /// let mut enigma_2 = Enigma::random_from_u64_seed(42); 88 | /// assert_eq!(enigma_1.encrypt("ENIGMA"), enigma_2.encrypt("ENIGMA")); 89 | /// ``` 90 | pub fn random_from_u64_seed(seed: u64) -> Enigma { 91 | Enigma::random_from_rng(&mut StdRng::seed_from_u64(seed)) 92 | } 93 | 94 | fn random_from_rng(rng: &mut R) -> Enigma { 95 | let rotors: String = { 96 | let mut rotor_pool: Vec = "12345".chars().collect(); 97 | rotor_pool.shuffle(rng); 98 | rotor_pool[..3].iter().collect() 99 | }; 100 | 101 | // Randomize key and ring settings for the rotors. 102 | let mut alpha: Vec = ('A'..='Z').collect(); 103 | 104 | alpha.shuffle(rng); 105 | let key: String = alpha[..3].iter().collect(); 106 | 107 | alpha.shuffle(rng); 108 | let ring: String = alpha[..3].iter().collect(); 109 | 110 | // Pick random plugs to fill plugboard with. 111 | alpha.shuffle(rng); 112 | let plugboard = alpha 113 | .chunks(2) 114 | .take(rng.gen_range(0..=MAX_PLUGS)) 115 | .map(|chrs| chrs.iter().collect::()) 116 | .join(" "); 117 | 118 | Enigma::new(&rotors, &key, &ring, 'B', &plugboard) 119 | } 120 | 121 | 122 | /// Encrypts an entire message, advancing the rotors of the machine 123 | /// after each alphabetic character is encrypted. 124 | /// 125 | /// # Examples 126 | /// 127 | /// ``` 128 | /// use ultra::Enigma; 129 | /// 130 | /// let mut enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 131 | /// assert_eq!(enigma.encrypt("ENIGMA"), "HKAJWW"); 132 | /// ``` 133 | pub fn encrypt(&mut self, msg: &str) -> String { 134 | msg.chars().map(|c| self.encrypt_char(c)).collect() 135 | } 136 | 137 | /// Advances the rotors then returns the substitution of 138 | /// a character, if the input character was alphabetic. 139 | fn encrypt_char(&mut self, c: char) -> char { 140 | if !c.is_ascii() || !c.is_alphabetic() { 141 | return c; 142 | } 143 | 144 | self.advance(); 145 | self.substitute(c.to_ascii_uppercase()) 146 | } 147 | 148 | 149 | /// Returns the substitution of a character, which is determined by 150 | /// passing the character in sequence through the plugboard, the rotors 151 | /// from `fast` to `slow`, through the reflector, inverted through the 152 | /// rotors from `slow` to `fast`, and finally through the plugboard. 153 | fn substitute(&self, c: char) -> char { 154 | let mut c = self.plugboard.map(c); 155 | c = self.slow.substitute(self.mid.substitute(self.fast.substitute(c))); 156 | c = self.reflector.reflect(c); 157 | c = self.fast.invert(self.mid.invert(self.slow.invert(c))); 158 | self.plugboard.map(c) 159 | } 160 | 161 | /// Advances the `fast` rotor, and also advances the 162 | /// `mid` and `slow` rotors if appropriate. 163 | fn advance(&mut self) { 164 | // Check for double-rotation situation 165 | if self.mid.at_notch() { 166 | self.mid.advance(); 167 | self.slow.advance(); 168 | } else if self.fast.at_notch() { 169 | self.mid.advance(); 170 | } 171 | 172 | // Finally, advance the fast rotor 173 | self.fast.advance(); 174 | } 175 | 176 | /// Resets the `Enigma` to its initial state. 177 | /// 178 | /// # Examples 179 | /// 180 | /// ``` 181 | /// use ultra::Enigma; 182 | /// 183 | /// let msg = "THIS IS A TEST"; 184 | /// 185 | /// let mut enigma = Enigma::random(); 186 | /// let ciphertext_1 = enigma.encrypt(msg); 187 | /// let ciphertext_2 = enigma.encrypt(msg); 188 | /// 189 | /// enigma.reset(); 190 | /// 191 | /// assert_eq!(ciphertext_1, enigma.encrypt(msg)); 192 | /// assert!(ciphertext_1 != ciphertext_2); 193 | /// ``` 194 | pub fn reset(&mut self) { 195 | self.slow.reset(); 196 | self.mid.reset(); 197 | self.fast.reset(); 198 | } 199 | 200 | 201 | /// Returns a string representing the `Enigma`'s rotor list. 202 | /// 203 | /// # Examples 204 | /// 205 | /// ``` 206 | /// use ultra::Enigma; 207 | /// 208 | /// let enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 209 | /// assert_eq!(enigma.rotor_list(), "123"); 210 | /// ``` 211 | pub fn rotor_list(&self) -> String { 212 | self.rotors().map(|r| r.to_string()).collect() 213 | } 214 | 215 | /// Returns a string representing the `Enigma`'s key settings. 216 | /// 217 | /// # Examples 218 | /// 219 | /// ``` 220 | /// use ultra::Enigma; 221 | /// 222 | /// let enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 223 | /// assert_eq!(enigma.key_settings(), "ABC"); 224 | /// ``` 225 | pub fn key_settings(&self) -> String { 226 | self.rotors() 227 | .map(|r| ((r.key_setting as u8) + b'A') as char) 228 | .collect() 229 | } 230 | 231 | /// Returns a string representing the `Enigma`'s ring settings. 232 | /// 233 | /// # Examples 234 | /// 235 | /// ``` 236 | /// use ultra::Enigma; 237 | /// 238 | /// let enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 239 | /// assert_eq!(enigma.ring_settings(), "DEF"); 240 | /// ``` 241 | pub fn ring_settings(&self) -> String { 242 | self.rotors() 243 | .map(|r| ((r.ring_setting as u8) + b'A') as char) 244 | .collect() 245 | } 246 | 247 | /// Returns a string representing the `Enigma`'s plugboard. 248 | /// 249 | /// # Examples 250 | /// 251 | /// ``` 252 | /// use ultra::Enigma; 253 | /// 254 | /// let enigma = Enigma::new("123", "ABC", "DEF", 'B', "PY"); 255 | /// assert_eq!(enigma.plugboard(), "PY"); 256 | /// ``` 257 | pub fn plugboard(&self) -> String { 258 | self.plugboard.to_string() 259 | } 260 | 261 | /// Returns an iterator over the slow, middle, and fast rotors. 262 | fn rotors(&self) -> impl Iterator { 263 | vec![self.slow.clone(), self.mid.clone(), self.fast.clone()].into_iter() 264 | } 265 | } 266 | 267 | impl fmt::Display for Enigma { 268 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 269 | write!(f, "Rotors: {} / Key: {} / Ring: {} / Plugs: {}", 270 | self.rotor_list().chars().join("-"), 271 | self.key_settings().chars().join("-"), 272 | self.ring_settings().chars().join("-"), 273 | self.plugboard) 274 | } 275 | } 276 | 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use super::Enigma; 281 | 282 | #[test] 283 | fn symmetrical_behaviour() { 284 | let msg = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG".repeat(10); 285 | 286 | for _ in 0..10 { 287 | let mut enigma = Enigma::random(); 288 | let ciphertext = enigma.encrypt(&msg); 289 | 290 | enigma.reset(); 291 | let plaintext = enigma.encrypt(&ciphertext); 292 | 293 | assert_eq!(plaintext, msg); 294 | } 295 | } 296 | 297 | #[test] 298 | fn identical_from_same_seed() { 299 | let mut enigma_1 = Enigma::random_from_u64_seed(42); 300 | let mut enigma_2 = Enigma::random_from_u64_seed(42); 301 | assert_eq!(enigma_1.encrypt("ENIGMA"), enigma_2.encrypt("ENIGMA")); 302 | } 303 | 304 | #[test] 305 | fn case_insensitive_encryption() { 306 | let mut enigma = Enigma::new("123", "AAA", "AAA", 'B', ""); 307 | let ciphertext1 = enigma.encrypt("Test Message"); 308 | 309 | enigma.reset(); 310 | let ciphertext2 = enigma.encrypt("TEST MESSAGE"); 311 | 312 | assert_eq!(ciphertext1, ciphertext2); 313 | } 314 | 315 | #[test] 316 | fn key_settings() { 317 | let mut enigma = Enigma::new("123", "CAT", "AAA", 'B', ""); 318 | assert_eq!(enigma.encrypt("AAAAA"), "XLEPK"); 319 | } 320 | 321 | #[test] 322 | fn ring_settings() { 323 | let mut enigma = Enigma::new("123", "AAA", "DOG", 'B', ""); 324 | assert_eq!(enigma.encrypt("AAAAA"), "XKJZE"); 325 | } 326 | 327 | #[test] 328 | fn repetition_period() { 329 | // Due to the double-rotation of the middle rotor, the Enigma 330 | // has a period of 26 * 25 * 26 rather the expected 26^3. 331 | let mut enigma = Enigma::new("123", "AAA", "AAA", 'B', ""); 332 | 333 | for _ in 0..(26 * 25 * 26) { 334 | enigma.advance(); 335 | } 336 | 337 | assert_eq!(enigma.slow.offset, 0); 338 | assert_eq!(enigma.mid.offset, 0); 339 | assert_eq!(enigma.fast.offset, 0); 340 | } 341 | 342 | #[test] 343 | #[should_panic] 344 | fn invalid_rotor_count() { 345 | Enigma::new("12", "AAA", "AAA", 'B', ""); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/fitness.rs: -------------------------------------------------------------------------------- 1 | use super::CharIndex; 2 | 3 | lazy_static! { 4 | static ref BIGRAMS: Vec = { 5 | let f = include_str!("data/bigrams.txt"); 6 | let mut bigrams = vec![0.0; 676]; // 26^2 = 676 7 | 8 | for line in f.lines() { 9 | let line: Vec<_> = line.split(' ').collect(); 10 | let bigram: &str = line[0]; 11 | let count: f64 = line[1].parse().unwrap(); 12 | 13 | let index = bigram.chars().fold(0, |acc, c| 26 * acc + c.index()); 14 | bigrams[index] = count.ln(); 15 | } 16 | 17 | bigrams 18 | }; 19 | 20 | static ref TRIGRAMS: Vec = { 21 | let f = include_str!("data/trigrams.txt"); 22 | let mut trigrams = vec![0.0; 17_576]; // 26^3 = 17,576 23 | 24 | for line in f.lines() { 25 | let line: Vec<_> = line.split(' ').collect(); 26 | let trigram: &str = line[0]; 27 | let count: f64 = line[1].parse().unwrap(); 28 | 29 | let index = trigram.chars().fold(0, |acc, c| 26 * acc + c.index()); 30 | trigrams[index] = count.ln(); 31 | } 32 | 33 | trigrams 34 | }; 35 | 36 | static ref QUADGRAMS: Vec = { 37 | let f = include_str!("data/quadgrams.txt"); 38 | let mut quadgrams = vec![0.0; 456_976]; // 26^4 = 456,976 39 | 40 | for line in f.lines() { 41 | let line: Vec<_> = line.split(' ').collect(); 42 | let quadgram: &str = line[0]; 43 | let count: f64 = line[1].parse().unwrap(); 44 | 45 | let index = quadgram.chars().fold(0, |acc, c| 26 * acc + c.index()); 46 | quadgrams[index] = count.ln(); 47 | } 48 | 49 | quadgrams 50 | }; 51 | } 52 | 53 | 54 | /// For a given `n` and `ngram` vector, returns the sum of log-probabilities 55 | /// for each n-gram substring. 56 | fn ngram_score(n: usize, ngrams: &[f64], msg: &str) -> f64 { 57 | let char_indices: Vec = msg.to_uppercase().chars() 58 | .filter(|&c| c.is_alphabetic()) 59 | .map(|c| c.index()) 60 | .collect(); 61 | 62 | if char_indices.len() < n { 63 | panic!("Message has fewer than n alphabetic characters."); 64 | } 65 | 66 | char_indices.windows(n) 67 | .map(|w| w.iter().fold(0, |acc, x| 26 * acc + x)) 68 | .map(|i| ngrams[i]) 69 | .sum() 70 | } 71 | 72 | 73 | pub trait FitnessFn { 74 | /// Returns the fitness score for a given message after stripping away 75 | /// non-alphabetic characters. 76 | fn score(msg: &str) -> f64; 77 | } 78 | 79 | /// Bigram probability fitness function for English. 80 | pub struct Bigram; 81 | impl FitnessFn for Bigram { 82 | fn score(msg: &str) -> f64 { 83 | ngram_score(2, &BIGRAMS, msg) 84 | } 85 | } 86 | 87 | /// Trigram probability fitness function for English. 88 | pub struct Trigram; 89 | impl FitnessFn for Trigram { 90 | fn score(msg: &str) -> f64 { 91 | ngram_score(3, &TRIGRAMS, msg) 92 | } 93 | } 94 | 95 | /// Quadgram probability fitness function for English. 96 | pub struct Quadgram; 97 | impl FitnessFn for Quadgram { 98 | fn score(msg: &str) -> f64 { 99 | ngram_score(4, &QUADGRAMS, msg) 100 | } 101 | } 102 | 103 | /// Returns the index of coincidence for the given message. 104 | pub struct IoC; 105 | impl FitnessFn for IoC { 106 | fn score(msg: &str) -> f64 { 107 | let char_indices: Vec = msg.chars() 108 | .filter(|&c| c.is_alphabetic()) 109 | .map(|c| c.index()) 110 | .collect(); 111 | 112 | let mut buckets = [0; 26]; 113 | 114 | for c in &char_indices { 115 | buckets[*c] += 1; 116 | } 117 | 118 | let tot: isize = buckets 119 | .iter() 120 | .map(|n| n * (n - 1)) 121 | .sum(); 122 | 123 | let n = char_indices.len(); 124 | tot as f64 / (n * (n - 1) / 26) as f64 125 | } 126 | } 127 | 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | 133 | macro_rules! assert_approx_eq { 134 | ($a:expr, $b:expr) => { 135 | let (a, b) = (&$a, &$b); 136 | assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b); 137 | } 138 | } 139 | 140 | #[test] 141 | fn quadgram_estimates() { 142 | assert_approx_eq!(QUADGRAMS[0], 8.81060879); 143 | assert_approx_eq!(Quadgram::score("THE QUICK BROWN FOX"), 149.80102862); 144 | } 145 | 146 | #[test] 147 | fn sensible_ngram_scores() { 148 | assert!(Bigram::score("AN ENGLISH PHRASE") > Bigram::score("ESARHP HSILGNE NA")); 149 | assert!(Trigram::score("AN ENGLISH PHRASE") > Trigram::score("ESARHP HSILGNE NA")); 150 | assert!(Quadgram::score("AN ENGLISH PHRASE") > Quadgram::score("ESARHP HSILGNE NA")); 151 | } 152 | 153 | #[test] 154 | #[should_panic] 155 | fn invalid_qgram_check() { 156 | Quadgram::score("ABC"); 157 | } 158 | 159 | #[test] 160 | fn sensible_ioc_scores() { 161 | assert_approx_eq!(IoC::score("THE INDEX OF COINCIDENCE PROVIDES A MEASURE OF HOW LIKELY IT IS TO DRAW TWO MATCHING LETTERS BY RANDOMLY SELECTING TWO LETTERS FROM A GIVEN TEXT"), 1.55925925); 162 | 163 | assert_approx_eq!(IoC::score(&"ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(100)), 0.99038091); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cryptanalysis of the Engima in Rust. 2 | 3 | #[macro_use] 4 | extern crate lazy_static; 5 | #[macro_use] 6 | extern crate itertools; 7 | extern crate ordered_float; 8 | extern crate rand; 9 | extern crate rayon; 10 | 11 | mod decrypt; 12 | mod constants; 13 | mod enigma; 14 | mod fitness; 15 | mod plugboard; 16 | mod reflector; 17 | mod rotor; 18 | 19 | pub use decrypt::decrypt; 20 | pub use enigma::Enigma; 21 | 22 | 23 | trait CharIndex { 24 | fn index(&self) -> usize; 25 | } 26 | 27 | impl CharIndex for char { 28 | fn index(&self) -> usize { 29 | debug_assert!(self.is_ascii_uppercase()); 30 | *self as usize - 65 31 | } 32 | } 33 | 34 | 35 | trait ToChar { 36 | fn to_char(&self) -> char; 37 | } 38 | 39 | impl ToChar for usize { 40 | fn to_char(&self) -> char { 41 | ((*self % 26) as u8 + 65) as char 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | extern crate ultra; 4 | 5 | use ultra::{Enigma, decrypt}; 6 | 7 | 8 | trait CasedString { 9 | fn with_case_of(&self, target: &str) -> String; 10 | } 11 | 12 | impl CasedString for String { 13 | fn with_case_of(&self, target: &str) -> String { 14 | self.chars().zip(target.chars()).map(|(s, t)| { 15 | match t.is_lowercase() { 16 | true => s.to_ascii_lowercase(), 17 | false => s 18 | } 19 | }).collect() 20 | } 21 | } 22 | 23 | 24 | fn main() { 25 | let app = clap_app!(ultra => 26 | (version: crate_version!()) 27 | (@setting ArgRequiredElseHelp) 28 | (@setting ColoredHelp) 29 | (@arg decrypt: --decrypt -d "Decrypt a given piece of ciphertext") 30 | (@arg randomize: --randomize -R "Encrypt a message with random Enigma settings") 31 | (@arg ROTORS: --rotor -w +takes_value "Rotor order (default: \"123\")") 32 | (@arg KEY: --key -k +takes_value "Key settings (default: \"AAA\")") 33 | (@arg RING: --ring -r +takes_value "Ring settings (default: \"AAA\")") 34 | (@arg PLUGBOARD: --plugboard -p +takes_value "Plugboard settings (default: \"\")") 35 | (@arg MESSAGE: +required "The message to encrypt/decrypt") 36 | ); 37 | 38 | let matches = app.get_matches(); 39 | let msg = matches.value_of("MESSAGE").unwrap(); 40 | 41 | if matches.is_present("decrypt") { 42 | let (plaintext, enigma) = decrypt(msg); 43 | println!("{}", plaintext.with_case_of(msg)); 44 | eprintln!("> {}", enigma); 45 | } 46 | 47 | else if matches.is_present("randomize") { 48 | let mut enigma = Enigma::random(); 49 | println!("{}", enigma.encrypt(msg).with_case_of(msg)); 50 | eprintln!("> {}", enigma); 51 | } 52 | 53 | else { 54 | let rotors = matches.value_of("ROTORS").unwrap_or("123"); 55 | let key = matches.value_of("KEY").unwrap_or("AAA"); 56 | let ring = matches.value_of("RING").unwrap_or("AAA"); 57 | let plugboard = matches.value_of("PLUGBOARD").unwrap_or(""); 58 | 59 | let mut enigma = Enigma::new(rotors, key, ring, 'B', plugboard); 60 | println!("{}", enigma.encrypt(msg).with_case_of(msg)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/plugboard.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use itertools::Itertools; 4 | 5 | use super::CharIndex; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct Plugboard { 9 | mapping: Vec, 10 | } 11 | 12 | impl Plugboard { 13 | pub fn new(pairs: &str) -> Plugboard { 14 | let mut mapping: Vec = ('A'..='Z').collect(); 15 | 16 | for pair in pairs.split_whitespace() { 17 | let pair: Vec = pair.chars().collect(); 18 | let a = pair[0]; 19 | let b = pair[1]; 20 | 21 | mapping[a.index()] = b; 22 | mapping[b.index()] = a; 23 | } 24 | 25 | Plugboard { mapping } 26 | } 27 | 28 | pub fn map(&self, c: char) -> char { 29 | self.mapping[c.index()] 30 | } 31 | } 32 | 33 | impl fmt::Display for Plugboard { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | let plugs: Vec = self.mapping.iter().zip('A'..='Z') 36 | .filter(|(a, b)| a < &b) 37 | .map(|(a, b)| format!("{}{}", a, b)) 38 | .collect(); 39 | 40 | if plugs.is_empty() { 41 | write!(f, "") 42 | } else { 43 | write!(f, "{}", plugs.iter().join(" ")) 44 | } 45 | } 46 | } 47 | 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::Plugboard; 52 | 53 | #[test] 54 | fn no_connections() { 55 | let plugboard = Plugboard::new(""); 56 | assert_eq!(plugboard.map('A'), 'A'); 57 | } 58 | 59 | #[test] 60 | fn single_connection() { 61 | let plugboard = Plugboard::new("AB"); 62 | assert_eq!(plugboard.map('A'), 'B'); 63 | assert_eq!(plugboard.map('B'), 'A'); 64 | assert_eq!(plugboard.map('C'), 'C'); 65 | } 66 | 67 | #[test] 68 | fn multiple_connections() { 69 | let plugboard = Plugboard::new("AB CD"); 70 | assert_eq!(plugboard.map('A'), 'B'); 71 | assert_eq!(plugboard.map('B'), 'A'); 72 | assert_eq!(plugboard.map('C'), 'D'); 73 | assert_eq!(plugboard.map('E'), 'E'); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/reflector.rs: -------------------------------------------------------------------------------- 1 | use super::CharIndex; 2 | 3 | use constants::REFLECTORS; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct Reflector { 7 | mapping: Vec, 8 | } 9 | 10 | impl Reflector { 11 | /// Creates a new `Reflector` with a given 26-character mapping. 12 | pub fn new(mapping: &str) -> Reflector { 13 | Reflector { mapping: mapping.chars().collect() } 14 | } 15 | 16 | pub fn from_enigma(reflector: char) -> Reflector { 17 | Reflector::new(REFLECTORS[reflector.index()]) 18 | } 19 | 20 | pub fn reflect(&self, c: char) -> char { 21 | self.mapping[c.index()] 22 | } 23 | } 24 | 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::Reflector; 29 | 30 | #[test] 31 | fn char_reflection() { 32 | let reflector = Reflector::new("EJMZALYXVBWFCRQUONTSPIKHGD"); 33 | assert_eq!(reflector.reflect('A'), 'E'); 34 | assert_eq!(reflector.reflect('B'), 'J'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/rotor.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::{CharIndex, ToChar}; 4 | use constants::{ROTORS, NOTCHES}; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct Rotor { 8 | mapping: Vec, 9 | inverse: Vec, 10 | notches: Vec, 11 | pub offset: usize, 12 | pub key_setting: usize, 13 | pub ring_setting: usize, 14 | } 15 | 16 | impl Rotor { 17 | /// Creates a new `Rotor`, where `mapping` is a 26-character `&str` 18 | /// containing some ordering of all letters in the alphabet, `notches` 19 | /// is a `&str` where each character in the string corresponds to a 20 | /// single notch in the rotor, and `key` and `ring` are the rotor's 21 | /// key and ring settings respectively. 22 | pub fn new(mapping: &str, notches: &str, key: char, ring: char) -> Rotor { 23 | let mapping: Vec = mapping.chars().collect(); 24 | 25 | if mapping.len() != 26 { 26 | panic!("Rotor mappings must be 26 characters long."); 27 | } 28 | 29 | let mut inverse = vec!['A'; 26]; 30 | 31 | for (i, &c) in mapping.iter().enumerate() { 32 | inverse[c.index()] = i.to_char(); 33 | } 34 | 35 | Rotor { 36 | mapping, 37 | inverse, 38 | notches: notches.chars().map(|c| c.index()).collect(), 39 | offset: key.index(), 40 | key_setting: key.index(), 41 | ring_setting: ring.index(), 42 | } 43 | } 44 | 45 | /// Creates a new `Rotor`, where `num` is a number from 1-8 corresponding 46 | /// to rotors I through VIII of the Enigma machine, and `key` and `ring` 47 | /// are the rotor's key and ring settings respectively. 48 | pub fn from_enigma(num: usize, key: char, ring: char) -> Rotor { 49 | Rotor::new(ROTORS[num - 1], NOTCHES[num - 1], key, ring) 50 | } 51 | 52 | fn map(&self, c: char, mapping: &[char]) -> char { 53 | let offset = 26 + self.offset - self.ring_setting; 54 | let index = mapping[(c.index() + offset) % 26].index(); 55 | (52 + index - offset).to_char() 56 | } 57 | 58 | /// Returns the substitution of a given character 59 | /// based on the current offset of the rotor. 60 | pub fn substitute(&self, c: char) -> char { 61 | self.map(c, &self.mapping) 62 | } 63 | 64 | /// Returns the substitution of a given character when run through 65 | /// the rotor in reverse (on the path back from the reflector). 66 | pub fn invert(&self, c: char) -> char { 67 | self.map(c, &self.inverse) 68 | } 69 | 70 | /// Advances this rotor one position. 71 | pub fn advance(&mut self) { 72 | self.offset = (self.offset + 1) % 26; 73 | } 74 | 75 | /// Returns true if the rotor is currently in a notch position. 76 | pub fn at_notch(&self) -> bool { 77 | self.notches.iter().any(|&n| n == self.offset) 78 | } 79 | 80 | /// Resets the rotor to its initial state. 81 | pub fn reset(&mut self) { 82 | self.offset = self.key_setting; 83 | } 84 | } 85 | 86 | impl fmt::Display for Rotor { 87 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 88 | for (i, &rotor) in ROTORS.iter().enumerate() { 89 | if self.mapping.iter().collect::() == rotor { 90 | return write!(f, "{}", i + 1) 91 | } 92 | } 93 | write!(f, "?") 94 | } 95 | } 96 | 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::Rotor; 101 | 102 | #[test] 103 | fn char_substitution() { 104 | let rotor = Rotor::from_enigma(1, 'A', 'A'); 105 | assert_eq!(rotor.substitute('A'), 'E'); 106 | assert_eq!(rotor.substitute('B'), 'K'); 107 | } 108 | 109 | #[test] 110 | fn step_rotor() { 111 | let mut rotor = Rotor::from_enigma(1, 'A', 'A'); 112 | assert_eq!(rotor.substitute('A'), 'E'); 113 | 114 | rotor.advance(); 115 | assert_eq!(rotor.offset, 1); 116 | assert_eq!(rotor.substitute('A'), 'J'); 117 | 118 | rotor.advance(); 119 | assert_eq!(rotor.offset, 2); 120 | assert_eq!(rotor.substitute('A'), 'K'); 121 | } 122 | 123 | #[test] 124 | fn step_inverse() { 125 | let mut rotor = Rotor::from_enigma(1, 'A', 'A'); 126 | assert_eq!(rotor.invert('E'), 'A'); 127 | rotor.advance(); 128 | assert_eq!(rotor.invert('K'), 'D'); 129 | rotor.advance(); 130 | assert_eq!(rotor.invert('M'), 'K'); 131 | } 132 | 133 | #[test] 134 | fn key_setting() { 135 | let rotor = Rotor::from_enigma(1, 'D', 'A'); 136 | assert_eq!(rotor.offset, 3) 137 | } 138 | 139 | #[test] 140 | fn ring_setting() { 141 | let rotor = Rotor::from_enigma(1, 'A', 'B'); 142 | assert_eq!(rotor.substitute('A'), 'K'); 143 | } 144 | 145 | #[test] 146 | fn reset_rotor() { 147 | let mut rotor = Rotor::from_enigma(1, 'A', 'A'); 148 | assert_eq!(rotor.offset, 0); 149 | 150 | for _ in 0..10 { 151 | rotor.advance(); 152 | } 153 | 154 | assert_eq!(rotor.offset, 10); 155 | rotor.reset(); 156 | assert_eq!(rotor.offset, 0); 157 | } 158 | 159 | #[test] 160 | fn inverse_mapping() { 161 | let rotor = Rotor::from_enigma(1, 'A', 'A'); 162 | let inverse: String = rotor.inverse.into_iter().collect(); 163 | assert_eq!(&inverse, "UWYGADFPVZBECKMTHXSLRINQOJ"); 164 | } 165 | 166 | #[test] 167 | fn matching_inverses() { 168 | let mut rotor = Rotor::from_enigma(1, 'A', 'A'); 169 | for i in b'A'..(b'Z' + 1) { 170 | let c = i as char; 171 | assert_eq!(c, rotor.invert(rotor.substitute(c))); 172 | rotor.advance(); 173 | } 174 | } 175 | 176 | #[test] 177 | #[should_panic] 178 | fn invalid_rotor() { 179 | Rotor::new("ABC", "A", 'A', 'A'); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/decrypt.rs: -------------------------------------------------------------------------------- 1 | extern crate ultra; 2 | 3 | mod texts; 4 | 5 | use texts::TWO_CITIES; 6 | use ultra::{decrypt, Enigma}; 7 | 8 | // Removes all non-alphabetic characters from a string and uppercases it. 9 | macro_rules! preprocess { 10 | ($string:tt) => { 11 | $string.to_uppercase().chars() 12 | .filter(|&c| c.is_alphabetic()) 13 | .collect::() 14 | } 15 | } 16 | 17 | #[test] 18 | fn two_cities_decryption() { 19 | let two_cities = preprocess!(TWO_CITIES); 20 | let mut enigma = Enigma::random_from_u64_seed(0); 21 | let ciphertext = enigma.encrypt(&two_cities); 22 | let (plaintext, _) = decrypt(&ciphertext); 23 | assert_eq!(plaintext, two_cities); 24 | } 25 | -------------------------------------------------------------------------------- /tests/encrypt.rs: -------------------------------------------------------------------------------- 1 | extern crate ultra; 2 | 3 | use ultra::Enigma; 4 | 5 | #[test] 6 | fn expected_ciphertext() { 7 | let mut enigma = Enigma::new("123", "BAT", "HTU", 'B', ""); 8 | assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "USSXBXPNRLBSTKQR"); 9 | } 10 | 11 | #[test] 12 | fn turnover_points() { 13 | let mut enigma = Enigma::new("123", "AAA", "ADU", 'B', ""); 14 | assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "ACGXKHKYCBVQZMJM"); 15 | } 16 | -------------------------------------------------------------------------------- /tests/texts.rs: -------------------------------------------------------------------------------- 1 | pub const TWO_CITIES: &'static str = " 2 | It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way -- in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only. 3 | 4 | There were a king with a large jaw and a queen with a plain face, on the throne of England; there were a king with a large jaw and a queen with a fair face, on the throne of France. In both countries it was clearer than crystal to the lords of the State preserves of loaves and fishes, that things in general were settled for ever. 5 | 6 | It was the year of Our Lord one thousand seven hundred and seventy-five. Spiritual revelations were conceded to England at that favoured period, as at this. Mrs. Southcott had recently attained her five-and-twentieth blessed birthday, of whom a prophetic private in the Life Guards had heralded the sublime appearance by announcing that arrangements were made for the swallowing up of London and Westminster. Even the Cock-lane ghost had been laid only a round dozen of years, after rapping out its messages, as the spirits of this very year last past (supernaturally deficient in originality) rapped out theirs. Mere messages in the earthly order of events had lately come to the English Crown and People, from a congress of British subjects in America: which, strange to relate, have proved more important to the human race than any communications yet received through any of the chickens of the Cock-lane brood. 7 | "; 8 | --------------------------------------------------------------------------------