charlesreid1.com blog

Perl vs. Java: N Queens Problem

Posted in Computer Science

permalink

Table of Contents

Summary

In this post, we describe an implementation of the N Queens Problem, which is a puzzle related to optimization, combinatorics, and recursive backtracking. The puzzle asks: how many configurations are there for placing 8 queens on a chessboard such that no queen can attack any othr queen?

This problem was implemented in Perl and in Java, the solution results were timed, and the codes were profiled. While Perl is an interpreted language, and is therefore fully expeted to be much slower than Java (which indeed it is), it is still useful to compare the performance between these two codes to gain an appreciation for the advantages and disadvantages to both approaches.

Background: Huh?

Recently I read an (11 year old) article by Steve Yegge entitled "Execution in the Kingdom of Nouns." In it, Steve describes the way that in Java, "Classes are really the only modeling tool Java provides you. So whenever a new idea occurs to you, you have to sculpt it or wrap it or smash at it until it becomes a thing, even if it began life as an action, a process, or any other non-'thing' concept."

The article inspired me to try on this verb-oriented mode of thinking in a more... active way. Prior experiences with OCaml were confusing, and Haskell continues to evade me, so it was easier to dust off old Perl skills than learn enough Haskell or Ocaml to solve N queens problem. Perl was the next-closest verb-oriented "scripting" language.

I was also familiar with the N queens problem, since I'm a programming instructor (I'll let you guess which language), and it seemed like a nice problem for both noun-based and verb-based approaches. But it also meant I had to learn enough Perl to solve the N queens problem.

...or, use the Perl solution to the N queens problem from Rosetta Code.

Right...

So here's the plan: study a verb-oriented implementation of this canonical, deceptively subtle programming problem in Perl; translate it into a verb-oriented Java program; and run the two head-to-head, using a profiler to understand the results.

N Queens Problem

The N queens problem predates computers - it's a chess puzzle that asks: how many ways can you place 8 queens on a chessboard such that no queen can attack any other queen?

The number of possible configurations of queens on a chessboard is 64 pick 8, or

$$ \dfrac{64!}{(64-8)!} = 64 \times 63 \times \dots \times 57 \times 56 = 178,462,987,637,760 $$

Here's that calculation in Python:

>>> import numpy as np
>>> np.prod(range(64-8+1,64+1))
178462987637760

That's bigger than the net worth of most U.S. Presidents!

If we implemented a dumb brute-force solution that tested each of these configurations, we'd be waiting until the heat death of the universe.

Fortunately, as we place queens on the board we can check if it is an invalid placement, and rule out any configurations that would follow from that choice. As long as we are making our choices in an orderly fashion, this enables us to rule out most of the nearly 10 quadrillion possibilities. If we place queens column-by-column and rule out rows where there are already queens, by keeping track of where queens have already been placed, we can reduce the number of possible rows by 1 with each queen placed. The first queen has 8 possible rows where it can be placed, the second queen has 7 possible rows (excluding the row that the first queen was placed on), and so on. The number of possiblities is:

$$ 8! = 8 \times 7 \times \dots \times 2 \times 1 = 40,320 $$

A big improvement! Here's that calculation in Python:

>>> def fact(n):
...     if(n==1):
...         return 1
...     else:
...         return n*fact(n-1)
...
>>> fact(8)
40320

This still-large number of possibilities can be further reduced by using the same procedure, but checking for invalid rows based on the diagonal squares that each already-placed queen attacks. This covers each precondition for a solved board, and allows the base case of the recursive backtracking method to be as simple as, "If you've reached this point, you have a valid solution. Add it to the solutions bucket."

Now, let's get to the solution algorithm.

N Queens Solution

As a recap, we dusted off our Perl skills to utilize an N queens solution in Perl from Rosetta Code.

Here's the pseudocode:

explore(column):
    if last column:
        # base case
        add to solutions
    else:
        # recursive case
        for each row:
            if this is a safe row:
                place queen on this row
                explore(column+1)
                remove queen from this row

Both codes use integer arrays to keep track of where queens are placed. Solutions are stringified version of these arrays, consisting of 8 digits.

Perl Solution

After looking at the Rosetta Code solution for a (long) while and marking it up with comments to understand what it was doing, I decided it was precisely the kind of verb-oriented solution I wanted to test out to compare Perl and Java. It uses no objects, but instead relies on fast built-in data structures (arrays), for loop expansion (only for my $i (1 .. $N), no for($i=1; $i<=$N; $i++)), and basic integer math. The cost of solving the problem comes down to basic indexing and array access.

This is the kind of solution I imagine a human calculator like Alan Turing or John Von Neuman looking at, nodding, and saying, "Makes sense! (And by the way the answer is 92.)"

Github gist: nqueens.pl

#!/usr/bin/perl

# Solve the N queens problem
# using recursive backtracking.
# 
# Author: Charles Reid
# Date: March 2017

# Create an array to store solutions
my @solutions;

# Create an array to store where queens have been placed
my @queens;

# Mark the rows already used (useful for lookup)
my @occupied;

# explore() implements a recursive, depth-first backtracking method
sub explore { 
    # Parameters:
    #   depth : this is the argument passed by the user

    # First argument passed to the function is $depth 
    # (how many queens we've placed on the board),
    # so use shift to pop that out of the parameters 
    my ($depth, @diag) = shift;

    # Explore is a recursive method,
    # so we need a base case and a recursive case.
    #
    # The base case is, we've reached a leaf node,
    # placed 8 queens, and had no problems, 
    # so we found a solution.
    if ($depth==$board_size) { 
        # Here, we store the stringified version of @queens,
        # which are the row numbers of prior queens. 
        # This is a global variable that is shared across
        # instances of this recursive function.
        push @solutions, "@queens\n";
        return;
    }

    # Mark the squares that are attackable, 
    # so that we can cut down on the search space.
    $#diag = 2 * $board_size;
    for( 0 .. $#queens) { 
        $ix1 = $queens[$_] + $depth - $_ ;
        $diag[ $ix1 ] = 1;

        $ix2 = $queens[$_] - $depth + $_ ;
        $diag[ $ix2 ] = 1;
    }

    for my $row (0 .. $board_size-1) {
        # Cut down on the search space:
        # if this square is already occupied
        # or will lead to an invalid solution,
        # don't bother exploring it.
        next if $occupied[$row] || $diag[$row];

        # Make a choice
        push @queens, $row;
        # Mark the square as occupied
        $occupied[$row] = 1;

        # Explore the consequences
        explore($depth+1);

        # Unmake the choice
        pop @queens;

        # Mark the square as unoccupied
        $occupied[$row] = 0;

    }
}

$board_size = 8; 

explore(0);

print "total ", scalar(@solutions), " solutions\n";

Java Solution

Starting with the Rosetta Code solution in Perl, I translated the algorithm into Java, sticking as closely as possible to the Way of the Verb. I replicated the solution in Java with a minimal amount of object-oriented-ness. A Board class simply wraps the same set of arrays and array manipulations that the Perl solution implements directly. These constitute the lookahead check for safe places to put the queen.

