├── .gitignore ├── .idea ├── description.html ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── project-template.xml ├── uiDesigner.xml └── vcs.xml ├── EnigmaLib.iml ├── LICENSE ├── README.md ├── resources └── data │ ├── bigrams │ ├── quadgrams │ ├── single │ └── trigrams ├── src └── com │ └── mikepound │ ├── Main.java │ ├── analysis │ ├── EnigmaAnalysis.java │ ├── EnigmaKey.java │ ├── ScoredEnigmaKey.java │ └── fitness │ │ ├── BigramFitness.java │ │ ├── FitnessFunction.java │ │ ├── FrequencyAnalysis.java │ │ ├── IoCFitness.java │ │ ├── KnownPlaintextFitness.java │ │ ├── QuadramFitness.java │ │ ├── SingleCharacterFitness.java │ │ └── TrigramFitness.java │ └── enigma │ ├── Enigma.java │ ├── Plugboard.java │ ├── Reflector.java │ └── Rotor.java └── test └── EnigmaTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | .idea/usage.statistics.xml 3 | .idea/tasks.xml 4 | .idea/shelf/* 5 | .idea/libraries/*.xml 6 | out/* -------------------------------------------------------------------------------- /.idea/description.html: -------------------------------------------------------------------------------- 1 | Simple Java application that includes a class with main() method -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/project-template.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EnigmaLib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Enigma 2 | 3 | This is a Java implementation of an Enigma machine, along with code that attempts to break the encryption. This code is associated with the Computerphile video on [cracking enigma](https://www.youtube.com/watch?v=RzWB5jL5RX0). 4 | 5 | An enigma machine is a mechanical encryption device that saw a lot of use before and during WW2. This code simulates a 3 rotor enigma, including the 8 rotors commonly seen during the war. 6 | 7 | ## Installing and Usage 8 | You can compile and run this code yourself if you have Java installed. For convenience I recommend using [IntelliJ](https://www.jetbrains.com/idea/) or a similar IDE. The community edition is free, and it'll make editing, compiling and running the code a lot easier. If you'd like to run it yourself, first install java, then follow the instructions below. This assumes you've installed java and git. 9 | 10 | ### Windows 11 | Clone and traverse into the enigma directory 12 | ``` 13 | git clone https://github.com/mikepound/enigma.git 14 | cd enigma 15 | ``` 16 | 17 | Compile all the java files from src into bin 18 | ``` 19 | javac -d bin -sourcepath src src\com\mikepound\Main.java 20 | ``` 21 | 22 | Copy the n-gram statistics into the bin folder too 23 | ``` 24 | xcopy resources\data bin\data\ 25 | ``` 26 | 27 | Run the Enigma code in main 28 | ``` 29 | java -cp bin com.mikepound.Main 30 | ``` 31 | 32 | ### Linux/Unix 33 | Clone and traverse into the enigma directory 34 | ``` 35 | git clone https://github.com/mikepound/enigma.git 36 | cd enigma 37 | ``` 38 | 39 | Compile all the java files from src into bin 40 | ``` 41 | javac -d bin -sourcepath src src/com/mikepound/Main.java 42 | ``` 43 | 44 | Copy the n-gram statistics into the bin folder too 45 | ``` 46 | cp -r resources/data bin/data 47 | ``` 48 | 49 | Run the Enigma code in main 50 | ``` 51 | java -cp bin com.mikepound.Main 52 | ``` 53 | 54 | ## The Enigma Machine 55 | The code for the enigma machine can be found in the `enigma` package. In the `analysis` package is the code to perform attacks on ciphertext. The attack uses various fitness functions that attempt to measure the effectiveness of a test decryption, found within the `analysis.fitness` package. Finally, the `Main.java` file is where you'll find the actual attack I performed in the video, and so it also contains a lot of examples you can use to run your own attacks. 56 | 57 | ### Creating a Java Enigma 58 | The code itself is fairly straightforward. You can create a new enigma machine using a constructor, for example this code will create a new object called `enigmaMachine` with the settings provided: 59 | 60 | ```java 61 | enigmaMachine = new Enigma(new String[] {"VII", "V", "IV"}, "B", new int[] {10,5,12}, new int[] {1,2,3}, "AD FT WH JO PN"); 62 | ``` 63 | 64 | Rotors and the reflector are given by their common names used in the war, with rotors labelled as `"I"` through to `"VIII"`, and reflectors `"B"` and `"C"`. I've not implemented every variant, such as the thin reflectors seen in naval 4-rotor enigma. You could easily add these if you liked. Starting positions and ring settings are given as integers 0-25 rather than the A-Z often seen, this is just to avoid unnecessary mappings. The majority of the code here treats letters as 0-25 to aid indexing. Plugs are given as a string of character pairs representing steckered partners. If you don't wish to use a plugboard, `""` or `null` is fine. 65 | 66 | ### Encrypting and Decrypting 67 | Given an enigma instance like the `enigmaMachine` above, encryption or decryption is performed on character arrays of capital letters [A-Z]. Simply to save time I\'ve not done a lot of defensive coding to remove invalid characters, so be careful to only use uppercase, or to strip unwanted characters out of strings. Here is an encryption example using the enigma machine above: 68 | 69 | ```java 70 | char[] plaintext = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); 71 | char[] ciphertext = enigmaMachine.encrypt(plaintext); 72 | String s = new String(ciphertext); // UJFZBOKXBAQSGCLDNUTSNTASEF 73 | ``` 74 | You can quickly check everything is working by running the tests found in the `EnigmaTest.java` file. 75 | 76 | ### How it works 77 | Throughout the enigma machine, letters A-Z are represented as integers 0-25. Most of the components, the rotors, reflector and plugboard are treated as arrays that map values 0-25 to a different set of values 0-25. Encrypting or decrypting is simply a case of passing a value through these arrays in turn. What makes enigma trickier is that the arrays rotate, and that they can have different starting or ring positions. For efficiency in this implementation I keep the arrays fixed, and simulate rotation by shifting the index in and out of each rotor. Before each character is encrypted the rotors rotate, sometimes causing the neighbouring rotors to also rotate, this is handled by the `rotate()` function. Enigma has a quirk whereby the middle rotors moves twice when it turns the left-most rotor. This is called double stepping, and is also implemented here. 78 | 79 | ## Breaking a Code 80 | Breaking an enigma message here comes down to decrypting a ciphertext with all possible rotor configurations and seeing which output looks the best. We measure best here using a fitness function. 81 | 82 | ### Fitness functions 83 | The code makes a number of fitness functions available that can be used to measure how close a test decryption is to English text. Each works similarly, some work better than others. You can test to see which work best for a given message. The fitness functions are: 84 | * **Index of coincidence**. The probability of any random two letters being identical. Tends to be higher for proper sentences than for random encrypted text. I've found this is quite good as an initial fitness function when there are many plugs involved. 85 | * **Single / Bi / Tri / Quad grams**. The probability of a sentence measured based on the probability of constituent sequences of characters. Bigrams are pairs, such as AA or ST. Trigrams are triplets, e.g. THE, and so on. The more letters you use, e.g. single -> bi -> tri -> quad seems to improve the power of the fitness function, but you can't rely on this. I've found the longer measures are better when you already have some of the settings correct. 86 | * **Plaintext Fitness**. This function is a known plaintext attack, comparing the decryption against all or portions of a suspected real plaintext. This is by far the most effective solution, even a few words of known plaintext will substantially increase your odds of a break even with a number of plugboard swaps. The constructor for this fitness function has two possible constructors: 87 | ```java 88 | public KnownPlaintextFitness(char[] plaintext) 89 | ``` 90 | Use this if you have an entire complete plaintext you're looking for. 91 | 92 | ```java 93 | public KnownPlaintextFitness(String[] words, int[] offsets) 94 | ``` 95 | This one takes pairs of words and their possible positions within the plaintext. For example, in the string "tobeornottobethatisthequestion" you might supply {"to", "that", "question"} and {0, 13, 22}. This function is used if you can guess some words, but aren't sure of the whole sentence, such as when you have partially broken the message already. Note that in the default example known plaintext won't improve much, because the attack is already successful. The errors in the output are not due to the fitness function, rather that we are not simultaneously pairing rotors and ring settings. 96 | 97 | ### Ciphertext Analysis 98 | The basic approach to the attack is as follows: 99 | 1. Decrypt the ciphertext with every possible rotor in each position, and rotated to each starting position. All rotor ring settings are set to 0. No plugboard. For each decryption, measure the text fitness using one of the available fitness functions. Save the best performing rotor configuration. 100 | 2. Fix the rotors, and iterate through all possible ring settings for the middle and right rotors, again testing the fitness of the decryption. You do not have to use the same fitness function as before. 101 | 3. Fix all settings, and then use a hill climbing approach to find the best performing plugboard swaps, again measured using a fitness function. 102 | 103 | ## Notes 104 | * The code is fairly efficient, Enigma boils down to a lot of array indexing into different rotors. This said, I didn't worry too much about speed, it's plenty fast enough. I used classes and functions rather than doing things inline, for example. Modern compilers will optimise a lot of it anyway. 105 | * I've added a more optimised and multi-threaded version in a branch called [optimised](https://github.com/mikepound/enigma/tree/optimised). I've managed to get the code to break a message in under 4 seconds on one of our servers! I'm keeping this code separate to the main branch to keep the main branch clean and based off the original video. 106 | * Similarly, in the brute force key search code, for simplicity I create new enigma machines as required rather than implementing a number of specific reinitialisation functions that would be faster. 107 | * I've not written any kind of command line parsing here. You're welcome to add this, but i felt for a tutorial on enigma and breaking it, a step by step procedure in main was fine. 108 | 109 | ## Resources 110 | 1. For more details on enigma, the [wikipedia articles](https://en.wikipedia.org/wiki/Enigma_machine) are a great resource. 111 | 112 | 2. This attack is a variant of that originally proposed by James Gillogly. His work on this is still available via the web archive [here](https://web.archive.org/web/20060720040135/http://members.fortunecity.com/jpeschel/gillog1.htm). 113 | 114 | 3. If you'd like a more visual example of both encryption and cracking enigma codes, the Cryptool project is a great tool for this. [Cryptool 2](https://www.cryptool.org/en/) has a great visualiser and can run cracking code similar to my own. I used cryptool to write the tests I used to make sure my own enigma implementation was working. 115 | -------------------------------------------------------------------------------- /resources/data/bigrams: -------------------------------------------------------------------------------- 1 | TH,-1.449013409 2 | HE,-1.512191427 3 | IN,-1.61390336 4 | ER,-1.688613896 5 | AN,-1.702206436 6 | RE,-1.73181457 7 | ON,-1.754969661 8 | AT,-1.827767223 9 | EN,-1.837361388 10 | ND,-1.868932911 11 | TI,-1.872060209 12 | ES,-1.873091732 13 | OR,-1.893965891 14 | TE,-1.919059941 15 | OF,-1.92997127 16 | ED,-1.932511289 17 | IS,-1.947525422 18 | IT,-1.949514395 19 | AL,-1.963590891 20 | AR,-1.968632554 21 | ST,-1.977375495 22 | TO,-1.982438091 23 | NT,-1.982444505 24 | NG,-2.020900336 25 | SE,-2.030530699 26 | HA,-2.033499918 27 | AS,-2.059934443 28 | OU,-2.06047959 29 | IO,-2.078348971 30 | LE,-2.081312302 31 | VE,-2.083398382 32 | CO,-2.100256231 33 | ME,-2.100723261 34 | DE,-2.116441678 35 | HI,-2.117337845 36 | RI,-2.138096049 37 | RO,-2.138630234 38 | IC,-2.155704603 39 | NE,-2.160068245 40 | EA,-2.162307236 41 | RA,-2.163908268 42 | CE,-2.18614067 43 | LI,-2.204570365 44 | CH,-2.223468807 45 | LL,-2.239147148 46 | BE,-2.239363652 47 | MA,-2.247744566 48 | SI,-2.259592113 49 | OM,-2.262603076 50 | UR,-2.265401943 51 | CA,-2.269085278 52 | EL,-2.275477096 53 | TA,-2.275817497 54 | LA,-2.277753295 55 | NS,-2.293335589 56 | DI,-2.307182677 57 | FO,-2.311799545 58 | HO,-2.314345963 59 | PE,-2.32058193 60 | EC,-2.32122429 61 | PR,-2.323790404 62 | NO,-2.332944204 63 | CT,-2.336325682 64 | US,-2.342698315 65 | AC,-2.348942874 66 | OT,-2.354476245 67 | IL,-2.36498436 68 | TR,-2.370773763 69 | LY,-2.371597259 70 | NC,-2.381172084 71 | ET,-2.384462101 72 | UT,-2.392382797 73 | SS,-2.392464335 74 | SO,-2.400409293 75 | RS,-2.401726932 76 | UN,-2.404048728 77 | LO,-2.412419006 78 | WA,-2.414159201 79 | GE,-2.414325545 80 | IE,-2.414938342 81 | WH,-2.421597561 82 | EE,-2.422961794 83 | WI,-2.426640145 84 | EM,-2.427519162 85 | AD,-2.434203617 86 | OL,-2.437121354 87 | RT,-2.441679814 88 | PO,-2.44204408 89 | WE,-2.442614041 90 | NA,-2.45937654 91 | UL,-2.461137971 92 | NI,-2.469528181 93 | TS,-2.471741391 94 | MO,-2.472527837 95 | OW,-2.480857391 96 | PA,-2.490028435 97 | IM,-2.497900846 98 | MI,-2.497994583 99 | AI,-2.499697985 100 | SH,-2.501358676 101 | IR,-2.501451825 102 | SU,-2.50699316 103 | ID,-2.529436767 104 | OS,-2.537651773 105 | IV,-2.540836453 106 | IA,-2.543205298 107 | AM,-2.54535943 108 | FI,-2.545787037 109 | CI,-2.550545047 110 | VI,-2.569369768 111 | PL,-2.580065581 112 | IG,-2.593525599 113 | TU,-2.593618716 114 | EV,-2.593828333 115 | LD,-2.597555687 116 | RY,-2.606003654 117 | MP,-2.621283807 118 | FE,-2.626034464 119 | BL,-2.631898829 120 | AB,-2.638675646 121 | GH,-2.643012184 122 | TY,-2.643444319 123 | OP,-2.64992447 124 | WO,-2.654127921 125 | SA,-2.661508753 126 | AY,-2.662818706 127 | EX,-2.669495744 128 | KE,-2.670057368 129 | FR,-2.671235992 130 | OO,-2.677244957 131 | AV,-2.68838885 132 | AG,-2.688829319 133 | IF,-2.691955524 134 | AP,-2.692833748 135 | GR,-2.706023753 136 | OD,-2.708965594 137 | BO,-2.709052036 138 | SP,-2.718388971 139 | RD,-2.722811797 140 | DO,-2.725299688 141 | UC,-2.726646196 142 | BU,-2.732959298 143 | EI,-2.736715167 144 | OV,-2.749368892 145 | BY,-2.753332979 146 | RM,-2.7566322 147 | EP,-2.765549184 148 | TT,-2.767808962 149 | OC,-2.778833405 150 | FA,-2.78515672 151 | EF,-2.788526731 152 | CU,-2.788842129 153 | RN,-2.794901106 154 | SC,-2.810371084 155 | GI,-2.819196072 156 | DA,-2.820829349 157 | YO,-2.824193702 158 | CR,-2.825487137 159 | CL,-2.826780647 160 | DU,-2.828388288 161 | GA,-2.829512196 162 | QU,-2.831086319 163 | UE,-2.831265849 164 | FF,-2.834706463 165 | BA,-2.835039364 166 | EY,-2.842405058 167 | LS,-2.849202154 168 | VA,-2.85393378 169 | UM,-2.858918465 170 | PP,-2.864722295 171 | UA,-2.865398203 172 | UP,-2.866421231 173 | LU,-2.869057495 174 | GO,-2.879005769 175 | HT,-2.88543613 176 | RU,-2.891676013 177 | UG,-2.893103733 178 | DS,-2.898731893 179 | LT,-2.907851193 180 | PI,-2.909762746 181 | RC,-2.915829108 182 | RR,-2.918137841 183 | EG,-2.922500795 184 | AU,-2.924333444 185 | CK,-2.929496213 186 | EW,-2.932535666 187 | MU,-2.940745156 188 | BR,-2.952492071 189 | BI,-2.972254535 190 | PT,-2.975587678 191 | AK,-2.980056138 192 | PU,-2.980716652 193 | UI,-2.99501989 194 | RG,-3.00098501 195 | IB,-3.006110451 196 | TL,-3.00678789 197 | NY,-3.009139927 198 | KI,-3.009313004 199 | RK,-3.013074511 200 | YS,-3.013979921 201 | OB,-3.014704608 202 | MM,-3.017416675 203 | FU,-3.017842284 204 | PH,-3.025073923 205 | OG,-3.026760305 206 | MS,-3.03229684 207 | YE,-3.03306751 208 | UD,-3.039046742 209 | MB,-3.044526217 210 | IP,-3.049580587 211 | UB,-3.052662689 212 | OI,-3.056749179 213 | RL,-3.064166964 214 | GU,-3.066667078 215 | DR,-3.068288416 216 | HR,-3.073695965 217 | CC,-3.080198938 218 | TW,-3.084219164 219 | FT,-3.087967414 220 | WN,-3.102441099 221 | NU,-3.104331894 222 | AF,-3.129553401 223 | HU,-3.132575168 224 | NN,-3.138079287 225 | EO,-3.139659251 226 | VO,-3.148086515 227 | RV,-3.159371392 228 | NF,-3.172755293 229 | XP,-3.174808737 230 | GN,-3.182837374 231 | SM,-3.185758904 232 | FL,-3.187722637 233 | IZ,-3.191520606 234 | OK,-3.191709301 235 | NL,-3.195288882 236 | MY,-3.206304491 237 | GL,-3.217263994 238 | AW,-3.222455275 239 | JU,-3.231343929 240 | OA,-3.240437408 241 | EQ,-3.242209661 242 | SY,-3.245309914 243 | SL,-3.252738318 244 | PS,-3.263036604 245 | JO,-3.269301128 246 | LF,-3.271833897 247 | NV,-3.283936959 248 | JE,-3.284937358 249 | NK,-3.287304306 250 | KN,-3.288708911 251 | GS,-3.290787954 252 | DY,-3.297373564 253 | HY,-3.300256858 254 | ZE,-3.303359155 255 | KS,-3.323225052 256 | XT,-3.33105038 257 | BS,-3.338827361 258 | IK,-3.367413717 259 | DD,-3.369049108 260 | CY,-3.379670122 261 | RP,-3.380698512 262 | SK,-3.403791278 263 | XI,-3.404301926 264 | OE,-3.413067814 265 | OY,-3.441516026 266 | WS,-3.454889511 267 | LV,-3.457101 268 | DL,-3.490256406 269 | RF,-3.491330049 270 | EU,-3.506503823 271 | DG,-3.508592249 272 | WR,-3.511997288 273 | XA,-3.528692899 274 | YI,-3.54031016 275 | NM,-3.556745061 276 | EB,-3.567454391 277 | RB,-3.573289946 278 | TM,-3.577096968 279 | XC,-3.577413943 280 | EH,-3.579652794 281 | TC,-3.582756185 282 | GY,-3.586030337 283 | JA,-3.587346441 284 | HN,-3.589088245 285 | YP,-3.603550888 286 | ZA,-3.603736794 287 | GG,-3.606341795 288 | YM,-3.625712175 289 | SW,-3.628411273 290 | BJ,-3.634053336 291 | LM,-3.637877336 292 | CS,-3.641627898 293 | II,-3.642402448 294 | IX,-3.656946236 295 | XE,-3.661651888 296 | OH,-3.670513206 297 | LK,-3.705213789 298 | DV,-3.720043539 299 | LP,-3.720550107 300 | AX,-3.724933427 301 | OX,-3.731061529 302 | UF,-3.732071111 303 | DM,-3.740484077 304 | IU,-3.759226326 305 | SF,-3.76536983 306 | BT,-3.766904289 307 | KA,-3.770682594 308 | YT,-3.777702302 309 | EK,-3.783258881 310 | PM,-3.797071521 311 | YA,-3.802283534 312 | GT,-3.812404845 313 | WL,-3.817551815 314 | RH,-3.820690103 315 | YL,-3.831017934 316 | HS,-3.83315057 317 | AH,-3.865080733 318 | YC,-3.869647988 319 | YN,-3.878959793 320 | RW,-3.894239119 321 | HM,-3.894720087 322 | LW,-3.898291019 323 | HL,-3.899908131 324 | AE,-3.906700224 325 | ZI,-3.916588823 326 | AZ,-3.925581142 327 | LC,-3.927312299 328 | PY,-3.929454126 329 | AJ,-3.929864924 330 | IQ,-3.946777096 331 | NJ,-3.955209613 332 | BB,-3.961256496 333 | NH,-3.963222989 334 | UO,-3.972375625 335 | KL,-3.975932419 336 | LR,-3.997779126 337 | TN,-3.999537647 338 | GM,-4.006204606 339 | SN,-4.037396012 340 | NR,-4.038495977 341 | FY,-4.041050544 342 | MN,-4.056007666 343 | DW,-4.088181705 344 | SB,-4.101479028 345 | YR,-4.108374106 346 | DN,-4.120938353 347 | SQ,-4.128196754 348 | ZO,-4.143813955 349 | OJ,-4.156400204 350 | YD,-4.166341418 351 | LB,-4.174554688 352 | WT,-4.184327137 353 | LG,-4.215534683 354 | KO,-4.216376539 355 | NP,-4.219271913 356 | SR,-4.222576947 357 | NQ,-4.225482092 358 | KY,-4.225504298 359 | LN,-4.231374052 360 | NW,-4.236796316 361 | TF,-4.247091912 362 | FS,-4.258886071 363 | CQ,-4.261652437 364 | DH,-4.264529175 365 | SD,-4.2791286 366 | VY,-4.310050015 367 | DJ,-4.320401836 368 | HW,-4.321103153 369 | XU,-4.321383978 370 | AO,-4.334778659 371 | ML,-4.336625358 372 | UK,-4.336855913 373 | UY,-4.340340261 374 | EJ,-4.342327506 375 | EZ,-4.344550096 376 | HB,-4.35755255 377 | NZ,-4.359611385 378 | NB,-4.362916927 379 | MC,-4.365294417 380 | YB,-4.366620313 381 | TP,-4.367091088 382 | XH,-4.379720934 383 | UX,-4.405078621 384 | TZ,-4.414657224 385 | BV,-4.415227725 386 | MF,-4.418124869 387 | WD,-4.451208259 388 | OZ,-4.459392329 389 | YW,-4.47215247 390 | KH,-4.496865151 391 | GD,-4.5003793 392 | BM,-4.50458721 393 | MR,-4.50779074 394 | KU,-4.519177688 395 | UV,-4.535048863 396 | DT,-4.538249866 397 | HD,-4.544162285 398 | AA,-4.548222663 399 | XX,-4.552195078 400 | DF,-4.556172085 401 | DB,-4.55704462 402 | JI,-4.558660383 403 | KR,-4.565156181 404 | XO,-4.568828603 405 | CM,-4.574297823 406 | ZZ,-4.575062993 407 | NX,-4.581555237 408 | YG,-4.58626518 409 | XY,-4.588986451 410 | KG,-4.591253004 411 | TB,-4.594398479 412 | DC,-4.598754898 413 | BD,-4.606583459 414 | SG,-4.607658553 415 | WY,-4.615338254 416 | ZY,-4.627550676 417 | AQ,-4.648903385 418 | HF,-4.649137175 419 | CD,-4.651505734 420 | VU,-4.655117512 421 | KW,-4.661911851 422 | ZU,-4.66705968 423 | BN,-4.678888137 424 | IH,-4.679592982 425 | TG,-4.705725222 426 | XV,-4.709229154 427 | UZ,-4.718819549 428 | BC,-4.723648487 429 | XF,-4.731079458 430 | YZ,-4.741793681 431 | KM,-4.747342727 432 | DP,-4.761282494 433 | LH,-4.790822754 434 | WF,-4.793806185 435 | KF,-4.799310909 436 | PF,-4.837177569 437 | CF,-4.851359519 438 | MT,-4.864300021 439 | YU,-4.876905018 440 | CP,-4.881204789 441 | PB,-4.883153222 442 | TD,-4.887434464 443 | ZL,-4.900497021 444 | SV,-4.906067039 445 | HC,-4.910725241 446 | MG,-4.911912283 447 | PW,-4.918240039 448 | GF,-4.919197071 449 | PD,-4.92130135 450 | PN,-4.924684252 451 | PC,-4.929626329 452 | RX,-4.931806798 453 | TV,-4.934247199 454 | IJ,-4.954313521 455 | WM,-4.96260308 456 | UH,-4.971671753 457 | WK,-4.971692258 458 | WB,-4.974103337 459 | BH,-4.976011029 460 | OQ,-4.984403166 461 | KT,-4.985824199 462 | RQ,-5.00067909 463 | KB,-5.045257402 464 | CG,-5.052679984 465 | VR,-5.057478463 466 | CN,-5.06549187 467 | PK,-5.086595318 468 | UU,-5.107638412 469 | YF,-5.122906985 470 | WP,-5.128338999 471 | CZ,-5.136294103 472 | KP,-5.138598467 473 | DQ,-5.150735648 474 | WU,-5.157904398 475 | FM,-5.163723288 476 | WC,-5.171254957 477 | MD,-5.173069076 478 | KD,-5.173855849 479 | ZH,-5.17643893 480 | GW,-5.188675386 481 | RZ,-5.195090514 482 | CB,-5.200951484 483 | IW,-5.204389271 484 | XL,-5.226746517 485 | HP,-5.227581392 486 | MW,-5.233626032 487 | VS,-5.238989899 488 | FC,-5.239226507 489 | RJ,-5.257128017 490 | BP,-5.261908666 491 | MH,-5.273125584 492 | HH,-5.281981906 493 | YH,-5.283385727 494 | UJ,-5.287393174 495 | FG,-5.291095693 496 | FD,-5.305099274 497 | GB,-5.305783274 498 | PG,-5.324554852 499 | TK,-5.333523337 500 | KK,-5.34357581 501 | HQ,-5.37372594 502 | FN,-5.377467069 503 | LZ,-5.379502274 504 | VL,-5.384952972 505 | GP,-5.385254851 506 | HZ,-5.41959819 507 | DK,-5.472746721 508 | YK,-5.482060688 509 | QI,-5.498150168 510 | LX,-5.515069865 511 | VD,-5.524352505 512 | ZS,-5.526129719 513 | BW,-5.530080695 514 | XQ,-5.535032976 515 | MV,-5.537840394 516 | UW,-5.556740366 517 | HG,-5.558673775 518 | FB,-5.561970386 519 | SJ,-5.568136957 520 | WW,-5.58228096 521 | GK,-5.584566568 522 | UQ,-5.59071501 523 | BG,-5.592668394 524 | SZ,-5.602559632 525 | JR,-5.614723623 526 | QL,-5.623544635 527 | ZT,-5.628857342 528 | HK,-5.630942215 529 | VC,-5.632575961 530 | XM,-5.632683245 531 | GC,-5.640296496 532 | FW,-5.640535741 533 | PZ,-5.645226774 534 | KC,-5.649066485 535 | HV,-5.65133959 536 | XW,-5.651372234 537 | ZW,-5.652286919 538 | FP,-5.653422053 539 | IY,-5.654484801 540 | PV,-5.656260476 541 | VT,-5.65907296 542 | JP,-5.662775889 543 | CV,-5.681602958 544 | ZB,-5.682432173 545 | VP,-5.709041959 546 | ZR,-5.724243266 547 | FH,-5.73702891 548 | YV,-5.741286767 549 | ZG,-5.775643463 550 | ZM,-5.776843721 551 | ZV,-5.785676959 552 | QS,-5.80197363 553 | KV,-5.80527041 554 | VN,-5.814937478 555 | ZN,-5.81667601 556 | QA,-5.816901066 557 | YX,-5.825792141 558 | JN,-5.832056168 559 | BF,-5.836493174 560 | MK,-5.852843974 561 | CW,-5.858106738 562 | JM,-5.886770685 563 | LQ,-5.89595886 564 | JH,-5.900964689 565 | KJ,-5.9097223 566 | JC,-5.912683964 567 | GZ,-5.914751909 568 | JS,-5.927878431 569 | TX,-5.927896695 570 | FK,-5.932578021 571 | JL,-5.946089812 572 | VM,-5.948012817 573 | LJ,-5.949160801 574 | TJ,-5.949184778 575 | JJ,-5.975988206 576 | CJ,-5.978605091 577 | VG,-5.978866153 578 | MJ,-5.984320083 579 | JT,-5.985134221 580 | PJ,-6.00137141 581 | WG,-6.010583 582 | VH,-6.020270897 583 | BK,-6.028676998 584 | VV,-6.031469929 585 | JD,-6.032701077 586 | TQ,-6.049244955 587 | VB,-6.052950166 588 | JF,-6.066061778 589 | DZ,-6.10763555 590 | XB,-6.114794651 591 | JB,-6.122610432 592 | ZC,-6.127813095 593 | FJ,-6.135185441 594 | YY,-6.15068618 595 | QN,-6.180728605 596 | XS,-6.193782539 597 | QR,-6.208569778 598 | JK,-6.20961474 599 | JV,-6.214737926 600 | QQ,-6.216637871 601 | XN,-6.242398337 602 | VF,-6.259776674 603 | PX,-6.281856475 604 | ZD,-6.299435833 605 | QT,-6.308589086 606 | ZP,-6.316069184 607 | QO,-6.328190144 608 | DX,-6.337499368 609 | HJ,-6.342183834 610 | GV,-6.373787599 611 | JW,-6.383530666 612 | QC,-6.398119538 613 | JY,-6.400893473 614 | GJ,-6.411565896 615 | QB,-6.415391115 616 | PQ,-6.418768924 617 | JG,-6.435298796 618 | BZ,-6.447006446 619 | MX,-6.452664893 620 | QM,-6.454619898 621 | MZ,-6.463299209 622 | QF,-6.473444282 623 | WJ,-6.489165943 624 | ZQ,-6.489709282 625 | XR,-6.492394101 626 | ZK,-6.492811649 627 | CX,-6.507328344 628 | FX,-6.540579116 629 | FV,-6.543163845 630 | BX,-6.558870513 631 | VW,-6.579683255 632 | VJ,-6.590236728 633 | MQ,-6.599903917 634 | QV,-6.647072061 635 | ZF,-6.66601583 636 | QE,-6.692464934 637 | YJ,-6.700975835 638 | GX,-6.704318598 639 | KX,-6.705574105 640 | XG,-6.715759016 641 | QD,-6.734259824 642 | XJ,-6.741713396 643 | SX,-6.747963158 644 | VZ,-6.750195653 645 | VX,-6.797125597 646 | WV,-6.860110282 647 | YQ,-6.864869583 648 | BQ,-6.910486424 649 | GQ,-6.911593291 650 | VK,-6.921865504 651 | ZJ,-6.960197947 652 | XK,-7.001096931 653 | QP,-7.015340973 654 | HX,-7.028598192 655 | FZ,-7.028823625 656 | QH,-7.02940173 657 | QJ,-7.075580795 658 | JZ,-7.106444048 659 | VQ,-7.122111287 660 | KQ,-7.177793912 661 | XD,-7.224208555 662 | QW,-7.237232275 663 | JX,-7.241352894 664 | QX,-7.247422882 665 | KZ,-7.271911054 666 | WX,-7.272265525 667 | FQ,-7.275759387 668 | XZ,-7.314997722 669 | ZX,-7.415873602 -------------------------------------------------------------------------------- /resources/data/single: -------------------------------------------------------------------------------- 1 | E,-0.90336585 2 | T,-1.032659634 3 | A,-1.094711264 4 | O,-1.116867253 5 | I,-1.12094557 6 | N,-1.140643756 7 | S,-1.186234488 8 | R,-1.202080419 9 | H,-1.296424797 10 | L,-1.390513799 11 | D,-1.418282587 12 | C,-1.475763124 13 | U,-1.563884787 14 | M,-1.600021756 15 | F,-1.619223931 16 | P,-1.67042091 17 | G,-1.728303373 18 | W,-1.775813011 19 | Y,-1.778590954 20 | B,-1.828376277 21 | V,-1.977467865 22 | K,-2.267193463 23 | X,-2.629196699 24 | J,-2.799221369 25 | Q,-2.919124981 26 | Z,-3.045995483 -------------------------------------------------------------------------------- /src/com/mikepound/Main.java: -------------------------------------------------------------------------------- 1 | package com.mikepound; 2 | 3 | import com.mikepound.analysis.EnigmaAnalysis; 4 | import com.mikepound.analysis.EnigmaKey; 5 | import com.mikepound.analysis.ScoredEnigmaKey; 6 | import com.mikepound.analysis.fitness.*; 7 | import com.mikepound.enigma.Enigma; 8 | 9 | public class Main { 10 | 11 | public static void main(String[] args) { 12 | 13 | FitnessFunction ioc = new IoCFitness(); 14 | FitnessFunction bigrams = new BigramFitness(); 15 | FitnessFunction quadgrams = new QuadramFitness(); 16 | 17 | final long startTime = System.currentTimeMillis(); 18 | 19 | // For those interested, these were the original settings 20 | // II V III / 7 4 19 / 12 2 20 / AF TV KO BL RW 21 | char[] ciphertext = "OZLUDYAKMGMXVFVARPMJIKVWPMBVWMOIDHYPLAYUWGBZFAFAFUQFZQISLEZMYPVBRDDLAGIHIFUJDFADORQOOMIZPYXDCBPWDSSNUSYZTJEWZPWFBWBMIEQXRFASZLOPPZRJKJSPPSTXKPUWYSKNMZZLHJDXJMMMDFODIHUBVCXMNICNYQBNQODFQLOGPZYXRJMTLMRKQAUQJPADHDZPFIKTQBFXAYMVSZPKXIQLOQCVRPKOBZSXIUBAAJBRSNAFDMLLBVSYXISFXQZKQJRIQHOSHVYJXIFUZRMXWJVWHCCYHCXYGRKMKBPWRDBXXRGABQBZRJDVHFPJZUSEBHWAEOGEUQFZEEBDCWNDHIAQDMHKPRVYHQGRDYQIOEOLUBGBSNXWPZCHLDZQBWBEWOCQDBAFGUVHNGCIKXEIZGIZHPJFCTMNNNAUXEVWTWACHOLOLSLTMDRZJZEVKKSSGUUTHVXXODSKTFGRUEIIXVWQYUIPIDBFPGLBYXZTCOQBCAHJYNSGDYLREYBRAKXGKQKWJEKWGAPTHGOMXJDSQKYHMFGOLXBSKVLGNZOAXGVTGXUIVFTGKPJU".toCharArray(); 22 | 23 | // Begin by finding the best combination of rotors and start positions (returns top n) 24 | ScoredEnigmaKey[] rotorConfigurations = EnigmaAnalysis.findRotorConfiguration(ciphertext, 25 | EnigmaAnalysis.AvailableRotors.FIVE, 26 | "", 27 | 10, 28 | ioc); 29 | 30 | System.out.println("\nTop 10 rotor configurations:"); 31 | for (ScoredEnigmaKey key : rotorConfigurations) { 32 | System.out.println(String.format("%s %s %s / %d %d %d / %f", 33 | key.rotors[0], key.rotors[1], key.rotors[2], 34 | key.indicators[0], key.indicators[1], key.indicators[2], 35 | key.getScore())); 36 | } 37 | System.out.println(String.format("Current decryption: %s\n", 38 | new String(new Enigma(rotorConfigurations[0]).encrypt(ciphertext)))); 39 | 40 | // Next find the best ring settings for the best configuration (index 0) 41 | ScoredEnigmaKey rotorAndRingConfiguration = EnigmaAnalysis.findRingSettings(rotorConfigurations[0], ciphertext, bigrams); 42 | 43 | System.out.println(String.format("Best ring settings: %d %d %d", 44 | rotorAndRingConfiguration.rings[0], rotorAndRingConfiguration.rings[1], rotorAndRingConfiguration.rings[2])); 45 | System.out.println(String.format("Current decryption: %s\n", 46 | new String(new Enigma(rotorAndRingConfiguration).encrypt(ciphertext)))); 47 | 48 | // Finally, perform hill climbing to find plugs one at a time 49 | ScoredEnigmaKey optimalKeyWithPlugs = EnigmaAnalysis.findPlugs(rotorAndRingConfiguration, 5, ciphertext, quadgrams); 50 | System.out.println(String.format("Best plugboard: %s", optimalKeyWithPlugs.plugboard)); 51 | System.out.println(String.format("Final decryption: %s\n", 52 | new String(new Enigma(optimalKeyWithPlugs).encrypt(ciphertext)))); 53 | 54 | final long endTime = System.currentTimeMillis(); 55 | 56 | System.out.println("Total execution time: " + (endTime - startTime)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/EnigmaAnalysis.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis; 2 | 3 | import com.mikepound.analysis.fitness.FitnessFunction; 4 | import com.mikepound.enigma.Enigma; 5 | import com.mikepound.enigma.Plugboard; 6 | 7 | import java.util.*; 8 | 9 | public class EnigmaAnalysis { 10 | public enum AvailableRotors { 11 | THREE, 12 | FIVE, 13 | EIGHT 14 | } 15 | 16 | public static ScoredEnigmaKey[] findRotorConfiguration(char[] ciphertext, AvailableRotors rotors, String plugboard, int requiredKeys, FitnessFunction f) { 17 | String[] availableRotorList; 18 | 19 | switch (rotors) { 20 | case THREE: 21 | availableRotorList = new String[] {"I", "II", "III"}; 22 | break; 23 | case FIVE: 24 | availableRotorList = new String[] {"I", "II", "III", "IV", "V"}; 25 | break; 26 | case EIGHT: 27 | default: 28 | availableRotorList = new String[] {"I", "II", "III", "IV", "V", "VI", "VII", "VIII"}; 29 | break; 30 | } 31 | 32 | String[] optimalRotors; 33 | int[] optimalPositions; 34 | 35 | List keySet = new ArrayList<>(); 36 | 37 | for (String rotor1 : availableRotorList) { 38 | for (String rotor2 : availableRotorList) { 39 | if (rotor1.equals(rotor2)) continue; 40 | for (String rotor3 : availableRotorList) { 41 | if (rotor1.equals(rotor3) || rotor2.equals(rotor3)) continue; 42 | System.out.println(rotor1 + " " + rotor2 + " " + rotor3 ); 43 | 44 | float maxFitness = -1e30f; 45 | EnigmaKey bestKey = null; 46 | for (int i = 0; i < 26; i++) { 47 | for (int j = 0; j < 26; j++) { 48 | for (int k = 0; k < 26; k++) { 49 | Enigma e = new Enigma(new String[]{rotor1, rotor2, rotor3}, "B", new int[]{i, j, k}, new int[]{0, 0, 0}, plugboard); 50 | char[] decryption = e.encrypt(ciphertext); 51 | float fitness = f.score(decryption); 52 | if (fitness > maxFitness) { 53 | maxFitness = fitness; 54 | optimalRotors = new String[] { e.leftRotor.getName(), e.middleRotor.getName(), e.rightRotor.getName()}; 55 | optimalPositions = new int[] { i, j, k}; 56 | bestKey = new EnigmaKey(optimalRotors, optimalPositions, null, plugboard); 57 | } 58 | } 59 | } 60 | } 61 | 62 | keySet.add(new ScoredEnigmaKey(bestKey, maxFitness)); 63 | } 64 | } 65 | } 66 | 67 | // Sort keys by best performing (highest fitness score) 68 | keySet.sort(Collections.reverseOrder()); 69 | return keySet.stream() 70 | .sorted(Collections.reverseOrder()) 71 | .limit(requiredKeys) 72 | .toArray(ScoredEnigmaKey[]::new); 73 | } 74 | 75 | public static ScoredEnigmaKey findRingSettings(EnigmaKey key, char[] ciphertext, FitnessFunction f) { 76 | EnigmaKey newKey = new EnigmaKey(key); 77 | 78 | int rightRotorIndex = 2, middleRotorIndex = 1; 79 | 80 | // Optimise right rotor 81 | int optimalIndex = EnigmaAnalysis.findRingSetting(newKey, ciphertext, rightRotorIndex, f); 82 | newKey.rings[rightRotorIndex] = optimalIndex; 83 | newKey.indicators[rightRotorIndex] = (newKey.indicators[rightRotorIndex] + optimalIndex) % 26; 84 | 85 | // Optimise middle rotor 86 | optimalIndex = EnigmaAnalysis.findRingSetting(newKey, ciphertext, middleRotorIndex, f); 87 | newKey.rings[middleRotorIndex] = optimalIndex; 88 | newKey.indicators[middleRotorIndex] = (newKey.indicators[middleRotorIndex] + optimalIndex) % 26; 89 | 90 | // Calculate fitness and return scored key 91 | Enigma e = new Enigma(newKey); 92 | char[] decryption = e.encrypt(ciphertext); 93 | return new ScoredEnigmaKey(newKey, f.score(decryption)); 94 | } 95 | 96 | public static int findRingSetting(EnigmaKey key, char[] ciphertext, int rotor, FitnessFunction f) { 97 | String[] rotors = key.rotors; 98 | int[] originalIndicators = key.indicators; 99 | int[] originalRingSettings = key.rings != null ? key.rings : new int[] {0,0,0} ; 100 | String plugboard = key.plugboard; 101 | int optimalRingSetting = 0; 102 | 103 | 104 | float maxFitness = -1e30f; 105 | for (int i = 0; i < 26; i++) { 106 | int[] currentStartingPositions = Arrays.copyOf(originalIndicators,3); 107 | int[] currentRingSettings = Arrays.copyOf(originalRingSettings, 3); 108 | 109 | currentStartingPositions[rotor] = Math.floorMod(currentStartingPositions[rotor] + i, 26); 110 | currentRingSettings[rotor] = i; 111 | 112 | Enigma e = new Enigma(rotors, 113 | "B", 114 | currentStartingPositions, 115 | currentRingSettings, 116 | plugboard); 117 | char[] decryption = e.encrypt(ciphertext); 118 | float fitness = f.score(decryption); 119 | if (fitness > maxFitness) { 120 | maxFitness = fitness; 121 | optimalRingSetting = i; 122 | } 123 | } 124 | return optimalRingSetting; 125 | } 126 | 127 | public static String findPlug(EnigmaKey key, char[] ciphertext, FitnessFunction f) { 128 | Set unpluggedCharacters = Plugboard.getUnpluggedCharacters(key.plugboard); 129 | Set charCount = new HashSet(); 130 | 131 | EnigmaKey currentKey = new EnigmaKey(key); 132 | String originalPlugs = currentKey.plugboard; 133 | String optimalPlugSetting = ""; 134 | float maxFitness = -1e30f; 135 | for (int i: unpluggedCharacters) { 136 | for (int j: unpluggedCharacters) { 137 | if (i >= j) continue; 138 | 139 | String plug = "" + (char)(i + 65) + (char)(j + 65); 140 | currentKey.plugboard = originalPlugs.isEmpty() ? plug : originalPlugs + " " + plug; 141 | 142 | Enigma e = new Enigma(currentKey); 143 | char[] decryption = e.encrypt(ciphertext); 144 | float fitness = f.score(decryption); 145 | if (fitness > maxFitness) { 146 | maxFitness = fitness; 147 | optimalPlugSetting = plug; 148 | } 149 | } 150 | } 151 | 152 | return optimalPlugSetting; 153 | } 154 | 155 | public static ScoredEnigmaKey findPlugs(EnigmaKey key, int maxPlugs, char[] ciphertext, FitnessFunction f) { 156 | EnigmaKey currentKey = new EnigmaKey(key); 157 | String plugs = ""; 158 | //String findPlug(EnigmaKey key, char[] ciphertext, FitnessFunction f) { 159 | for (int i = 0; i < maxPlugs; i++) { 160 | currentKey.plugboard = plugs; 161 | String nextPlug = findPlug(currentKey, ciphertext, f); 162 | plugs = plugs.isEmpty() ? nextPlug : plugs + " " + nextPlug; 163 | } 164 | 165 | currentKey.plugboard = plugs; 166 | // Calculate fitness and return scored key 167 | Enigma e = new Enigma(currentKey); 168 | char[] decryption = e.encrypt(ciphertext); 169 | return new ScoredEnigmaKey(currentKey, f.score(decryption)); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/EnigmaKey.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis; 2 | 3 | import java.util.Arrays; 4 | 5 | public class EnigmaKey { 6 | public String[] rotors; 7 | public int[] indicators; 8 | public int[] rings; 9 | public String plugboard; 10 | 11 | public EnigmaKey(String[] rotors, int[] indicators, int[] rings, String plugboardConnections) { 12 | this.rotors = rotors == null ? new String[] {"I", "II", "III"} : rotors; 13 | this.indicators = indicators == null ? new int[] {0,0,0} : indicators; 14 | this.rings = rings == null ? new int[] {0,0,0} : rings; 15 | this.plugboard = plugboardConnections == null ? "" : plugboardConnections; 16 | } 17 | 18 | public EnigmaKey(EnigmaKey key) { 19 | this.rotors = key.rotors == null ? new String[] {"I", "II", "III"} : new String[] {key.rotors[0], key.rotors[1], key.rotors[2]}; 20 | this.indicators = key.indicators == null ? new int[] {0,0,0} : Arrays.copyOf(key.indicators, 3); 21 | this.rings = key.rings == null ? new int[] {0,0,0} : Arrays.copyOf(key.rings,3); 22 | this.plugboard = key.plugboard == null ? "" : key.plugboard; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/ScoredEnigmaKey.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis; 2 | 3 | import com.mikepound.enigma.Enigma; 4 | 5 | public class ScoredEnigmaKey extends EnigmaKey implements Comparable { 6 | float score; 7 | 8 | public ScoredEnigmaKey(EnigmaKey key, float score) { 9 | super(key); 10 | this.score = score; 11 | } 12 | 13 | public float getScore() { return this.score; } 14 | 15 | @Override 16 | public int compareTo(ScoredEnigmaKey o) { 17 | return Float.compare(this.score, o.score); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/BigramFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.stream.Stream; 7 | 8 | public class BigramFitness extends FitnessFunction { 9 | private float[] bigrams; 10 | 11 | private static int biIndex(int a, int b) { 12 | return (a << 5) | b; 13 | } 14 | 15 | 16 | public BigramFitness() { 17 | // Bigrams 18 | this.bigrams = new float[826]; 19 | Arrays.fill(this.bigrams, (float)Math.log10(epsilon)); 20 | try (final InputStream is = BigramFitness.class.getResourceAsStream("/data/bigrams"); 21 | final Reader r = new InputStreamReader(is, StandardCharsets.UTF_8); 22 | final BufferedReader br = new BufferedReader(r); 23 | final Stream lines = br.lines()) { 24 | lines.map(line -> line.split(",")) 25 | .forEach(s -> { 26 | String key = s[0]; 27 | int i = biIndex(key.charAt(0) - 65, key.charAt(1) - 65); 28 | this.bigrams[i] = Float.parseFloat(s[1]); 29 | }); 30 | } catch (IOException e) { 31 | this.bigrams = null; 32 | } 33 | } 34 | 35 | @Override 36 | public float score(char[] text) { 37 | float fitness = 0; 38 | int current = 0; 39 | int next = text[0] - 65; 40 | for (int i = 1; i < text.length; i++) { 41 | current = next; 42 | next = text[i] - 65; 43 | fitness += this.bigrams[biIndex(current, next)]; 44 | } 45 | return fitness; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/FitnessFunction.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.stream.Stream; 7 | 8 | public abstract class FitnessFunction { 9 | protected final float epsilon = 3e-10f; 10 | 11 | public float score(char[] text) { 12 | return 0f; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/FrequencyAnalysis.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | public class FrequencyAnalysis { 4 | private float[] counts; 5 | private int total; 6 | 7 | public FrequencyAnalysis() { 8 | this.total = 0; 9 | this.counts = new float[26]; 10 | } 11 | 12 | public void analyse(byte[] text) { 13 | for (byte b: text) { 14 | this.counts[b]++; 15 | } 16 | this.total += text.length; 17 | } 18 | 19 | public float[] frequencies() { 20 | float[] freq = new float[26]; 21 | for (int i = 0; i < freq.length; i++) { 22 | freq[i] = this.counts[i] / total; 23 | } 24 | return freq; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/IoCFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.Random; 7 | import java.util.stream.Stream; 8 | 9 | public class IoCFitness extends FitnessFunction { 10 | 11 | public IoCFitness() { 12 | } 13 | 14 | @Override 15 | public float score(char[] text) { 16 | int[] histogram = new int[26]; 17 | for (char c : text) { 18 | histogram[c - 65]++; 19 | } 20 | 21 | int n = text.length; 22 | float total = 0.0f; 23 | 24 | for (int v : histogram) { 25 | total += (v * (v - 1)); 26 | } 27 | 28 | return total / (n * (n-1)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/KnownPlaintextFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | public class KnownPlaintextFitness extends FitnessFunction { 4 | char[] plaintext; 5 | 6 | public KnownPlaintextFitness(char[] plaintext) { 7 | this.plaintext = plaintext; 8 | } 9 | 10 | public KnownPlaintextFitness(String[] words, int[] offsets) { 11 | int length = 0; 12 | for (int i = 0; i < words.length; i++) { 13 | int offset = offsets[i] + words[i].length(); 14 | length = Math.max(offset, length); 15 | } 16 | 17 | this.plaintext = new char[length]; 18 | 19 | for (int i = 0; i < words.length; i++) { 20 | System.arraycopy(words[i].toCharArray(), 0, this.plaintext, offsets[i], words[i].length()); 21 | } 22 | 23 | } 24 | 25 | @Override 26 | public float score(char[] text) { 27 | int length = Math.min(this.plaintext.length, text.length); 28 | int total = 0; 29 | for (int i = 0; i < length; i++) { 30 | if (this.plaintext[i] > 0) { 31 | total += this.plaintext[i] == text[i] ? 1 : 0; 32 | } 33 | } 34 | return total; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/QuadramFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.stream.Stream; 7 | 8 | public class QuadramFitness extends FitnessFunction { 9 | private float[] quadgrams; 10 | 11 | private static int quadIndex(int a, int b, int c, int d) { 12 | return (a << 15) | (b << 10) | (c << 5) | d; 13 | } 14 | 15 | public QuadramFitness() { 16 | // Quadgrams 17 | this.quadgrams = new float[845626]; 18 | Arrays.fill(this.quadgrams, (float)Math.log10(epsilon)); 19 | try (final InputStream is = QuadramFitness.class.getResourceAsStream("/data/quadgrams"); 20 | final Reader r = new InputStreamReader(is, StandardCharsets.UTF_8); 21 | final BufferedReader br = new BufferedReader(r); 22 | final Stream lines = br.lines()) { 23 | lines.map(line -> line.split(",")) 24 | .forEach(s -> { 25 | String key = s[0]; 26 | int i = quadIndex(key.charAt(0) - 65,key.charAt(1) - 65,key.charAt(2) - 65,key.charAt(3) - 65); 27 | this.quadgrams[i] = Float.parseFloat(s[1]); 28 | }); 29 | } catch (IOException e) { 30 | this.quadgrams = null; 31 | } 32 | } 33 | 34 | @Override 35 | public float score(char[] text) { 36 | float fitness = 0; 37 | int current = 0; 38 | int next1 = text[0] - 65; 39 | int next2 = text[1] - 65; 40 | int next3 = text[2] - 65; 41 | for (int i = 3; i < text.length; i++) { 42 | current = next1; 43 | next1 = next2; 44 | next2 = next3; 45 | next3 = text[i] - 65; 46 | fitness += this.quadgrams[quadIndex(current, next1, next2, next3)]; 47 | } 48 | return fitness; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/SingleCharacterFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.stream.Stream; 6 | 7 | public class SingleCharacterFitness extends FitnessFunction { 8 | private float[] singles; 9 | 10 | public SingleCharacterFitness() { 11 | // Single characters 12 | this.singles = new float[26]; 13 | try (final InputStream is = SingleCharacterFitness.class.getResourceAsStream("/data/single"); 14 | final Reader r = new InputStreamReader(is, StandardCharsets.UTF_8); 15 | final BufferedReader br = new BufferedReader(r); 16 | final Stream lines = br.lines()) { 17 | lines.map(line -> line.split(",")) 18 | .forEach(s -> { 19 | int i = s[0].charAt(0) - 65; 20 | this.singles[i] = Float.parseFloat(s[1]); 21 | }); 22 | } catch (IOException e) { 23 | this.singles = null; 24 | } 25 | } 26 | 27 | @Override 28 | public float score(char[] text) { 29 | float fitness = 0; 30 | for (char c: text) { 31 | fitness += this.singles[c - 65]; 32 | } 33 | return fitness; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/com/mikepound/analysis/fitness/TrigramFitness.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.analysis.fitness; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.stream.Stream; 7 | 8 | public class TrigramFitness extends FitnessFunction { 9 | private float[] trigrams; 10 | 11 | private static int triIndex(int a, int b, int c) { 12 | return (a << 10) | (b << 5) | c; 13 | } 14 | 15 | public TrigramFitness() { 16 | // Trigrams 17 | this.trigrams = new float[26426]; 18 | Arrays.fill(this.trigrams, (float)Math.log10(epsilon)); 19 | try (final InputStream is = TrigramFitness.class.getResourceAsStream("/data/trigrams"); 20 | final Reader r = new InputStreamReader(is, StandardCharsets.UTF_8); 21 | final BufferedReader br = new BufferedReader(r); 22 | final Stream lines = br.lines()) { 23 | lines.map(line -> line.split(",")) 24 | .forEach(s -> { 25 | String key = s[0]; 26 | int i = triIndex(key.charAt(0) - 65, key.charAt(1) - 65, key.charAt(2) - 65); 27 | this.trigrams[i] = Float.parseFloat(s[1]); 28 | }); 29 | } catch (IOException e) { 30 | this.trigrams = null; 31 | } 32 | } 33 | 34 | @Override 35 | public float score(char[] text) { 36 | float fitness = 0; 37 | int current = 0; 38 | int next1 = text[0] - 65; 39 | int next2 = text[1] - 65; 40 | for (int i = 2; i < text.length; i++) { 41 | current = next1; 42 | next1 = next2; 43 | next2 = text[i] - 65; 44 | fitness += this.trigrams[triIndex(current, next1, next2)]; 45 | } 46 | return fitness; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/mikepound/enigma/Enigma.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.enigma; 2 | 3 | 4 | import com.mikepound.analysis.EnigmaKey; 5 | 6 | public class Enigma { 7 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 8 | // A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 9 | public Rotor leftRotor; 10 | public Rotor middleRotor; 11 | public Rotor rightRotor; 12 | 13 | public Reflector reflector; 14 | 15 | public Plugboard plugboard; 16 | 17 | public Enigma(String[] rotors, String reflector, int[] rotorPositions, int[] ringSettings, String plugboardConnections) { 18 | this.leftRotor = Rotor.Create(rotors[0], rotorPositions[0], ringSettings[0]); 19 | this.middleRotor = Rotor.Create(rotors[1], rotorPositions[1], ringSettings[1]); 20 | this.rightRotor = Rotor.Create(rotors[2], rotorPositions[2], ringSettings[2]); 21 | this.reflector = Reflector.Create(reflector); 22 | this.plugboard = new Plugboard(plugboardConnections); 23 | } 24 | 25 | public Enigma(EnigmaKey key) { 26 | this(key.rotors, "B", key.indicators, key.rings, key.plugboard); 27 | } 28 | 29 | public void rotate() { 30 | // If middle rotor notch - double-stepping 31 | if (middleRotor.isAtNotch()) { 32 | middleRotor.turnover(); 33 | leftRotor.turnover(); 34 | } 35 | // If left-rotor notch 36 | else if (rightRotor.isAtNotch()) { 37 | middleRotor.turnover(); 38 | } 39 | 40 | // Increment right-most rotor 41 | rightRotor.turnover(); 42 | } 43 | 44 | public int encrypt(int c) { 45 | rotate(); 46 | 47 | // Plugboard in 48 | c = this.plugboard.forward(c); 49 | 50 | // Right to left 51 | int c1 = rightRotor.forward(c); 52 | int c2 = middleRotor.forward(c1); 53 | int c3 = leftRotor.forward(c2); 54 | 55 | // Reflector 56 | int c4 = reflector.forward(c3); 57 | 58 | // Left to right 59 | int c5 = leftRotor.backward(c4); 60 | int c6 = middleRotor.backward(c5); 61 | int c7 = rightRotor.backward(c6); 62 | 63 | // Plugboard out 64 | c7 = plugboard.forward(c7); 65 | 66 | return c7; 67 | } 68 | 69 | public char encrypt(char c) { 70 | return (char)(this.encrypt(c - 65) + 65); 71 | } 72 | 73 | public char[] encrypt(char[] input) { 74 | char[] output = new char[input.length]; 75 | for (int i = 0; i < input.length; i++) { 76 | output[i] = this.encrypt(input[i]); 77 | } 78 | return output; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/com/mikepound/enigma/Plugboard.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.enigma; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class Plugboard { 7 | private int[] wiring; 8 | 9 | public Plugboard(String connections) { 10 | this.wiring = decodePlugboard(connections); 11 | } 12 | 13 | public int forward(int c) { 14 | return this.wiring[c]; 15 | } 16 | 17 | private static int[] identityPlugboard() { 18 | int[] mapping = new int[26]; 19 | for (int i = 0; i < 26; i++) { 20 | mapping[i] = i; 21 | } 22 | return mapping; 23 | } 24 | 25 | public static Set getUnpluggedCharacters(String plugboard) { 26 | Set unpluggedCharacters = new HashSet<>(); 27 | for (int i = 0; i < 26; i++) { 28 | unpluggedCharacters.add(i); 29 | } 30 | 31 | if (plugboard.equals("")) { 32 | return unpluggedCharacters; 33 | } 34 | 35 | String[] pairings = plugboard.split("[^a-zA-Z]"); 36 | 37 | // Validate and create mapping 38 | for (String pair : pairings) { 39 | int c1 = pair.charAt(0) - 65; 40 | int c2 = pair.charAt(1) - 65; 41 | 42 | unpluggedCharacters.remove(c1); 43 | unpluggedCharacters.remove(c2); 44 | } 45 | 46 | return unpluggedCharacters; 47 | } 48 | 49 | public static int[] decodePlugboard(String plugboard) { 50 | if (plugboard == null || plugboard.equals("")) { 51 | return identityPlugboard(); 52 | } 53 | 54 | String[] pairings = plugboard.split("[^a-zA-Z]"); 55 | Set pluggedCharacters = new HashSet<>(); 56 | int[] mapping = identityPlugboard(); 57 | 58 | // Validate and create mapping 59 | for (String pair : pairings) { 60 | if (pair.length() != 2) 61 | return identityPlugboard(); 62 | 63 | int c1 = pair.charAt(0) - 65; 64 | int c2 = pair.charAt(1) - 65; 65 | 66 | if (pluggedCharacters.contains(c1) || pluggedCharacters.contains(c2)) { 67 | return identityPlugboard(); 68 | } 69 | 70 | pluggedCharacters.add(c1); 71 | pluggedCharacters.add(c2); 72 | 73 | mapping[c1] = c2; 74 | mapping[c2] = c1; 75 | } 76 | 77 | return mapping; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/mikepound/enigma/Reflector.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.enigma; 2 | 3 | public class Reflector { 4 | protected int[] forwardWiring; 5 | 6 | public Reflector(String encoding) { 7 | this.forwardWiring = decodeWiring(encoding); 8 | } 9 | 10 | public static Reflector Create(String name) { 11 | switch (name) { 12 | case "B": 13 | return new Reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT"); 14 | case "C": 15 | return new Reflector("FVPJIAOYEDRZXWGCTKUQSBNMHL"); 16 | default: 17 | return new Reflector("ZYXWVUTSRQPONMLKJIHGFEDCBA"); 18 | } 19 | } 20 | 21 | protected static int[] decodeWiring(String encoding) { 22 | char[] charWiring = encoding.toCharArray(); 23 | int[] wiring = new int[charWiring.length]; 24 | for (int i = 0; i < charWiring.length; i++) { 25 | wiring[i] = charWiring[i] - 65; 26 | } 27 | return wiring; 28 | } 29 | 30 | public int forward(int c) { 31 | return this.forwardWiring[c]; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/com/mikepound/enigma/Rotor.java: -------------------------------------------------------------------------------- 1 | package com.mikepound.enigma; 2 | 3 | public class Rotor { 4 | protected String name; 5 | protected int[] forwardWiring; 6 | protected int[] backwardWiring; 7 | 8 | protected int rotorPosition; 9 | protected int notchPosition; 10 | protected int ringSetting; 11 | 12 | public Rotor(String name, String encoding, int rotorPosition, int notchPosition, int ringSetting) { 13 | this.name = name; 14 | this.forwardWiring = decodeWiring(encoding); 15 | this.backwardWiring = inverseWiring(this.forwardWiring); 16 | this.rotorPosition = rotorPosition; 17 | this.notchPosition = notchPosition; 18 | this.ringSetting = ringSetting; 19 | } 20 | 21 | public static Rotor Create(String name, int rotorPosition, int ringSetting) { 22 | switch (name) { 23 | case "I": 24 | return new Rotor("I","EKMFLGDQVZNTOWYHXUSPAIBRCJ", rotorPosition, 16, ringSetting); 25 | case "II": 26 | return new Rotor("II","AJDKSIRUXBLHWTMCQGZNPYFVOE", rotorPosition, 4, ringSetting); 27 | case "III": 28 | return new Rotor("III","BDFHJLCPRTXVZNYEIWGAKMUSQO", rotorPosition, 21, ringSetting); 29 | case "IV": 30 | return new Rotor("IV","ESOVPZJAYQUIRHXLNFTGKDCMWB", rotorPosition, 9, ringSetting); 31 | case "V": 32 | return new Rotor("V","VZBRGITYUPSDNHLXAWMJQOFECK", rotorPosition, 25, ringSetting); 33 | case "VI": 34 | return new Rotor("VI","JPGVOUMFYQBENHZRDKASXLICTW", rotorPosition, 0, ringSetting) { 35 | @Override 36 | public boolean isAtNotch() { 37 | return this.rotorPosition == 12 || this.rotorPosition == 25; 38 | } 39 | }; 40 | case "VII": 41 | return new Rotor("VII","NZJHGRCXMYSWBOUFAIVLPEKQDT", rotorPosition, 0, ringSetting) { 42 | @Override 43 | public boolean isAtNotch() { 44 | return this.rotorPosition == 12 || this.rotorPosition == 25; 45 | } 46 | }; 47 | case "VIII": 48 | return new Rotor("VIII","FKQHTLXOCBJSPDZRAMEWNIUYGV", rotorPosition, 0, ringSetting) { 49 | @Override 50 | public boolean isAtNotch() { 51 | return this.rotorPosition == 12 || this.rotorPosition == 25; 52 | } 53 | }; 54 | default: 55 | return new Rotor("Identity","ABCDEFGHIJKLMNOPQRSTUVWXYZ", rotorPosition, 0, ringSetting); 56 | } 57 | } 58 | 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | public int getPosition() { 64 | return rotorPosition; 65 | } 66 | 67 | protected static int[] decodeWiring(String encoding) { 68 | char[] charWiring = encoding.toCharArray(); 69 | int[] wiring = new int[charWiring.length]; 70 | for (int i = 0; i < charWiring.length; i++) { 71 | wiring[i] = charWiring[i] - 65; 72 | } 73 | return wiring; 74 | } 75 | 76 | protected static int[] inverseWiring(int[] wiring) { 77 | int[] inverse = new int[wiring.length]; 78 | for (int i = 0; i < wiring.length; i++) { 79 | int forward = wiring[i]; 80 | inverse[forward] = i; 81 | } 82 | return inverse; 83 | } 84 | 85 | protected static int encipher(int k, int pos, int ring, int[] mapping) { 86 | int shift = pos - ring; 87 | return (mapping[(k + shift + 26) % 26] - shift + 26) % 26; 88 | } 89 | 90 | public int forward(int c) { 91 | return encipher(c, this.rotorPosition, this.ringSetting, this.forwardWiring); 92 | } 93 | 94 | public int backward(int c) { 95 | return encipher(c, this.rotorPosition, this.ringSetting, this.backwardWiring); 96 | } 97 | 98 | public boolean isAtNotch() { 99 | return this.notchPosition == this.rotorPosition; 100 | } 101 | 102 | public void turnover() { 103 | this.rotorPosition = (this.rotorPosition + 1) % 26; 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/EnigmaTest.java: -------------------------------------------------------------------------------- 1 | import com.mikepound.enigma.Enigma; 2 | import com.mikepound.enigma.Plugboard; 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Random; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 8 | 9 | class EnigmaTest { 10 | 11 | @Test 12 | void encryptTest() { 13 | // Basic settings 14 | Enigma e = new Enigma(new String[] {"I", "II", "III"}, "B", new int[] {0,0,0}, new int[] {0,0,0}, ""); 15 | String input = "ABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBABCDEFGHIJKLMNOPQRSTUVWXYZ"; 16 | String output = "BJELRQZVJWARXSNBXORSTNCFMEYHCXTGYJFLINHNXSHIUNTHEORXOPLOVFEKAGADSPNPCMHRVZCYECDAZIHVYGPITMSRZKGGHLSRBLHL"; 17 | char[] ciphertext = e.encrypt(input.toCharArray()); 18 | assertArrayEquals(output.toCharArray(), ciphertext); 19 | 20 | // Varied rotors 21 | e = new Enigma(new String[] {"VII", "V", "IV"}, "B", new int[] {10,5,12}, new int[] {1,2,3}, ""); 22 | ciphertext = e.encrypt(input.toCharArray()); 23 | output = "FOTYBPKLBZQSGZBOPUFYPFUSETWKNQQHVNHLKJZZZKHUBEJLGVUNIOYSDTEZJQHHAOYYZSENTGXNJCHEDFHQUCGCGJBURNSEDZSEPLQP"; 24 | assertArrayEquals(output.toCharArray(), ciphertext); 25 | 26 | // Long input 27 | e = new Enigma(new String[] {"III", "VI", "VIII"}, "B", new int[] {3,5,9}, new int[] {11,13,19}, ""); 28 | char[] longInput = new char[500]; 29 | for (int i = 0; i < 500; i++) longInput[i] = 'A'; 30 | ciphertext = e.encrypt(longInput); 31 | output = "YJKJMFQKPCUOCKTEZQVXYZJWJFROVJMWJVXRCQYFCUVBRELVHRWGPYGCHVLBVJEVTTYVMWKJFOZHLJEXYXRDBEVEHVXKQSBPYZN" + 32 | "IQDCBGTDDWZQWLHIBQNTYPIEBMNINNGMUPPGLSZCBRJULOLNJSOEDLOBXXGEVTKCOTTLDZPHBUFKLWSFSRKOMXKZELBDJNRUDUCO" + 33 | "TNCGLIKVKMHHCYDEKFNOECFBWRIEFQQUFXKKGNTSTVHVITVHDFKIJIHOGMDSQUFMZCGGFZMJUKGDNDSNSJKWKENIRQKSUUHJYMIG" + 34 | "WWNMIESFRCVIBFSOUCLBYEEHMESHSGFDESQZJLTORNFBIFUWIFJTOPVMFQCFCFPYZOJFQRFQZTTTOECTDOOYTGVKEWPSZGHCTQRP" + 35 | "GZQOVTTOIEGGHEFDOVSUQLLGNOOWGLCLOWSISUGSVIHWCMSIUUSBWQIGWEWRKQFQQRZHMQJNKQTJFDIJYHDFCWTHXUOOCVRCVYOHL"; 36 | assertArrayEquals(output.toCharArray(), ciphertext); 37 | } 38 | 39 | @Test 40 | void decryptTest() { 41 | Random rand = new Random(); 42 | String[] allRotors = new String[] {"I", "II", "III", "IV", "V", "VI", "VII", "VIII"}; 43 | 44 | char[] input = new char[1000]; 45 | for (int i = 0; i < 1000; i++) { 46 | input[i] = (char)(rand.nextInt(26) + 65); 47 | } 48 | 49 | for (int test = 0; test < 10; test++) { 50 | // Random initialisation 51 | String[] rotors = new String[] { allRotors[rand.nextInt(8)], 52 | allRotors[rand.nextInt(8)], 53 | allRotors[rand.nextInt(8)]}; 54 | 55 | int[] startingPositions = new int[] {rand.nextInt(26),rand.nextInt(26),rand.nextInt(26)}; 56 | int[] ringSettings = new int[] {rand.nextInt(26), rand.nextInt(26), rand.nextInt(26)}; 57 | 58 | // Machine 1 - Encryption 59 | Enigma e = new Enigma(rotors, "B", startingPositions, ringSettings, ""); 60 | char[] ciphertext = e.encrypt(input); 61 | 62 | // Machine 2 - Decryption 63 | Enigma e2 = new Enigma(rotors, "B", startingPositions, ringSettings, ""); 64 | char[] plaintext = e2.encrypt(ciphertext); 65 | 66 | assertArrayEquals(input, plaintext); 67 | } 68 | 69 | } 70 | 71 | @Test 72 | void plugboardTest() { 73 | // Simple test - 4 plugs 74 | Enigma e = new Enigma(new String[] {"I", "II", "III"}, "B", new int[] {0,0,0}, new int[] {0,0,0}, "AC FG JY LW"); 75 | char[] input = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".toCharArray(); 76 | char[] output = e.encrypt(input); 77 | char[] expectedOutput = "QREBNMCYZELKQOJCGJVIVGLYEMUPCURPVPUMDIWXPPWROOQEGI".toCharArray(); 78 | assertArrayEquals(expectedOutput, output); 79 | 80 | // 6 plugs 81 | e = new Enigma(new String[] {"IV", "VI", "III"}, "B", new int[] {0,10,6}, new int[] {0,0,0}, "BM DH RS KN GZ FQ"); 82 | input = "WRBHFRROSFHBCHVBENQFAGNYCGCRSTQYAJNROJAKVKXAHGUZHZVKWUTDGMBMSCYQSKABUGRVMIUOWAPKCMHYCRTSDEYTNJLVWNQY".toCharArray(); 83 | expectedOutput = "FYTIDQIBHDONUPAUVPNKILDHDJGCWFVMJUFNJSFYZTSPITBURMCJEEAMZAZIJMZAVFCTYTKYORHYDDSXHBLQWPJBMSSWIPSWLENZ".toCharArray(); 84 | output = e.encrypt(input); 85 | assertArrayEquals(expectedOutput, output); 86 | 87 | // 10 plugs 88 | e = new Enigma(new String[] {"I", "II", "III"}, "B", new int[] {0,1,20}, new int[] {5,5,4}, "AG HR YT KI FL WE NM SD OP QJ"); 89 | input = "RNXYAZUYTFNQFMBOLNYNYBUYPMWJUQSBYRHPOIRKQSIKBKEKEAJUNNVGUQDODVFQZHASHMQIHSQXICTSJNAUVZYIHVBBARPJADRH".toCharArray(); 90 | expectedOutput = "CFBJTPYXROYGGVTGBUTEBURBXNUZGGRALBNXIQHVBFWPLZQSCEZWTAWCKKPRSWOGNYXLCOTQAWDRRKBCADTKZGPWSTNYIJGLVIUQ".toCharArray(); 91 | output = e.encrypt(input); 92 | assertArrayEquals(expectedOutput, output); 93 | } 94 | } 95 | --------------------------------------------------------------------------------