12
\$\begingroup\$

This is an Enigma Machine Simulator written in Java:

Enigma.java

package enigma;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import enigma.rotor.LargeRotor;
import enigma.rotor.MedRotor;
import enigma.rotor.SmallRotor;

public class Enigma extends Frame {
    String FileExtension = ".enigma";

    public static void main (String[] args)  {
    screen = new Enigma();
    screen.show();
    }

    public static final int FrameWidth = 660;

    public static final int FrameHeight = 400;

    public static Enigma screen;

    private final Insets ins;


    private SmallRotor smrotor = new SmallRotor();
    private MedRotor medrotor = new MedRotor();
    private LargeRotor lgrotor = new LargeRotor();

    protected TextArea Message = new TextArea();
    protected TextArea Encrypted = new TextArea();

    protected TextField messFN = new TextField();
    protected TextField encFN = new TextField();

    protected Label FNLabel = new Label("Enter a FileName",Label.CENTER );
    protected Label FNLabel2 = new Label("Enter a FileName",Label.CENTER);

    public Enigma() {
        this.FileExtension = ".enigma";
    setTitle ("Enigma Simulator");
    setSize (FrameWidth, FrameHeight);
    setResizable(false);

    setLayout(new FlowLayout());

    add(Message);
    Panel p = new Panel();
    p.setLayout(new GridLayout(5,1));
    p.add(new ButtonAdapter("Encrypt"){@Override
        public void pressed(){Encrypt();}});
    p.add(FNLabel);
    p.add(messFN);
    p.add(new ButtonAdapter("Load"){@Override
        public void pressed(){LoadMessage(messFN.getText());}});
    p.add(new ButtonAdapter("Save"){@Override
        public void pressed(){SaveMessage(messFN.getText());}});

    add(p);

    add(Encrypted);
    Panel p2 = new Panel();
    p2.setLayout(new GridLayout(5,1));
    p2.add(new ButtonAdapter("Decrypt"){@Override
        public void pressed(){Decrypt();}});
    p2.add(FNLabel2);
    p2.add(encFN);
    p2.add(new ButtonAdapter("Load"){@Override
        public void pressed(){LoadCypherTxt(encFN.getText());}});
    p2.add(new ButtonAdapter("Save"){@Override
        public void pressed(){SaveCypherTxt(encFN.getText());}});

    add(p2);

    addMouseListener (new MouseKeeper());
    addWindowListener (new WindowKeeper());

    ins = getInsets();

    }



    public void Encrypt()
    {
    System.out.println("Encrypting");
    String plain = Message.getText();
    plain = plain.toUpperCase();
    char [] cypher = new char[10000];

    for(int i = 0; i < plain.length();i++)
        {
        cypher[i] = EncryptChar(plain.charAt(i));

        }

    Encrypted.setText(String.copyValueOf(cypher));

    smrotor = new SmallRotor();
    medrotor = new MedRotor();
    lgrotor = new LargeRotor();

    }

    public void Decrypt()
    {
    System.out.println("Decrypting");
    String cypher = Encrypted.getText();
    cypher = cypher.toUpperCase();
    char [] plaintxt = new char[10000];

    for(int i = 0; i < cypher.length();i++)
        {
        plaintxt[i] = DecryptChar(cypher.charAt(i));

        }

    Message.setText(String.copyValueOf(plaintxt));

    smrotor = new SmallRotor();
    medrotor = new MedRotor();
    lgrotor = new LargeRotor(); 
    }

    public void LoadMessage(String FileName)
    {
    System.out.println(FileName + FileExtension);
    try {
            FileInputStream in = new FileInputStream(FileName + FileExtension);
            DataInputStream din = new DataInputStream(in);

            char [] mess = new char[10000];

            try {
                int i = 0;
                while(true)
                    {
                        mess[i] = (char)din.readByte();
                        System.out.println("Recieved a |" + mess[i]+"|");
                        i++;
                    }
            }
            catch(IOException e)
            {
                Message.setText(String.valueOf(mess));

            }


        }
        catch(FileNotFoundException e) {
                System.out.println("Can't Find File");
                Message.setText("Can't Find File " + FileName + FileExtension);
            }
        }