The Java solution implements a static class containing a Linked List to store solutions. This is the only use of non-array objects and has a trivial impact on the solution walltime.

Github gist: NQueens.java

(Verbatim code not included for length.)

Head to Head: Walltime vs. Number of Queens

Graph of walltime versus number of queens

-----------------------------------------------
| NQueens | Nsolutions | Java [s]  | Perl [s] |
|---------|------------|-----------|----------|
| 8       | 92         | 0.003     | 0.016    |
| 9       | 352        | 0.006     | 0.067    |
| 10      | 724        | 0.017     | 0.259    |
| 11      | 2680       | 0.061     | 1.542    |
| 12      | 14200      | 0.240     | 8.431    |
| 13      | 73712      | 1.113     | 48.542   |
| 14      | 365596     | 6.557     | 303.278  |
| 15      | 2279184    | 42.619    | 2057.052 |
-----------------------------------------------

Java smokes Perl.

Initially I was using the Unix time utility to time these two, and it seemed to be close for smaller problem sizes (N=9 or smaller) - Perl would start up and run faster than Java, measured end-to-end. But when you time the program by using timers built into the language, it removes some of the overhead from the timing comparisons, and Java becomes the clear winner.

We can dig deeper and understand this comparison better by using some profiling tools.

Perl Profiling

I profiled Perl with Devel::NYTProf , an excellent Perl module available here on Cpanm.

More details about the profiling tools I used for Perl are on the charlesreid1 wiki at Perl/Profiling.

