├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/project-template.xml:
--------------------------------------------------------------------------------
1 |
2 | IJ_BASE_PACKAGE
3 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------