        public void LoadCypherTxt(String FileName)
        {
            System.out.println(FileName + FileExtension);
            try{
            FileInputStream in = new FileInputStream(FileName + FileExtension);
            DataInputStream din = new DataInputStream(in);

            char [] mess = new char[10000];

            try {
                int i = 0;
                while(true) {
                        mess[i] = (char)din.readByte();
                        System.out.println("Recieved a |" + mess[i]+"|");
                        i++;
                }
            }
            catch(IOException e) {
        Encrypted.setText(String.valueOf(mess));
        }
    }
        catch(FileNotFoundException e) {
        System.out.println("Can't Find File");
        Encrypted.setText("Can't Find File " + FileName + FileExtension);
    }
    }

    public void SaveMessage(String FileName) {
    System.out.println("Saved " + FileName + FileExtension);
    try {
            FileOutputStream out = new FileOutputStream(FileName + FileExtension);
            DataOutputStream dout = new DataOutputStream(out);


            String mess = new String(Message.getText());

            try {
                for(int i = 0; i < mess.length(); i++) {
                    dout.writeByte(mess.charAt(i));

                }
            }
            catch(IOException e) {
                Message.setText(String.valueOf(mess));
            }

        }
        catch(FileNotFoundException e) {
        System.out.println("Can't Find File");
        messFN.setText("Can't Find File " + FileName + FileExtension);
    }
    }

    public void SaveCypherTxt(String FileName) {
        try {
            FileOutputStream out = new FileOutputStream(FileName + FileExtension);
            DataOutputStream dout = new DataOutputStream(out);

            String mess = new String(Encrypted.getText());

            try {
                for(int i = 0; i < mess.length(); i++) {
                   dout.writeByte(mess.charAt(i));
                }
            }
            catch(IOException e) {
                Message.setText(String.valueOf(mess));
            }
        }
        catch(FileNotFoundException e) {
        System.out.println("Can't Find File");
        encFN.setText("Can't Find File " + FileName + FileExtension);
    }
    }

    public char EncryptChar(char c)
    {
    char ch;

    try {
        ch = lgrotor.charAt(smrotor.indexOf(c));
        ch = lgrotor.charAt(medrotor.indexOf(ch));

    }
        catch(Exception e) {
            System.out.println("Warning, character not in alphabet |" + c + "|");
            return c;
    }

    smrotor.turn();

    if(smrotor.turns()%27 == 0)
        medrotor.turn();

    return ch;
    }

    public char DecryptChar(char c)
    {
    System.out.println("Decrypting " + c);
    char ch;

    try {
        ch = medrotor.charAt(lgrotor.indexOf(c));      
        ch = smrotor.charAt(lgrotor.indexOf(ch));
    }
        catch(Exception e) {
            System.out.println("Warning, character not in alphabet |" + c +"|");
            return c;
    }

    smrotor.turn();

    if(smrotor.turns()%27 == 0)
        medrotor.turn();

    return ch;
    }


    @Override
    public void paint (Graphics g) {

    }

    private class MouseKeeper extends MouseAdapter {
        @Override
    public void mousePressed (MouseEvent e) { 
        int x = e.getX();
        int y = e.getY();
    }
    }

    private class WindowKeeper extends WindowAdapter {
        @Override
    public void  windowClosing(WindowEvent e) {
        System.exit(0);
    }
    }

    abstract class ButtonAdapter extends Button implements ActionListener {

        public ButtonAdapter(String name) {
            super(name);
            addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent e){pressed();}
        public abstract void pressed();
    }
}

Rotor.java

package enigma.rotor;

import java.io.*;

public abstract class Rotor {
    private static final int AlphabetLength = 27;

    protected char [] rotor = new char[AlphabetLength];

    protected int turns = 0;

    public int indexOf(char c) {
    for(int i = 0; i < AlphabetLength; i++){
            if(rotor[i] == c)
            return i;
    }

    return -1;
    }

    public int turns()
    {return turns;}