To run with Devel::NYTProf, use `cpanm:

$ cpanm Devel::NYTProf

Now you can run Perl with Devel::NYTProf by doing:

$ perl -d:NYTProf nqueens.pl

This results in a binary output file called nytprof.out that can be processed with several NYTProf post-processing tools. Use the CSV file tool to begin with:

$ nytprofcsv nytprof.out

This puts the CSV file in a folder called nytprof/.

Perl Profiling Results

The CSV output of the NYTProf module gives a breakdown of the amount of time spent in each method call, how many times it was called, and how much time per call was spent. From this we can see the busiest lines are the lines accessing the arrays, and looping over the rows. This is confirmation that this algorithm is testing the performance of the arrays, and confirms the N queens problem is profiling Perl's core performance with its built-in data structures.

The profiling results of the 11 queens problem are shown below.

# Profile data generated by Devel::NYTProf::Reader
# Version: v6.04
# More information at http://metacpan.org/release/Devel-NYTProf/
# Format: time,calls,time/call,code
0.000238,2,0.000119,use Time::HiRes qw(time);
0.000039,2,0.000019,use strict;
0.000491,2,0.000246,use warnings;
0.000021,1,0.000021,my $start = time;
0.010338,2680,0.000004,push @solutions, "@queens\n";
0.009993,2680,0.000004,return;
0.186298,164246,0.000001,$#attacked = 2 * $board_size;
0.150338,164246,0.000001,for( 0 .. $#queens) { 
0.675523,1.26035e+06,0.000001,$attacked[ $ix2 ] = 1;
1.242624,164246,0.000008,for my $row (0 .. $board_size-1) {
0.267469,166925,0.000002,explore($depth+1);
0.125272,166925,0.000001,$occupied[$row] = 0;
0.000002,1,0.000002,explore(0);
0.000011,1,0.000011,my $duration = time - $start;
0.000075,1,0.000075,print "Found ", scalar(@solutions), " solutions\n";
0.000050,1,0.000050,printf "Execution time: %0.3f s \n",$duration;

One of the more interesting pieces of information comes from several lines populating the squares that are on the diagonals with other queens ($attacked):

# Format: time,calls,time/call,code
0.186298,164246,0.000001,$#attacked = 2 * $board_size;

The second column gives the number of times this line is executed - 164,246. This is actually the number of solutions that are tried, excluding the deepest depth of the tree (the base recursive case).

The Java profiler will show us that Java explores the exact same number of solutions, which is confirmation that these tests are comparing the two languages on equal footing.

Java Profiling

More details about the profiling tools I used for Java are on the charlesreid1 wiki at Java/Profiling

I profiled Java with two tools, the Java Interactive Profiler (JIP) and the HPROF tool that Oracle provides with Java.

No special compiler flags are needed, so compile as normal:

$ javac NQueens.java

If you are profiling with JIP, you want the JIP jar, as described on the wiki: Java/Profiling Then run Java with the -javaagent flag:

$ export PATH2JIP="${HOME}/Downloads/jip"
$ java -javaagent:${PATH2JIP}/profile/profile.jar NQueens

This results in a profile.txt file with detailed profiling information (an example is shown below).

The HPROF tool likewise requires no special compiler flags. It can be run with various options from the command line. Here's a basic usage of HPROF that will reduce the amount of output slightly, making the size of the output file a little smaller:

$ java -agentlib:hprof=verbose=n NQueens

This dumps out a file called java.hprof.txt that contains a significant amount of information. The most useful, though, is at the end, so use tail to get a quick overview of the results:

$ tail -n 100 java.hprof.txt

Java Profiling Results

The profiling results from JIP for the 11 queens problem are shown below.

+----------------------------------------------------------------------
|  File: profile.txt
|  Date: 2017.03.19 19:34:18 PM
+----------------------------------------------------------------------

+--------------------------------------+
| Most expensive methods summarized    |
+--------------------------------------+

               Net
          ------------
 Count     Time    Pct  Location
 =====     ====    ===  ========
166926    909.5   82.2  NQueens:explore
164246     55.6    5.0  Board:getDiagAttacked
166925     41.4    3.7  Board:unchoose
166925     40.7    3.7  Board:choose
164246     31.0    2.8  Board:getOccupied
     1     18.2    1.6  NQueens:main
  2680      7.3    0.7  Board:toString
  2680      2.3    0.2  SolutionSaver:saveSolution
     1      0.2    0.0  SolutionSaver:nSolutions
     1      0.1    0.0  SolutionSaver:<init>
     1      0.0    0.0  Board:<init>

From this output we can see that the method getDiagAttacked, which is called each time we check a solution in the recursive case, is called 164,246 times - exactly the same number of solutions that the Perl profiler showed. One of the downsides of the JIP profiler is that it only gives high-level profiling information about methods and classes - it stops there.

Fortunately, however, the HPROF tool picks up where JIP leaves off. The HPROF tool makes the program much slower but yields a huge amount of information. In addition to an enormous heap dump of all objects appearing on Java's heap at any point, it also shows where the time was spent in the low-level methods.

SITES BEGIN (ordered by live bytes) Sun Mar 19 19:34:21 2017
          percent          live          alloc'ed  stack class
 rank   self  accum     bytes objs     bytes  objs trace name
    1 86.01% 86.01%  10510976 164234  10510976 164234 300462 int[]
    2  1.93% 87.94%    235840 2680    235840  2680 300467 char[]
    3  1.09% 89.03%    133320 1515    133320  1515 300465 char[]
    4  1.07% 90.11%    131200    8    131200     8 300263 char[]
    5  1.05% 91.16%    128640 2680    128640  2680 300464 char[]
    6  1.04% 92.20%    127560 1313    127560  1313 300010 char[]
    7  0.76% 92.96%     92728 1009     92728  1009 300000 char[]
    8  0.54% 93.50%     65664    8     65664     8 300260 byte[]
    9  0.53% 94.02%     64320 2680     64320  2680 300468 java.util.LinkedList$Node
   10  0.53% 94.55%     64320 2680     64320  2680 300466 java.lang.String
SITES END

HPROF tells us that over 86% of the time spent on this program was spent accessing integer arrays. Again, confirmation that we are getting a fair measurement of Java's performance with a core data type, the integer array.

Head to Head: Walltime vs. Number of Solutions Tested

Using the results of the profilers from each N queens problem, N = 8 .. 15, I extracted the total number of solutions tried, and confirmed that these numbers were the same between Java and Perl for each of the problem sizes.

Here is a table of the number of solutions found, and number of solutions tried, versus problem size:

-------------------------------------------------------------
| NQueens | Nsolutions | Ntested     | Java [s]  | Perl [s] |
|---------|------------|-------------|-----------|----------|
| 8       | 92         | 1965        | 0.003     | 0.016    |
| 9       | 352        | 8042        | 0.006     | 0.067    |
| 10      | 724        | 34815       | 0.017     | 0.259    |
| 11      | 2680       | 164246      | 0.061     | 1.542    |
| 12      | 14200      | 841989      | 0.240     | 8.431    |
| 13      | 73712      | 4601178     | 1.113     | 48.542   |
| 14      | 365596     | 26992957    | 6.557     | 303.278  |
| 15      | 2279184    | 168849888   | 42.619    | 2057.052 |
-------------------------------------------------------------

When the wall time for Java and Perl are plotted against the number of solutions tested, an interesting trend emerges: the two scale the same way, with a fixed vertical offset.

Graph of walltime versus number of solutions tested

While this is proving what we already knew, that a compiled language beats a scripted language every time, it also provides proof Perl can scale as well as Java - it just takes significantly more overhead and time per statement.

Why Java Beat Perl

Compiled languages are turned into bytecode and pre-optimized for the processor.

Perl is a scripted and interpreted language, like Python, evaluated piece by piece.

So, we didn't learn anything surprising. But we did find an interesting result - Perl can scale as well as Java in its implementation of the N queens recursive backtracking algorithm.

Sources

  1. "Execution in the Kingdom of Nouns". Steve Yegge. March 2006. Accessed 18 March 2017. <https://web.archive.org/web/20170320081755/https://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html>

  2. "N-Queens Problem". Rosetta Code, GNU Free Documentation License. Edited 6 March 2017. Accessed 21 March 2017. <https://web.archive.org/web/20170320081421/http://rosettacode.org/wiki/N-queens_problem>

  3. "nqueens.pl". Charles Reid. Github Gist, Github Inc. Edited 20 March 2017. Accessed 20 March 2017. <https://gist.github.com/charlesreid1/4ce97a5f896ff1c89855a5d038d51535>

  4. "NQueens.java". Charles Reid. Github Gist, Github Inc. Edited 20 March 2017. Accessed 20 March 2017. <https://gist.github.com/charlesreid1/7b8d7b9dffb7b3090039849d72c5fff5>

  5. "Devel::NYTProf". Adam Kaplan, Tim Bunce. Copyright 2008-2016, Tim Bunce. Published 4 March 2008. Accessed 20 March 2017. <https://web.archive.org/web/20170320081508/http://search.cpan.org/~timb/Devel-NYTProf-6.04/lib/Devel/NYTProf.pm>

  6. "Perl/Profiling". Charles Reid. Edited 20 March 2017. Accessed 20 March 2017. <https://web.archive.org/web/20170320081532/https://charlesreid1.com/wiki/Perl/Profiling>

  7. "Java/Profiling". Charles Reid. Edited 20 March 2017. Accessed 20 March 2017. <https://web.archive.org/web/20170320081535/https://charlesreid1.com/wiki/Java/Profiling>

  8. "JIP - The Java Interactive Profiler." Andrew Wilcox. Published 30 April 2010. Accessed 20 March 2017. <https://web.archive.org/web/20170320081538/http://jiprof.sourceforge.net/>

  9. "HPROF". Oracle Corporation. Copyright 1993, 2016. Published 2016. Accessed 20 March 2017. <https://web.archive.org/web/20170320081540/https://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html>

Tags:    java    perl    algorithms    recursion    n-queens   

Enigma Cipher Implementation: Part 4: Combinatorics

Posted in Enigma

permalink

In this, the fourth article in a series on implementing the Enigma cipher in Java, we use some big number libraries to explore the combinatorics of the Enigma encryption scheme and better understand the Enigma's strengths and weaknesses.

Table of Contents

The Key Space

Basically, what the Enigma did was to encrypt each character of a message one at a time, using a different, unique key for each character. One key corresponded to one particular scrambled version of the alphabet (one possible set of substitutions). The huge number of possible initial settings for the machine - the rotors, wiring, and reflector - meant that finding the very first key was extremely difficult. Furthermore, as the operator entered additional characters into the Enigma, the machine would rotate the rotor wheels, sequentially stepping through the space of possible keys in a totally random but deterministic way. Any operator with a matching Enigma machine and matching settings could replicate this "random walk" through the key space.

What we will do below is look at each component of the Enigma and determine the total number of unique settings for each component. A single machine setting corresponds to a single key, so the total number of possible settings of the machine yields the total number of possible keys for the Enigma.

The Switchboard

The switchboard at the front of the Enigma consisted of a set of plugs, one for each letter, connected by wires. The operator would connect pairs of wires to swap pairs of letters. If the letters A and K were connected, any A signal entering the keyboard would become a K signal leaving the keyboard, and any K signal entering the keyboard would become an A signal leaving the keyboard. Letters could not be connected to themselves, and a wire could only connect two letters together.

From these constraints, we can get the total number of cable configurations on the front of the machine. For a machine with \(S\) symbols (typically 26) and \(N\) patch cables, the total number of configurations is:

$$ C_{sw} = \dfrac{ S! }{ N! \times (S - 2N)! \times 2^N } $$

Let's break down where those terms are coming from.

One Cable

Let's consider a single cable connecting two letters.

There are S (or, 26) places to plug in the left end, and S places to plug in the right end, for a total of \(S^2\) combinations. But no cable can connect to itself, so there are actually \(S (S-1)\) possible combinations. Furthermore, each plug is symmetric (if A connects to B, then B connects to A), so half of the plug combinations are simply mirror images of the other half.

For a single plug, we start from a total of \(26 \times 26 = 676\) possible configurations. Ruling out any combinations that connect letters to themselves eliminates 26 possibilities (A connects to A, B connects to B, etc.) for a total of \(26 \times 25 = 650\) possible configurations. But half of those configurations are mirror images of the other half (if we connect A to B, by implication we connect B to A), so our number of choices is actually half that, or \(\frac{26 \times 25}{2} = 350\).

More Cables

If we plug in a second cable, there are now 2 choices occupied by the first letter, so there are \(S-2\) possible places to plug in the left end, and \(S-3\) possible places to plug in the right end, but each having half duplicate solutions, since the wires are two-way, for a total of \(\frac{(S-2)(S-3)}{2 \times 2}\) combinations.

Many Cables

Once \(N\) wires have been plugged in, there are \(S - 2N\) spaces remaining, and \(2N\) spaces occupied by plug ends. That is, we are reducing the number of choices by 2 letters with each wire placed.

Taking the product of these numbers explains part of the expression given above:

$$ S \times (S-1) \times (S-2) \times \dots \times (S - 2N + 1) = \dfrac{ S! }{ (S - 2N) ! } $$

Accounting for Duplicates

But where did the \(2^N\) and \(N!\) terms come from? They come from the fact that many choices of wiring configurations are duplicates.

Dividing by \(2^N\) comes from the fact that the wires are doubled up: if A connects to B, B connects to A. This means that when we choose our pair and connect A to B using a wire, we also connect B to A. Even though it looks like two choices, it is only one!

Meanwhile, the \(N!\) term accounts for the fact that order is not important when we select pairs and place wires - making the choice to connect A to B and then making the choice to connect C to D is entirely equivalent to connecting C to D, then connecting A to B. This means that \(N!\) of the \(S!\) possible solutions are duplicate configurations with the same connections chosen in a different order.

Switchboard Combinations

Here's how the number of possible combinations that result when we plug in various numbers of wires in:

S = 2       N = 1       C = 1
S = 26      N = 1       C = 325
S = 26      N = 2       C = 44,850
S = 26      N = 3       C = 3,453,450
S = 26      N = 4       C = 164,038,875
S = 26      N = 5       C = 5,019,589,575
S = 26      N = 6       C = 100,391,791,500
S = 26      N = 7       C = 1,305,093,289,500
S = 26      N = 8       C = 10,767,019,638,375
S = 26      N = 9       C = 53,835,098,191,875
S = 26      N = 10      C = 150,738,274,937,250
S = 26      N = 11      C = 205,552,193,096,250
S = 26      N = 12      C = 102,776,096,548,125
S = 26      N = 13      C = 7,905,853,580,625

Notice the bump in the shape of the distribution, meaning the use of 11 wires is much more secure than the use of 13 wires. Let's explore that.

More Interesting Observations About the Switchboard

Notice how the switchboard expression is not proportional to \(N\), it is inversely proportional to two different terms, each changing differently as \(N\) changes.

We are looking at the denominator of this expression:

$$ C_{sw} = \dfrac{ S! }{ N! \times (S - 2N)! \times 2^N } $$

The term \(N!\) on the bottom will increase as \(N\) increases, thereby decreasing the total number of possible keys \(C_{sw}\). However, the term \((S - 2N)!\) will decrease as \(N\) increases, thereby increasing the total number of possible keys \(C_{sw}\). The tradeoff can be visualized just by printing it out - here are the total number of combinations that are possible for an alphabet of \(S = 26\) symbols, using \(N = 1 \dots 13\) wires:

This pattern holds for other alphabet sizes. Here's a 52-character alphabet:

S = 52      N = 1       C = 1,326
S = 52      N = 2       C = 812,175
S = 52      N = 3       C = 305,377,800
S = 52      N = 4       C = 79,016,505,750
S = 52      N = 5       C = 14,949,922,887,900
S = 52      N = 6       C = 2,145,313,934,413,650
S = 52      N = 7       C = 239,049,266,977,521,000
S = 52      N = 8       C = 21,006,454,335,649,657,875
S = 52      N = 9       C = 1,470,451,803,495,476,051,250
S = 52      N = 10      C = 82,492,346,176,096,206,475,125
S = 52      N = 11      C = 3,719,654,882,122,156,219,242,000
S = 52      N = 12      C = 134,837,489,476,928,162,947,522,500
S = 52      N = 13      C = 3,920,659,309,406,065,045,704,885,000
S = 52      N = 14      C = 91,015,305,396,926,509,989,577,687,500
S = 52      N = 15      C = 1,674,681,619,303,447,783,808,229,450,000
S = 52      N = 16      C = 24,178,215,878,693,527,378,731,312,684,375
S = 52      N = 17      C = 270,227,118,644,221,776,585,820,553,531,250
S = 52      N = 18      C = 2,296,930,508,475,885,100,979,474,705,015,625
S = 52      N = 19      C = 14,506,929,527,216,116,427,238,787,610,625,000
S = 52      N = 20      C = 66,006,529,348,833,329,743,936,483,628,343,750
S = 52      N = 21      C = 207,449,092,239,190,464,909,514,662,831,937,500
S = 52      N = 22      C = 424,327,688,671,071,405,496,734,537,610,781,250
S = 52      N = 23      C = 516,572,838,382,173,884,952,546,393,613,125,000
S = 52      N = 24      C = 322,858,023,988,858,678,095,341,496,008,203,125
S = 52      N = 25      C = 77,485,925,757,326,082,742,881,959,041,968,750
S = 52      N = 26      C = 2,980,227,913,743,310,874,726,229,193,921,875

For a 52-symbol alphabet the optimum number of pairs is 23 keys.

Okay, here we go with an alphabet of 100 characters:

S = 100     N = 1       C = 4,950
S = 100     N = 2       C = 11,763,675
S = 100     N = 3       C = 17,880,786,000
S = 100     N = 4       C = 19,539,228,901,500
S = 100     N = 5       C = 16,358,242,436,335,800
S = 100     N = 6       C = 10,919,126,826,254,146,500
S = 100     N = 7       C = 5,971,202,498,700,124,686,000
S = 100     N = 8       C = 2,728,093,141,593,619,465,916,250
S = 100     N = 9       C = 1,056,681,410,177,261,939,798,227,500
S = 100     N = 10      C = 350,923,896,319,868,690,206,991,352,750
S = 100     N = 11      C = 100,810,864,760,980,460,095,826,606,790,000
S = 100     N = 12      C = 25,227,918,906,435,360,138,980,608,349,197,500
S = 100     N = 13      C = 5,530,736,067,949,290,492,007,287,215,016,375,000
S = 100     N = 14      C = 1,067,037,008,537,930,972,779,405,911,982,802,062,500
S = 100     N = 15      C = 181,823,106,254,863,437,761,610,767,401,869,471,450,000
S = 100     N = 16      C = 27,443,925,100,343,450,137,143,125,204,719,673,346,984,375
S = 100     N = 17      C = 3,677,485,963,446,022,318,377,178,777,432,436,228,495,906,250
S = 100     N = 18      C = 438,233,743,977,317,659,606,613,804,310,698,650,562,428,828,125
S = 100     N = 19      C = 46,498,906,729,382,757,987,733,338,394,229,919,975,466,132,500,000
S = 100     N = 20      C = 4,396,471,631,263,139,767,740,187,145,174,438,933,680,322,827,875,000
S = 100     N = 21      C = 370,559,751,777,893,208,995,244,345,093,274,138,695,912,924,063,750,000
S = 100     N = 22      C = 27,842,512,258,584,430,657,688,131,929,053,734,148,379,275,612,608,125,000
S = 100     N = 23      C = 1,864,237,777,313,914,052,732,161,876,988,815,242,978,438,454,061,587,500,000
S = 100     N = 24      C = 111,155,177,472,342,125,394,155,151,915,458,108,862,589,392,823,422,154,687,500
S = 100     N = 25      C = 5,895,670,613,133,026,330,905,989,257,595,898,094,071,741,395,354,311,084,625,000
S = 100     N = 26      C = 277,776,788,503,382,971,359,993,724,636,729,814,047,610,892,665,731,964,564,062,500
S = 100     N = 27      C = 11,604,896,941,919,110,803,484,182,273,712,267,786,877,966,182,479,468,741,787,500,000
S = 100     N = 28      C = 428,966,726,245,938,560,057,361,737,617,578,469,979,239,107,102,366,076,705,359,375,000
S = 100     N = 29      C = 13,993,190,449,264,064,752,216,007,027,111,352,848,288,282,597,201,320,984,940,343,750,000
S = 100     N = 30      C = 401,604,565,893,878,658,388,599,401,678,095,826,745,873,710,539,677,912,267,787,865,625,000
S = 100     N = 31      C = 10,104,889,077,329,850,114,293,791,397,061,765,963,283,274,007,127,379,728,028,210,812,500,000
S = 100     N = 32      C = 221,991,781,917,590,144,698,391,729,754,200,671,005,879,425,844,079,623,400,119,756,287,109,375
S = 100     N = 33      C = 4,238,024,927,517,630,035,151,114,840,762,012,810,112,243,584,296,065,537,638,649,892,753,906,250
S = 100     N = 34      C = 69,927,411,304,040,895,579,993,394,872,573,211,366,852,019,140,885,081,371,037,723,230,439,453,125
S = 100     N = 35      C = 990,971,314,480,122,405,933,620,681,622,751,795,370,245,756,967,971,438,858,134,592,065,656,250,000
S = 100     N = 36      C = 11,974,236,716,634,812,405,031,249,902,941,584,194,057,136,230,029,654,886,202,459,654,126,679,687,500
S = 100     N = 37      C = 122,331,391,321,296,191,597,346,282,792,214,022,306,853,986,350,032,690,459,041,344,574,591,484,375,000
S = 100     N = 38      C = 1,046,255,320,511,085,849,187,830,050,196,567,296,045,461,725,362,121,694,715,485,183,861,637,695,312,500
S = 100     N = 39      C = 7,404,268,422,078,453,701,944,643,432,160,322,402,783,267,594,870,399,685,678,818,224,251,589,843,750,000
S = 100     N = 40      C = 42,759,650,137,503,070,128,730,315,820,725,861,876,073,370,360,376,558,184,795,175,245,052,931,347,656,250
S = 100     N = 41      C = 198,154,476,246,965,446,938,018,536,730,193,018,450,096,106,548,086,489,149,050,812,111,220,901,367,187,500
S = 100     N = 42      C = 721,848,449,185,374,128,131,353,240,945,703,138,639,635,816,710,886,496,185,827,958,405,161,854,980,468,750
S = 100     N = 43      C = 2,014,460,788,424,299,892,459,590,439,848,473,875,273,402,279,193,171,617,262,775,697,874,870,292,968,750,000
S = 100     N = 44      C = 4,166,271,176,059,347,504,859,607,500,595,707,332,951,809,259,240,423,117,520,740,647,877,572,651,367,187,500
S = 100     N = 45      C = 6,110,531,058,220,376,340,460,757,667,540,370,754,995,986,913,552,620,572,363,752,950,220,439,888,671,875,000
S = 100     N = 46      C = 5,977,693,426,519,933,376,537,697,718,246,014,869,017,813,284,997,128,820,790,627,886,085,212,934,570,312,500
S = 100     N = 47      C = 3,561,179,062,607,619,883,894,798,640,657,200,347,499,973,871,913,183,127,279,522,995,965,658,769,531,250,000
S = 100     N = 48      C = 1,112,868,457,064,881,213,717,124,575,205,375,108,593,741,834,972,869,727,274,850,936,239,268,365,478,515,625
S = 100     N = 49      C = 136,269,606,987,536,475,149,035,662,270,045,931,664,539,816,527,290,170,686,716,441,172,155,310,058,593,750
S = 100     N = 50      C = 2,725,392,139,750,729,502,980,713,245,400,918,633,290,796,330,545,803,413,734,328,823,443,106,201,171,875

Optimum number of pairs? 45.

Final Combination Count Switchboard

For a switchboard with holes for each of \(S\) symbols, with \(N\) unique pairs of letters chosen from among the symbols to be swapped by the switchboard, the number of possible combinations of ciphers with \(N\) wires is:

$$ C_{sw} = \dfrac{ S! }{ N! \times (S - 2N)! \times 2^N } $$

The Rotors

Typical Enigma machines had three rotors, with each rotor implementing a different scrambled alphabet. Assuming there are P possible rotors to choose from, the number of choices when selecting R rotors from P possible rotors is given by:

$$ C_{rot} = \dfrac{P!}{(P-R)!} $$

If the rotors are known, \(P\) and \(R\) are small numbers like 8 and 3, yielding a modest number of possible rotor combinations (336). If the number of rotors is unknown, however, P becomes the set of all possible rotors (the set of all possible alphabet scrambles), which is S!. Then we take the factorial of this number,

$$ C_{rot} = \frac{(S!)!}{(S!-R)!} $$

Note that the numerator \((S!)!\) is a double factorial. Here's how Wolfram Alpha describes 26 double-factorial (26!)!

$$ 10^{10^{28}} $$

This can also be written as \(403291461126605635584000000!\). This is a number with 10^28 digits. That's probably the biggest number you've ever seen in your life. The denominator is also pretty big, though. For small values of R, this is approximately \((S!)^R\). For a 26-character alphabet with 3 rotors, that is approximately

$$ (403,291,461,126,605,635,584,000,000)^3 $$

or

$$ 65,592,937,459,144,468,297,405,473,968,303,761,468,794,234,820,105,359,750,856,704,000,000,000,000,000,000 $$

which is a key space with more keys than there are protons in the universe (the Eddington number).

In addition, each wheel had notches at different locations. The notches change the path the Enigma takes through the key space. For \(R\) rotors containing \(S\) symbols, the total combinations increases by a factor of \({S}^{R-1}\). If each wheel has \(M\) notches, that factor is \({(MS)}^{R-1}\). (The \(R-1\) comes from the fact that the location of the notch on the last wheel has no effect.)

Final Combination Count for Rotors

The total number of combinations for \(R\) rotors with \(S\) symbols and \(M\) notches (which advance the neighboring left wheel by one), chosen from among \(P\) possible choices of rotors, is given by:

$$ C_{rot} = S^{R-1} \dfrac{P!}{(P-R)!} $$

NOTE: For a very large set of possible rotors \(P\), (\(P >> R\)),

$$ \dfrac{P!}{(P-R)!} \approx P^R $$

so it follows that

$$ C_{rot} \approx S^{R-1} P^R \qquad P >> R $$

(For example, if \(P = S!\), the set of all possible rotors.)

Reflector

Like the switchboard on the front of the Enigma, the reflector connected pairs of letters. It could only be changed by swapping it out like a rotor, so there were a small number of mechanically produced reflectors chosen from the set of all possible reflectors.

If a reflector pairs all letters with another letter, it makes \(N = \frac{S}{2}\) possible pairs. Using the analysis we performed above for the switchboard, and plugging that in, and using \(0!=1\):

$$ C_{rfl} = \dfrac{ S! }{ (\frac{S}{2})! \times (S - 2(\frac{S}{2}))! \times 2^S } = \dfrac{S!}{(\frac{S}{2})! 2^S} $$

For the 26 characters in the English alphabet, that's:

$$ C_{rfl} = \frac{26!}{13! * 2^26 } = 965,070,017 $$

This is the total number of possible reflectors. (Curiously enough, the number of possible keys goes down as N goes from 11 to 12 and 12 to 13, making the switchboard on the front, which swapped 10 pairs of letters, more secure than the rotor, which swapped 13 pairs of letters.

However, like the rotors, there were a finite number of reflectors in use. Supposing there were Q reflectors in use, that would make for Q possible sets of the 13 letter pairings. Since the reflectors did not rotate, this would lead to only 1 possible reflector position, meaning a choice from among Q reflectors only multiplied the number of possible combinations by Q.

Final Combination Count for Reflector

Here is the final expression for the total number of combinations resulting from all possible rotors:

$$ C_{rfl} = \dfrac{S!}{(\frac{S}{2})! 2^S} $$

and here is the final expression for the total number of combinations if there is 1 rotor chosen from among \(Q\) rotors:

$$ C_{rfl} = Q $$

Final Enigma Combination Count

Putting all of this together results in the following monstrosity of an expression for the Enigma's complete key space:

$$ C_{enigma-full} = \left( \dfrac{S!}{N! (S-2N)! 2^N} \right) \left( \dfrac{(S!)!}{(S!-R)!} \right) \left( \dfrac{ S! }{ \left( \dfrac{S}{2} \right)! 2^S } \right) $$

Note that this assumes the attacker has no idea which rotors or reflectors are actually used. If instead the attacker has knowledge that \(R\) rotors out of \(P\) possible rotors and 1 reflector out of \(Q\) possible reflectors are being used, this key space reduces to:

$$ C_{enigma-small} = \left( \dfrac{S!}{N! (S-2N)! 2^N} \right) \left( S^{R-1} \dfrac{P!}{(P-R)!} \right) Q $$

When you evaluate the above expressions for the following values of \(N\), \(P\), \(Q\), \(R\), and \(S\), here are the actual numbers you get:

N = 10; // number of switchboard wires
P = 5;  // number of possible rotors
Q = 5;  // number of possible reflectors
R = 3;  // number of rotors
S = 26; // number of symbols

For the case of utilizing the \(P\) known rotors and \(Q\) known reflectors, the key space is:

$$ C_{enigma-small} = 537,293,436,636,253,096,800,000 $$

For the completely unknown case of all possible rotors and reflectors, the key space increases to an astronomical number:

$$ C_{enigma-full} = 422,732,921,460,335,478,939,047,043,039,799,222,455,533,136,281,221,092,624,796,865,514,111,348,059,884,989,972,480,000,000,000,000,000,000,000,000 $$

There are plenty of additional big numbers related to the Enigma, and more math around the weaknesses in the system and how it was cracked. There is also yet more math around how Alan Turing managed to crack the German Navy's Enigma cipher, which utilized various protective steps like bigram replacement that made cracking via frequency analysis much more difficult.

But that's enough for one post!

Below you can find a Java program that uses the BigInteger class, part of the Java API, to perform calculations with extremely large numbers.

Java BigInteger Program

import java.math.BigInteger;
import java.text.*;

/** Cryptanalysis of the Enigma Machine.
 *
 * This program uses combinatorics and big integers
 * to analyze the cryptographic strength of the Enigma machine.
 *
 * Author: Charles Reid
 * Date: March 2017
 */

public class Combos {
    public static void main(String[] args) { 

        // This involves a double factorial. PREPARE YOUR CPU
        boolean doBig = true;



        /////////////////////////////////
        // Git Ready

        DecimalFormat formatter = new DecimalFormat("#,###");

        BigInteger small_combos = new BigInteger("1");
        BigInteger big_combos = new BigInteger("1");

        /// Useful temp variable 
        BigInteger next;



        /////////////////////////////////
        // Constants

        int N = 10; // number of switchboard wires
        int P = 5;  // number of possible rotors
        int Q = 5;  // number of possible reflectors
        int R = 3;  // number of rotors
        int S = 26; // number of symbols




        /////////////////////////////////
        // Plugboard combinations

        BigInteger plugboard = new BigInteger("1");

        // S!/(S-2N)!
        BigInteger num = BigInteger.valueOf(1);
        for(int i=S; i>(S-2*N); i--) {
            next = BigInteger.valueOf(i);
            num = num.multiply(next);
        }

        // divided by 2^N
        BigInteger pdenom = BigInteger.valueOf(2);
        pdenom = pdenom.pow(N);

        // divided by N!
        for(int j = N; j>1; j--) { 
            next = BigInteger.valueOf(j);
            pdenom = pdenom.multiply(next);
        }

        plugboard = num.divide(pdenom);



        /////////////////////////////////
        // Rotor combinations

        BigInteger small_rotors = new BigInteger("1");
        BigInteger big_rotors = new BigInteger("1");

        // -----------------
        // Small case:
        // R rotors selected from P possible known rotors

        // Rotor wheel combinations
        for(int j=P; j>(P-R); j--) { 
            next = BigInteger.valueOf(j);
            small_rotors = small_rotors.multiply(next);
        }

        // Rotor wheel notch positions (assume 1 per wheel). 
        // Ignore left-most wheel.
        for(int k=0; k<(R-1); k++ ) {
            next = BigInteger.valueOf(S);
            small_rotors = small_rotors.multiply(next);
        }

        // Rotor wheel starting positions
        for(int k=0; k<R; k++ ) {
            next = BigInteger.valueOf(S);
            small_rotors = small_rotors.multiply(next);
        }

        // -----------------
        // Big case:
        // R rotors selected from S! possible rotors

        if(doBig) { 
            BigInteger s_rm1 = BigInteger.valueOf(S).pow(R-1);
            BigInteger sfact_r = BigInteger.valueOf(1);
            for(int j=S; j>=1; j--) { 
                next = BigInteger.valueOf(j);
                sfact_r = sfact_r.multiply(next);
            }
            sfact_r = sfact_r.pow(R);
            big_rotors = s_rm1.multiply(sfact_r);
        }

        /////////////////////////////////
        // Reflector combinations

        BigInteger small_reflector = BigInteger.valueOf(Q);
        BigInteger big_reflector = new BigInteger("1");

        // (S!)/((S/2)!)
        for(int j=S; j>(S/2); j--) {
            next = BigInteger.valueOf(j);
            big_reflector = big_reflector.multiply(next);
        }

        // divided by 2^N
        BigInteger rfldenom = BigInteger.valueOf(2);
        rfldenom = rfldenom.pow(N);

        if(doBig) { 
            big_reflector = big_reflector.divide(rfldenom);
        }



        /////////////////////////////////
        // Final combinations

        small_combos = small_combos.multiply(plugboard);
        small_combos = small_combos.multiply(small_rotors);
        small_combos = small_combos.multiply(small_reflector);

        if(doBig) {
            big_combos = big_combos.multiply(plugboard);
            big_combos = big_combos.multiply(big_rotors);
            big_combos = big_combos.multiply(big_reflector);
        }

        System.out.println("Final number of (small) Enigma combinations: ");
        System.out.println(formatter.format(small_combos));

        if(doBig) { 
            System.out.println("Final number of (big) Enigma combinations: ");
            System.out.println(formatter.format(big_combos));
        }

    }
}

Output of the program:

Final number of (small) Enigma combinations:
537,293,436,636,253,096,800,000
Final number of (big) Enigma combinations:
422,732,921,460,335,478,939,047,043,039,799,222,455,533,136,281,221,092,624,796,865,514,111,348,059,884,989,972,480,000,000,000,000,000,000,000,000

Sources

  1. "The Enigma Cipher". Tony Sale and Andrew Hodges. Publication date unknown. Accessed 18 March 2017. <https://web.archive.org/web/20170320081639/http://www.codesandciphers.org.uk/enigma/index.htm>

  2. "BigInteger". Oracle Corporation. Copyright 1993-2016, Publication date unknown. Accessed 22 March 2017. <https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html>

Tags:    ciphers    enigma    encryption    java   

Enigma Cipher Implementation: Part 3: Enigma in Java Without Objects

Posted in Enigma

permalink

As the title suggests, we're continuing with the third in a series of posts exploring a verb-oriented approach to programming - in an attempt to free ourselves from the fetishization of objects, we are attempting to learn how to use languages against their will.

This is all inspired by Steve Yegge's 2006 blog post, "Execution in the Kingdom of Nouns," an excellent read that inspired me to explore the subject more deeply.

Java Pseudocode

In the last post, we ran through the pseudocode for an Enigma machine based entirely upon Strings, iterators, and integer indexes, leading to a vastly simpler abstraction of the Enigma machine than would have resulted if we had implicitly chosen a noun-centric approach, divided the entire Enigma encryption process into its component nouns like rotor wheels and reflectors, and implemented each as an object.

Here was the pseudocode:

define plaintext message
define normal alphabet and scrambled alphabets
define list of switchboard swap pairs
define list of reflector swap pairs
for each character in plaintext message:

    # Apply switchboard transformation
    for each pair in switchboard swap pairs:
        if character in swap pair, swap its value

    # Apply forward rotor transformation
    for each scrambled alphabet:
        get index of character in normal alphabet
        get new character at that index in scrambled alphabet
        replace character with new character 

    # Apply reflector transformation
    for each pair in reflector swap pairs:
        if character in swap pair, swap its value

    # Apply reverse rotor transformation
    for each scrambled alphabet:
        get index of input character in scrambled alphabet
        get new character at that index in normal alphabet
        replace character with new character 

    # Apply switchboard transformation
    for each pair in switchboard swap pairs:
        if character in swap pair, swap its value

    concatenate transformed input character to ciphertext message 

    # Increment rotor wheels
    for each rotor/scrambled alphabet, left to right:
        get index of left notch in left alphabet
        get index of right notch in right alphabet
        if left index equals right index:
            cycle left alphabet forward 1 character
    cycle right-most alphabet forward 1 character

Java Code

The Enigma code is defined in the Java program as follows:

  • The Enigma class defines a set of constants for historically accurate cipher settings.
  • The main method contains the encryption procedure.
  • There is one static helper method called rotateString.

Everything is in a public class. Starting with the definitions of constants:

public class Enigma {

    public static final String ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    // Historically accurate rotor scrambles
    // See http://www.codesandciphers.org.uk/enigma/rotorspec.htm
    public static final String[] WHEEL = { ALPHA,
                        "EKMFLGDQVZNTOWYHXUSPAIBRCJ", // Rotor I    - Royal
                        "AJDKSIRUXBLHWTMCQGZNPYFVOE", // Rotor II   - Flags
                        "BDFHJLCPRTXVZNYEIWGAKMUSQO", // Rotor III  - Wave
                        "ESOVPZJAYQUIRHXLNFTGKDCMWB", // Rotor IV   - Kings
                        "VZBRGITYUPSDNHLXAWMJQOFECK", // Rotor V    - Above
                        "JPGVOUMFYQBENHZRDKASXLICTW",
                        "NZJHGRCXMYSWBOUFAIVLPEKQDT",
                        "FKQHTLXOCBJSPDZRAMEWNIUYGV"};

    // Knocking (notch and clasp) advances the wheel to the left
    public static final String[] NOTCH = {"",  // No notch
                                          "R", // Royal
                                          "F", // Flags
                                          "W", // Wave
                                          "K", // Kings
                                          "A", // Above
                                          "AN",
                                          "AN",
                                          "AN"};

    // Reflectors
    public static final String REFLECTOR_ALPHA = "AY:BR:CU:DH:EQ:FS:GL:IP:JX:KN:MO:TZ:VW";

Main Method: User Settings

The next part of the code is the main method, where we begin by defining variables that correspond to settings that the Enigma operator would set from the daily Enigma code book. These included:

  • The numbering and ordering of wheels (e.g., IV II I)
  • The initial rotor settings for each wheel (position 0-25)
  • The pairs of letters connected on the switch board

The wheels are specified using the WHEEL array of Strings, above. Each element of the WHEEL array contains a different scrambled alphabet, corresponding to the alphabet scrambles hard-coded into the historical rotor wheels. These go into rotorAlpha, which stores each rotor's alphabet in a String.

The locations of the notches that advance neighboring wheels are fixed by the choice of wheels, and are available through the NOTCH array. The notch locations implemented in NOTCH are historically accurate for each rotor wheel.

The initial rotor settings were also contained in the code book as a sequence of 3 numbers, each 0-25, indicating how many turns each wheel was rotated before starting.

The plugboard pairs specify the wired connections on the front of the machine. These plugboard pairs were also daily machine settings specified in the daily Enigma code book. The plugboard pairs are input as a single string, with pairs of letters separated by a ":", like this: AB:CD:EF:GH. Pairs must be unique (no letter can connect to itself). Letters cannot be repeated (no letter can connect to more than 1 other letter).

    public static void main(String[] args) { 

        // These two strings should encrypt/decrypt to each other when you run them through the Enigma.
        //String message = "ABCDE FG HIJKL MNOP QRS TUVWXYZ"; 
        String message = "TVVFT KS UNVYJ FAFV NPC DZJPWEJ";


        //////////////////////////////////
        // Operator Settings
        // 
        // Enigma operators have code sheets that specify: 
        //  - The numbering/ordering of wheels 
        //  - The initial rotor settings
        //  - The plugboard pairs

        // Rotor scrambles are applied right-to-left
        //              {LAST, MIDDLE, FIRST}
        String[] rotorAlpha = {WHEEL[1],WHEEL[2],WHEEL[3]};
        String[] rotorNotch = {NOTCH[1],NOTCH[2],NOTCH[3]};
        int[] rotorInit = {0,0,0};

        String plugboardPairs = "IR:HQ:NT:WZ:VC:OY:GP:LF:BX:AK";

        String coded = enigma(message, rotorAlpha, rotorNotch, rotorInit, plugboardPairs);
        System.out.println(coded);
    }

Cipher Procedure

The next bit of code is the meat of the Enigma method. Notice that this is purely procedural code, and makes no use of objects other than the built-in String type. This is the kind of verb-oriented code we are striving for when we write noun-free Java code.

We also pass in any information that's required. Normally we would wrap all of these quantities in an object, to keep the list of parameters short, but this implementation is entirely object-free.

    public static String enigma(String message,
                                String[] rotorAlpha,
                                String[] rotorNotch,
                                int[] rotorInit,
                                String[] plugboardPairs) { 

        StringBuilder message_final = new StringBuilder();

        //////////////////////////////////////
        // Enigma Cipher

        // Apply each transformation in sequence
        for(int i=0; i<message.length(); i++) {

            // Starting char
            char c_orig = message.charAt(i);
            char c = c_orig;

            // Perform plugboard swap
            for(String pair : plugboardPairs.split(":")) {
                if(c==pair.charAt(0)) {
                    c = pair.charAt(1);
                } else if(c==pair.charAt(1)) {
                    c = pair.charAt(0);
                }
            }

            // Perform rotor letter substitutions
            // (forward order: right-to-left)
            int ix = -100;
            for(int j=(rotorAlpha.length-1); j>=0; j--) { 
                ix = ALPHA.indexOf(c);
                String thisAlpha = rotorAlpha[j];
                if(ix>=0) { 
                    c = thisAlpha.charAt(ix);
                } else {
                    c = c_orig;
                }
            }

            // Perform reflection
            for(String pair : REFLECTOR_ALPHA.split(":")) {
                if(c==pair.charAt(0)) {
                    c = pair.charAt(1);
                } else if(c==pair.charAt(1)) {
                    c = pair.charAt(0);
                }
            }

            // Perform rotor letter substitutions
            // (backwards order: left-to-right) 
            ix = -100;
            for(int j=0; j<rotorAlpha.length; j++) { 
                String thisAlpha = rotorAlpha[j];
                ix = thisAlpha.indexOf(c);
                if(ix>=0) { 
                    c = ALPHA.charAt(ix);
                } else {
                    c = c_orig;
                }
            }


            // Perform plugboard swap
            for(String pair : plugboardPairs.split(":")) {
                if(c==pair.charAt(0)) {
                    c = pair.charAt(1);
                } else if(c==pair.charAt(1)) {
                    c = pair.charAt(0);
                }
            }


            // Final text
            if( c>='A' && c<='Z') { 
                message_final.append(c);
            } else {
                // Could not resolve 
                message_final.append(c_orig);
            }

            // Increment rotors
            for(int j=0; j<(rotorAlpha.length-1); j++) {
                String alphaL = rotorAlpha[j];
                int ixL = alphaL.indexOf(rotorNotch[j]);
                String alphaR = rotorAlpha[j+1];
                int ixR = alphaR.indexOf(rotorNotch[j+1]);
                if(ixL!=ixR) { 
                    rotorAlpha[j] = rotateString(rotorAlpha[j]);
                }
            }
            // Always increment the right-most rotor
            int lenny = rotorAlpha.length;
            rotorAlpha[lenny-1] = rotateString(rotorAlpha[lenny-1]);
        }

        return message_final.toString();
    }

Utility Method: String Rotator

One last piece that's needed to emulate the rotation of the rotor wheels is a method to rotate strings forward 1 character. Here's that method:

    /// Rotate a string forward by 1 character, so "ABCDEF" becomes "FABCDE"
    public static String rotateString(String original) {
        int lenny = original.length();
        StringBuilder rotated = new StringBuilder();
        rotated.append(original.charAt(lenny-1));
        for(int i=0;i<lenny-1;i++) { 
            rotated.append(original.charAt(i));
        }
        return rotated.toString();
    }

} // end Enigma class

Complete Enigma Implementation

Here is a link to the complete Enigma code on git.charlesreid1.com: Enigma.java

Now that the Enigma machine implementation is finished, we can test it out. One feature of the Enigma that makes it easy to test is, it is symmetric. If we feed a plain text into the Enigma and get the corresponding ciphertext, we can feed that ciphertext through the Enigma (with the same initial settings) and recover the original plain text.

Running the alphabet through the Enigma yields:

$ java Enigma
IN:  ABCDE FG HIJKL MNOP QRS TUVWXYZ
OUT: TVVFT KS UNVYJ FAFV NPC DZJPWEJ

Running this back through the Enigma yields:

$ java Enigma
IN:  TVVFT KS UNVYJ FAFV NPC DZJPWEJ
OUT: ABCDE FG HIJKL MNOP QRS TUVWXYZ

NOTE: This code modifies the Enigma machine's settings in-place. This means multiple sequential calls to the enigma() method will not reset the rotors. The following code will not recover the original plain text message:

// This won't work:
String coded = enigma(message, rotorAlpha, rotorNotch, rotorInit, plugboardPairs);
String original2 = enigma(coded, rotorAlpha, rotorNotch, rotorInit, plugboardPairs);

To do this correctly, we would need multiple copies of the input arrays rotorAlpha, rotorNotch, and rotorInit:

String plugboardPairs = "IR:HQ:NT:WZ:VC:OY:GP:LF:BX:AK";

String[] rotorAlpha = {WHEEL[1],WHEEL[2],WHEEL[3]};
String[] rotorNotch = {NOTCH[1],NOTCH[2],NOTCH[3]};
int[] rotorInit = {0,0,0};
String coded = enigma(message, rotorAlpha, rotorNotch, rotorInit, plugboardPairs);

String[] rotorAlpha2 = {WHEEL[1],WHEEL[2],WHEEL[3]};
String[] rotorNotch2 = {NOTCH[1],NOTCH[2],NOTCH[3]};
int[] rotorInit2 = {0,0,0};
String original = enigma(coded, rotorAlpha2, rotorNotch2, rotorInit2, plugboardPairs);

System.out.println("ORIGINAL:  "+message);
System.out.println("RECOVERED: "+original);

This works fine:

ORIGINAL:  ABCDE FG HIJKL MNOP QRS TUVWXYZ
RECOVERED: ABCDE FG HIJKL MNOP QRS TUVWXYZ 

Sources

  1. "Execution in the Kingdom of Nouns". Steve Yegge. March 2006. Accessed 18 March 2017. <https://web.archive.org/web/20170320081755/https://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html>

  2. "The Enigma Cipher". Tony Sale and Andrew Hodges. Publication date unknown. Accessed 18 March 2017. <https://web.archive.org/web/20170320081639/http://www.codesandciphers.org.uk/enigma/index.htm>

Tags:    ciphers    enigma    encryption    java   

March 2022

How to Read Ulysses

July 2020

Applied Gitflow

September 2019

Mocking AWS in Unit Tests

May 2018

Current Projects

November 2017

A Hard(y) Math Problem