    public void turn() {
    char c = rotor[0];
    for(int i = 1; i < AlphabetLength;i++){
            rotor[i-1] = rotor[i];
    }

    rotor[AlphabetLength-1] = c;

    turns++;
    }

    public char charAt(int i)
    {
    return rotor[i];
    }

    public Rotor(){
    turns = 0;
    setAlphabet();
    }

    public abstract void setAlphabet();
}

LargeRotor.java

package enigma.rotor;

import enigma.rotor.Rotor;

public class LargeRotor extends Rotor{
    @Override
    public void setAlphabet()
    {
    int  i = 0;

        rotor[i] = ' ';
        i++;

        rotor[i] = 'B';
        i++;

        rotor[i] = 'D';
        i++;

        rotor[i] = 'F';
        i++;

        rotor[i] = 'H';
        i++;

        rotor[i] = 'J';
        i++;

        rotor[i] = 'L';
        i++;

        rotor[i] = 'N';
        i++;

        rotor[i] = 'P';
        i++;

        rotor[i] = 'R';
        i++;

        rotor[i] = 'T';
        i++;

        rotor[i] = 'V';
        i++;

        rotor[i] = 'X';
        i++;

        rotor[i] = 'Z';
        i++;

        rotor[i] = 'A';
        i++;

        rotor[i] = 'C';
        i++;

        rotor[i] = 'E';
        i++;

        rotor[i] = 'G';
        i++;

        rotor[i] = 'I';
        i++;

        rotor[i] = 'K';
        i++;

        rotor[i] = 'M';
        i++;

        rotor[i] = 'O';
        i++;

        rotor[i] = 'Q';
        i++;

        rotor[i] = 'S';
        i++;

        rotor[i] = 'U';
        i++;

        rotor[i] = 'W';
        i++;

        rotor[i] = 'Y';

    System.out.println("Finished Initialising Large Rotor  i="+i);
    }
}

MedRotor.java

package enigma.rotor;

import enigma.rotor.Rotor;

public class MedRotor extends Rotor {
    @Override
    public void setAlphabet()
    {
    int i = 0; 

    rotor[i] = ' ';
    i++;

    rotor[i] = 'E';
    i++;

    rotor[i] = 'J';
    i++;

    rotor[i] = 'O';
    i++;

    rotor[i] = 'T';
    i++;

    rotor[i] = 'Y';
    i++;

    rotor[i] = 'C';
    i++;

    rotor[i] = 'H';
    i++;

    rotor[i] = 'M';
    i++;

    rotor[i] = 'R';
    i++;

    rotor[i] = 'W';
    i++;

    rotor[i] = 'A';
    i++;

    rotor[i] = 'F';
    i++;

    rotor[i] = 'K';
    i++;

    rotor[i] = 'P';
    i++;

    rotor[i] = 'U';
    i++;

    rotor[i] = 'Z';
    i++;

    rotor[i] = 'D';
    i++;

    rotor[i] = 'I';
    i++;

    rotor[i] = 'N';
    i++;

    rotor[i] = 'S';
    i++;

    rotor[i] = 'X';
    i++;

    rotor[i] = 'B';
    i++;

    rotor[i] = 'G';
    i++;

    rotor[i] = 'L';
    i++;

    rotor[i] = 'Q';
    i++;

    rotor[i] = 'V';

    System.out.println("Finished Initialising Med Rotor  i="+i);
    }
}

SmallRotor.java

package enigma.rotor;

import enigma.rotor.Rotor;

public class MedRotor extends Rotor {
    @Override
    public void setAlphabet()
    {
    int i = 0; 

    rotor[i] = ' ';
    i++;

    rotor[i] = 'E';
    i++;

    rotor[i] = 'J';
    i++;

    rotor[i] = 'O';
    i++;

    rotor[i] = 'T';
    i++;

    rotor[i] = 'Y';
    i++;

    rotor[i] = 'C';
    i++;

    rotor[i] = 'H';
    i++;

    rotor[i] = 'M';
    i++;

    rotor[i] = 'R';
    i++;

    rotor[i] = 'W';
    i++;

    rotor[i] = 'A';
    i++;

    rotor[i] = 'F';
    i++;

    rotor[i] = 'K';
    i++;

    rotor[i] = 'P';
    i++;

    rotor[i] = 'U';
    i++;

    rotor[i] = 'Z';
    i++;

    rotor[i] = 'D';
    i++;

    rotor[i] = 'I';
    i++;

    rotor[i] = 'N';
    i++;

    rotor[i] = 'S';
    i++;

    rotor[i] = 'X';
    i++;

    rotor[i] = 'B';
    i++;

    rotor[i] = 'G';
    i++;

    rotor[i] = 'L';
    i++;

    rotor[i] = 'Q';
    i++;

    rotor[i] = 'V';

    System.out.println("Finished Initialising Med Rotor  i="+i);
    }

}

Any improvements, suggestions etc. welcome!

\$\endgroup\$
19
\$\begingroup\$

Single responsibility principle

A class should be responsible for one thing. The Enigma class is doing too much. It paints a graphical user interface, it works with files, it encrypts and decrypts, and lots of other things. It would be good to split this up aggressively.

Start by moving everything out of the class that doesn't need a graphical element. Only keep buttons, panels, action listeners in Enigma. And then rename the class to EnigmaGUI or similar to better reflect its primary (and hopefully, the only) responsibility.

Use as much memory as you need and not more

In this code, the size of the cypher array appears to be arbitrary:

plain = plain.toUpperCase();
char [] cypher = new char[10000];

for(int i = 0; i < plain.length();i++)
    {
    cypher[i] = EncryptChar(plain.charAt(i));

    }

Isn't 10000 too much? Is it enough? You can eliminate such concerns by allocating exactly as much as you need:

char[] cypher = new char[plain.length()];

Code style

The formatting is really awful. I suggest to copy paste into an IDE like Eclipse or similar, use the auto-format feature, and see what that looks like. That's the format most java developers expect to see and find it easy to review.

Naming

The convention in Java is to use camelCase for variable and method names, and SHOUT_CASE for constants. It would greatly improve readability if you follow that.

\$\endgroup\$
11
\$\begingroup\$

A few quick comments :

You could use post-increment to replace :

rotor[i] = 'Z';
i++;

by :

rotor[i++] = 'Z';

thus making your code twice as short (or three times if you take this chance to remove the empty lines in between).

I am not sure the different rotors deserve their own subclasses. They will just be different instances from a same class. Also, I am not sure there is a concept of rotor size in the classic Enigma machine, just a set of rotors of identical sizes than can be put in various positions.

You set the rotors using a lot of boiler-plate code to associate characters to indices (via an array). You could just as easily use a string :

smallRotor = " BDFHJLNPRTVXZACEGIKMOQSUWY"
mediRotor = " EJOTYCHMRWAFKPUZDINSXBGLQV"

This is very concise and would prevent you from using for instance the code of MedRotor.java when the one from SmallRotor.java is expected.

Also, rotors are expected to have 2 properties :

  • they describe a bijection : each letter can be assign one and only one other letter. This is required to have the decoding of the message possible.

  • one letter never gets converted to itself (this is not really required strictly speaking but a lot of ideas used to break Enigma are based on this so as non-nazi citizen, I'm glad this property was true when the machine was heavily used).

I could be interesting to check that the rotors your are creating respect these properties.

\$\endgroup\$
  • \$\begingroup\$ Nice ideas, I will run some tests about the same letter never becoming itself. \$\endgroup\$ – user69731 Jun 29 '15 at 19:01
  • 1
    \$\begingroup\$ "making your code twice as short" is not a good thing by itself. combining increments with array dereference is a common trick, yet it's still a code smell due to mixing two different aspects of code (array access and arithmetic operations) in one op. As such, it raises a warning in e.g. NetBeans, quote Value of increment/decrement expression used - While admirably terse, such expressions may be confusing, and violate the general design principle that a given construct should do precisely one thing. - frankly, I wouldn't give that one advice to newbie seeking CR help (or anyone, actually). \$\endgroup\$ – vaxquis Jun 30 '15 at 6:47
10
\$\begingroup\$

The parts where you set up the alphabet of each rotor has a lot more boilerplate than it should.

rotor[i] = 'Z';
i++;
... //Rest of letters

Could be changed to this:

rotor = new char[] { 'Z', \* Rest of letters and such *\ };

It is basically the same idea, but it is more readable. It actually is better performance because it eliminates the incrementing of i

\$\endgroup\$
3
\$\begingroup\$

What others said, plus:

  • Putting space (" ") into rotors' translation tables looks like an ugly and dangerous hack to me - get rid of it and deal with unwanted chars at the input time. Throw an exception when such characters actually get to indexOf/charAt method

  • why name the rotor's permutation 'rotor', when it's not a rotor?

  • instead of searching sequentially when encypting/decrypting the character, have two 26-chars arrays, for forward and backward pass, and index them with the entering character

  • Your idea of how Enigma worked is rather simplistic. Each normal rotor (there were also reflectors, gamma rotors, ...) consisted of

    • the permutation
    • settable ring with an alphabet, visible through a window
    • at least one notch
  • encryption and decryption was the same operation on the Enigma, no need for two different methods

\$\endgroup\$
  • \$\begingroup\$ FYI, your second bullet is more of a comment that part of a review. \$\endgroup\$ – Malachi Jun 30 '15 at 15:04
3
\$\begingroup\$

Adding to previous answers, I'll divide this to two lists:

You are not implementing Enigma.

  • The real military enigma had 3 rotors, but apart from that had another entry entry wheel (essentially an unmoveable rotor), a reflector and a plugboard. The flow was first the plugboard (which swapped several pairs of letters, depending on number of plugs), than the entry wheel, than the rotors, the reflectors, the rotors again but this time reversed, the entry wheel reversed and the plugboard again.

  • The machine had 3 rotors, plugged in in any order, taken from a set of delivered rotors (which got larger at times). This also adds to 'why you are subclassing rotors when these differ only by initialization'.

  • in EncryptChar you are taking the argument, passing it first through smrotor and lgrotor and then through medrotor and lgrotor. I don't think it will get the correct output (but I'm to lazy to check)

  • The real machine had different places for moving the next rotor. Sometimes more than one.

The real code review:

  • When you rotate the rotor, you are manually copying the whole array. If you really want to copy an array, there's System.arrayCopy for that. But in this case it would be better to store the number of turns (you already do this) and in charAt simply return rotor[(i+turns)%27]

  • You are creating new rotors at the end of every encrypt/decrypt operation. A better way would be to reset them. In your current implementation this would probably set turns to 0 and call setAlphabet. After my improvement above only resetting turns would suffice.

  • When increasing the turns member variable consider increasing it mod 27 or something. Overflow won't happen in real scenarios, but in theory the Enigma machine could encrypt a string of arbitrary length, while your implementation will fail somewhere above 2GB because of algorithm (and much sooner because of memory ;)

\$\endgroup\$
0
\$\begingroup\$

As someone who recently (as of today, actually) implemented the entire Enigma machine in Java, I can confidently state that you are not at all implementing Enigma:

  • no plugboard
  • no reflector
  • no cogs making the rotors turn (the right wheel at every letter encrypted, the middle and left wheel under certain conditions); let alone the infamous mechanism named "double stepping" that gave the guys at Bletchley Park so many headaches and sleepless nights
  • no choice of different rotors, and no possibility to place a given rotor either as right, middle or left rotor
  • no rings, interplaying with the cogs
  • no "Grundstellung" or initial offsets of the various rotors
  • no combination of rotor choice, plugboard, ring choice and "Grundstellung" into something known as "daily setting"

Your implementation will never, ever be able to do what an Enigma machine did. Read the specs, take all the advice offered above, and start all over again.

\$\endgroup\$
  • 1
    \$\begingroup\$ This isn't really an answer, more just a list of what is missing \$\endgroup\$ – George Willcox Apr 28 '17 at 18:39
  • \$\begingroup\$ Pointing out important missing features is a valid answer, I think. Sometimes not a very good one. Like this. Other answers already pointed out that important features are missing, this one just piles on top of it, and I don't find it very useful. \$\endgroup\$ – janos Jun 14 '17 at 8:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy