Last Updated: 2016-02-28 Sun 22:09

CS 211 Project 3: Cipherous Symmetry

CODE DISTRIBUTION

CHANGELOG

Sun Feb 28 22:09:12 EST 2016
There will be no honors problem for the project. Use the time to prepare for the first exam.
Fri Feb 26 16:08:12 EST 2016
A section has been added discussing strategies for implementing MorseCipher methods. Review this section if you are struggling to finish this portion of the project.
Mon Feb 22 08:53:23 EST 2016
Clarification: if an Alphabet is asked to get(i) when i is out of bounds, throw a NotInAlphabetException with any character as the offender. Use the first constructor for NotInAlphabetException which allows one to specify the exact message that will be displayed. Use any character for offender.

Table of Contents

1 Overview

1.1 Encryption Introduction

Encryption plays a central role in the age of computing. Every online purchase, every social media post, every login to a remote server would carry drastically more risk without the ability to obscure the contents of a message from intervening parties and still enable the message recipient to make sense of the message contents.

In this project, you will construct a small class hierarchy involving different kinds of encryption. The hierarchy will illustrate one of the nice features of inheritance: that several child classes can share and use code from a parent class in a uniform way. The fact that all of the classes will descend from a common parent also means they may be used interchangeably: a program wishing to do encryption could select from any of the ciphers provided here and utilize them via a uniform interface.

Several classes are provided for you to use but it will take some work to understand how to use them. Two of the encryption algorithms you will implement, the Caesar Cipher and the Vigenere Cipher, are very easy to understand and will provide an introduction to how inheritance works. The third cipher, MorseCipher, is different but we show how we can still fit it into our structure.

1.2 Class Hierarchy

There are a total of seven classes in this project but there is not a tremendous amount of code to write. Most of your effort will be spent understanding the organization of the eventual solution. The classes fall into the following hierarchy.

Cipher (abstract)
|
+--MorseCipher
|
+--SymmetricCipher (abstract)
   |
   +--CaesarCipher
   |
   +--VigenereCipher

Alphabet

NotInAlphabetException

1.3 Project Files

The following files are relevant to the project. Some are provided in the project distribution while others you must create. You should submit all files listed unless marked as Optional (in the State column).

File State Notes
Alphabet.java Modify Provided version contains only some important static fields to avoid mistyping
AlphabetTests.java Testing  
     
NotInAlphabetException.java Create  
NotInAlphabetExceptionTests.java Testing  
     
Cipher.java Create Very short abstract class
     
SymmetricCipher.java Create Descends from Cipher, abstract
SymmetricCipherTests.java Testing  
     
CaesarCipher.java Create Descends from SymmetricCipher, concrete
CaesarCipherTests.java Testing  
     
VigenereCipher.java Create Descends from SymmetricCipher, concrete
VigenereCipherTests.java Testing  
     
MorseCipher.java Modify Descends from Cipher. Provided version contains only some important static fields to avoid mistyping
MorseCipherTests.java Testing  
     
P3Tests.java Testing Run all standard tests for the project
P3Demo.java Provided Demonstrates use of each Cipher
     
junit-cs211.jar Testing JUnit library for command line testing
ID.txt Create Create in setup to identify yourself

1.4 A Demonstration

P3Demo.java is part of the project pack and demonstrates the central functionality of the Cipher class hierarchy: descendants of Cipher can be used interchangeably as they all provide functionality through their encrypt() / decrypt() methods. However, each child class specializes these methods to scramble and unscramble text differently. The demo also shows that several of the ciphers must be initialized with an Alphabet which indicates which characters it can handle. The default alphabet is used but smaller alphabets can be constructed.

// Demonstrate the use of interchangeable ciphers. Most of the classes
// in the project will need to be complete for this code to work
// properly.
public class P3Demo {
  public static void main(String[] args){
    Alphabet alphabet = Alphabet.DEFAULT;
    String encrypted, decrypted;
    Cipher c;
    String message = "All your base are belong to us.";
    
    c = new CaesarCipher(2,alphabet);
    encrypted = c.encrypt(message);
    decrypted = c.decrypt(encrypted);
    System.out.printf("Cipher:    %s\n",c.toString());
    System.out.printf("Message:   %s\n",message);
    System.out.printf("Encrypted: %s\n",encrypted);
    System.out.printf("Decrypted: %s\n",decrypted);
    System.out.println();
    // Cipher:    Caesar Cipher (shift=2)
    // Message:   All your base are belong to us.
    // Encrypted: Cnn2 qwt2dcug2ctg2dgnqpi2vq2wu?
    // Decrypted: All your base are belong to us.
    
    c = new CaesarCipher(17,alphabet);
    encrypted = c.encrypt(message);
    decrypted = c.decrypt(encrypted);
    System.out.printf("Cipher:    %s\n",c.toString());
    System.out.printf("Message:   %s\n",message);
    System.out.printf("Encrypted: %s\n",encrypted);
    System.out.printf("Decrypted: %s\n",decrypted);
    System.out.println();
    // Cipher:    Caesar Cipher (shift=17)
    // Message:   All your base are belong to us.
    // Encrypted: R22&%5!8&sr9v&r8v&sv254x&05&!9M
    // Decrypted: All your base are belong to us.
    
    c = new VigenereCipher("Cats",alphabet);
    encrypted = c.encrypt(message);
    decrypted = c.decrypt(encrypted);
    System.out.printf("Cipher:    %s\n",c.toString());
    System.out.printf("Message:   %s\n",message);
    System.out.printf("Encrypted: %s\n",encrypted);
    System.out.printf("Decrypted: %s\n",decrypted);
    System.out.println();
    // Cipher:    Vigenere Cipher (password='Cats')
    // Message:   All your base are belong to us.
    // Encrypted: C!|D $<,21(.g](,g])+n$:=2('Dw*o
    // Decrypted: All your base are belong to us.
    
    // Comment out this section if not implementing the MorseCipher
    message = "ALL YOUR BASE";
    c = new MorseCipher();
    encrypted = c.encrypt(message);
    decrypted = c.decrypt(encrypted);
    System.out.printf("Cipher:    %s\n",c.toString());
    System.out.printf("Message:   %s\n",message);
    System.out.printf("Encrypted: %s\n",encrypted);
    System.out.printf("Decrypted: %s\n",decrypted);
    System.out.println();
    // Cipher:    Morse Cipher
    // Message:   ALL YOUR BASE
    // Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
    // Decrypted: ALL YOUR BASE
  }
}

1.5 Suggestions

This is a larger project than our first two. It may seem a bit daunting at first due to the larger number of classes to create. Here are some suggestions of how to stay sane.

One Class at a time
Work on a single class until it is finished. Don't move on until you're confident the class works right. Start by adding all "method stubs" that the compiler complains are missing so that the relevant test file compiles are reports mostly errors. Then begin filling in method definitions checking frequently on your progress by running the tests. Use the test cases to guide your development and make forward progress but remember that test cases don't prove the correctness of your code.
Read the spec and plan your efforts
After you gain a basic understanding of the project, do some planning before diving in. Notice the dependencies of which classes rely on which other classes. A suggested ordering of what to tackle is as follows.
  1. NotInAlphabetException
  2. Alphabet
  3. Cipher
  4. SymmetricCipher
  5. CaesarCipher
  6. VigenereCipher
  7. MorseCipher

This ordering is mostly reflected in the order of class descriptions below.

Understand the tests
When you fail a tests, inspect the test case code. Trace through your code by hand drawing pictures and checking assumptions. Understanding what the test is looking for and where your code is producing different results is the key to finding logic errors. Add print statements to show you values during computation, to help you trace behavior. If you are really stuck, start exploring the debugger which allows you to stop at arbitrary lines and step forwards line by line and inspect any variable.

2 Class Alphabet

Alphabet represents the set of characters that will be allowed in some message. For different messages, we can choose to use different Alphabet objects.

The primary function of Alphabets is to provide translation of characters from symbols to integers and back via its indexOf(c) and get(i) methods. Several ciphers will require translating a character like 'C' into a number. The Alphabet provides such a functionality via its a.indexOf('C') method which will produce the integer associated with C if the letter is in the alphabet. Similarly, converting a number like 8 to an equivalent character is done via a.get(8) which returns a character from the alphabet.

Adhere to the abstraction barrier set up by Alphabet and use its methods to do character/number translations elsewhere in the project. This is a required part of the manual inspection criteria.

2.1 Fields

  • private String symbols
  • public static final Alphabet DEFAULT = .... Due to the chance to mistype this, we have provided it in the project pack in Alphabet.java.

2.2 Methods

  • public Alphabet(String symbols). The constructor initializes symbols.
  • public int indexOf(char c). Returns the index of char parameter c in string symbols. For example, the index of 'b' in "abc" is 1. Throws NotInAlphabetException if char parameter c is not in symbols.
  • public char get(int i). Returns the char at position i of symbols. For example, the char at position 1 of "abc" is 'b'. Throws NotInAlphabetException if int parameter i is not a position in symbols. The NotInAlphabetException may have any character associated with as the offender parameter for its constructor.
  • public int length(). Returns the length of string symbols.
  • public String getSymbols(). Returns symbols.
  • public String toString(). String representation. Example: if symbols is "ABC", returns "Alphabet(ABC)"
  • public boolean equals(Object other). Return true if other is another Alphabet and has the same characters and ordering as this alphabet. The below examples below should compile and produce the indicated output.
    Object a1 = new Alphabet("ABCabc");
    Object a2 = new Alphabet("ABCabc");
    Object a3 = new Alphabet("123");
    Object s1 = new String("123");
    Object o1 = new Object();
    
    System.out.println( a1.equals(a2) ); // true
    System.out.println( a1.equals(a3) ); // false
    System.out.println( a1.equals(s1) ); // false
    System.out.println( a1.equals(o1) ); // false
    

2.3 Demonstration of Alphabet

The following DrJava session demonstrates the basic functionality of the Alphabet class. It's primary purpose is to provide translation of characters from symbols to integers and back via its indexOf(c) and get(i) methods.

Welcome to DrJava.
> Alphabet a2e = new Alphabet("abcde");
> a2e.indexOf('c')
2
> a2e.indexOf('a')
0
> a2e.indexOf('z')
Not in alphabet: 'z' not found in Alphabet(abcde).
        at Alphabet.indexOf(Alphabet.java:15)
> a2e.get(3)
'd'
> a2e.get(0)
'a'
> a2e.get(9)
Asked to get symbol @9, but length of Alphabet is 5.
        at Alphabet.get(Alphabet.java:22)
> a2e.length()
5
> a2e.toString()
"Alphabet(abcde)"
> String syms = a2e.getSymbols();
> syms
"abcde"

> Alphabet def = Alphabet.DEFAULT;
> def.length()
93
> def.indexOf('A')
0
> def.indexOf('a')
26
> def.indexOf('0')
62
> def.indexOf('?')
90
> def.get(92)
'>'
> def.get(32)
'g'
> def.getSymbols()
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890!@#$%^&*()_+-=[]{}\|;:'",./?<>"
> def.toString()
"Alphabet(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890!@#$%^&*()_+-=[]{}\|;:'",./?<>)"

3 Class NotInAlphabetException

Class NotInAlphabetException inherits from class RuntimeException.

3.1 Fields

  • public final String msg;
  • public final char offender;
  • public final Alphabet a;

3.2 Methods

  • public NotInAlphabetException(String msg, char offender, Alphabet a). The constructor initializes the data members.

    In the event that a NotInAlphabetException is needed for an unknown character, use this constructor with any character can be used for the offender parameter. This situation arises during some operations of the Alphabet class.

  • public NotInAlphabetException(char offender, Alphabet a) The constructor initializes the data members. The value of the message is created by calling String.format() to create a string whose format is illustrated by the following example:
    "Not in alphabet: 'a' not found in ABC."
    

    In the event that a NotInAlphabetException is needed for an unknown character, do not use this constructor. Use the other constructor

  • public String toString(). Returns msg.

4 Class Cipher

Class Cipher is an abstract class and all of its methods are abstract. This parent class represents any object that can encrypt and decrypt strings.

4.1 Methods

  • public abstract String encrypt(String s);
  • public abstract String decrypt(String s);

5 Class SymmetricCipher

Class SymmetricCipher is an abstract class that inherits from class Cipher. Not all of its methods are abstract.

5.1 Fields

  • protected Alphabet alphabet: The alphabet that this cipher works on. Characters that are to be encrypted/decrypted that do not exist in the alphabet will cause problems. In such cases, a NotInAlphabetException should be raised.

5.2 Methods

  • public SymmetricCipher(Alphabet alphabet). The constructor initializes the data member.
  • public int wrapInt(int i). Given an index value that may be outside the range of valid indexes into the alphabet, wrap it back around to a valid index.
  • public int rotate(int index, int shift). Given an index into the alphabet, rotate it around shift times to the resulting valid index into alphabet.
  • public Alphabet getAlphabet(). Returns alphabet.
  • public String encrypt(String s). Implement this method based upon the encrypt1 definition (below), which encrypts a single character (think of the Caesar Cipher for an understanding). Throws NotInAlphabetException if any character is found that isn't in the alphabet.
  • public String decrypt(String s). Implement this method based upon the decrypt1 definition, which decrypts a single character (think of the Caesar Cipher for an understanding). Throws NotInAlphabetException if any character is found that isn't in the alphabet.
  • protected abstract char encrypt1(char c). Child classes must provide an implementation of this; it provides a way to encrypt a single character. Child class implementations will throw NotInAlphabetException if any character is found that isn't in the alphabet.
  • protected abstract char decrypt1(char c). Child classes must provide an implementation of this; it provides a way to decrypt a single character. Child class implementations will throw NotInAlphabetException if any character is found that isn't in the alphabet.

6 Class CaesarCipher

You'll want to do a bit of background reading on the Caesar Cipher to get a feel for it. Wikipedia is a good place to start.

Harvard's CS50 course also has a decent video covering both the Caesar and Vigenere Cipher (next section).

The central idea is that both communicating parties possess a secret number we will refer to as the shift key. Encrypting an integer is simply a matter of adding the shift key onto the integer and wrapping around if the integer overflows (goes beyond the valid indexes). Decrypting an integer simply subtracts the shift key from the integer and wraps it around if the result is negative.

It should be apparent that the shift key is the important secret that is pertinent to the Caesar Cipher: both the sender and receiver need to know the shift key in order to communicate. Since the same key both encrypts and decrypts messages, the Caesar Cipher is a type of symmetric cipher.

You should not need to implement encrypt() or decrypt() as these methods will be inherited from SymmetricCipher.

6.1 Example

If we had a tiny alphabet as follows (only A → P), their "shift by three" values are shown below.

original A B C D E F G H I J K L M N O P
shifted 3 D E F G H I J K L M N O P A B C

The key to understanding is to think of the alphabet inscribed on two concentric circles, initially so that the A's match each other, the B's match each other, and so on. We then rotate one wheel by the shift amount, showing us the per-character encryption. To decrypt these messages, you only need to shift in the opposite direction.

Consider the larger alphabet with the following index numbers associated with them.

alphabet 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 <space>
index 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 26

Suppose a Caesar Cipher chooses the shift key +17. The following demonstrates encryption and decryption using the Caesar Cipher.

ENCRYPTION
Alphabet Size: 27
Message: catfood
Shift: +17

|----------------+-----+-----+-----+-----+-----+-----+-----|
| Message        |   c |   a |   t |   f |   o |   o |   d |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Message Ints   |   2 |   0 |  19 |   5 |  14 |  14 |   3 |
| Shift          | +17 | +17 | +17 | +17 | +17 | +17 | +17 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Intermediate   |  19 |  17 |  36 |  22 |  31 |  31 |  20 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Rotate?        |  no |  no | -27 |  no | -27 | -27 |  no |
| Encrypted Ints |  19 |  17 |   9 |  22 |   4 |   4 |  20 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Encrypted      |   t |   r |   j |   w |   e |   e |   u |
|----------------+-----+-----+-----+-----+-----+-----+-----|

Encrypted Text: trjweeu

DECRYPTION
Alphabet Size: 27
Message:  trjweeu
Shift: +17

|----------------+-----+-----+-----+-----+-----+-----+-----|
| Message        |   t |   r |   j |   w |   e |   e |   u |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Message Ints   |  19 |  17 |   9 |  22 |   4 |   4 |  20 |
| Shift          | -17 | -17 | -17 | -17 | -17 | -17 | -17 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Intermediate   |   2 |   0 |  -8 |   5 | -13 | -13 |   3 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Rotate?        |  no |  no | +27 |  no | +27 | +27 |  no |
| Decrypted Ints |   2 |   0 |  19 |   5 |  14 |  14 |   3 |
|----------------+-----+-----+-----+-----+-----+-----+-----|
| Decrypted      |   c |   a |   t |   f |   o |   o |   d |
|----------------+-----+-----+-----+-----+-----+-----+-----|

Decrypted Text: catfood

Use the Alphabet class to determine the which number corresponds to each character in a message during encryption and decryption. This will be done the most in the implementation of encrypt() and decrypt() for which SymmetricCipher is primarily responsible. CaesarCipher will need to implement encrypt1()/decrypt1() that determines the results of shifting single ints.

6.2 Fields

  • protected int shift: The shift that this cipher uses to adjust characters during encryption/decryption.

6.3 Methods

  • public CaesarCipher(int shift, Alphabet alphabet). The constructor stores the shift and alphabet.
    • Note: even if shift is out of range of the alphabet, store it exactly as given and let other code deal with it.
  • public CaesarCipher(int shift). This constructor stores the shift and uses the default Alphabet found in Alphabet.DEFAULT.
    • Note: even if shift is out of range of the alphabet, store it exactly as given and let other code deal with it.
  • public String encrypt(String s). Shifts each character by the shift amount and returns the newly encoded string. Inherited implementation.
  • public String decrypt(String s). Shifts each character in the opposite direction by the shift amount, which will recover the original secret message. Inherited implementation.
  • public char encrypt1(char c). Encrypts and returns a single character based on the shift and the alphabet in use. Will throw NotInAlphabetException if any character is found that isn't in the alphabet.
  • public char decrypt1(char c). Decrypts and returns a single character based on the shift and the alphabet in use. Will throw NotInAlphabetException if any character is found that isn't in the alphabet.
  • public String toString(). Return a string representation of the cipher in the following format
    Caesar Cipher (shift=2)
    

    where the value associated with shift is taken from the internal field of the class.

7 Class VigenereCipher

As before, a little background reading can go a long way to gaining a basic understanding: Wikipedi's Description of the Vigenere Cipher.

Class VigenereCipher extends SymmetricCipher. Although we shift characters in a similar fashion to the Caesar Cipher, it is more sophisticated: instead of a static shift value that is used for every single letter, a secret password is chosen (made entirely of symbols in our alphabet). To determine the amount of shift for each character of the secret message, the password is concatenated to itself until it's as long as the secret message, and we compare indexes in the secret message with the same spot in our concatenated passphrase. If the password's character is e.g. the 11th symbol in our alphabet, then that corresponding character in our secret message is shifted by 11 (in the same style as described previously for the CaesarCipher).

7.1 Example

Suppose we want to encrypt the secret message "furball", and we have previously established with our co-conspirators that "cat" is the passphrase. We'll use an Alphabet with only lowercase letters and a space.

alphabet 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 <space>
index 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 26

Per character, we figure out which letter of the password controls the amount of shift, perform the shift, and result in the next character of the output.

Alphabet Size: 27
Message: furball
Password: cat

|----------------+----+----+-----+----+----+-----+----|
| Message        |  f |  u |   r |  b |  a |   l |  l |
| Password       |  c |  a |   t |  c |  a |   t |  c |
|----------------+----+----+-----+----+----+-----+----|
| Message Ints   |  5 | 20 |  17 |  1 |  0 |  11 | 11 |
| Password Ints  | +2 | +0 | +19 | +2 | +0 | +19 | +2 |
|----------------+----+----+-----+----+----+-----+----|
| Intermediate   |  7 | 20 |  36 |  3 |  0 |  30 | 13 |
|----------------+----+----+-----+----+----+-----+----|
| Rotate?        | no | no | -27 | no | no | -27 | no |
| Encrypted Ints |  7 | 20 |   9 |  3 |  0 |   3 | 13 |
|----------------+----+----+-----+----+----+-----+----|
| Encrypted      |  h |  u |   j |  d |  a |   d |  n |
|----------------+----+----+-----+----+----+-----+----|

Encrypted Text: hujdadn

Note that the two l's of "furball" are shifted by different letters in the password and thus have different encodings.

Decryption reverses the process by subtracting off the indices associated with the password characters.

Alphabet Size: 27
Message:  hujdadn
Password: cat

|----------------+----+----+-----+----+----+-----+----|
| Message        |  h |  u |   j |  d |  a |   d |  n |
| Password       |  c |  a |   t |  c |  a |   t |  c |
|----------------+----+----+-----+----+----+-----+----|
| Message Ints   |  7 | 20 |   9 |  3 |  0 |   3 | 13 |
| Password Ints  | -2 | -0 | -19 | -2 | -0 | -19 | -2 |
|----------------+----+----+-----+----+----+-----+----|
| Intermediate   |  5 | 20 | -10 |  3 |  0 | -16 | 11 |
|----------------+----+----+-----+----+----+-----+----|
| Rotate?        | no | no | +27 | no | no | +27 | no |
| Decrypted Ints |  5 | 20 |  17 |  1 |  0 |  11 | 11 |
|----------------+----+----+-----+----+----+-----+----|
| Decrypted      |  f |  u |   r |  b |  a |   l |  l |
|----------------+----+----+-----+----+----+-----+----|

Decrypted Text: furball

7.2 Encryption/Decryption

This is a bit trickier here, as Vigenere ciphers are stateful during encryption: they need to track which password integer to use for each position of the message. It is tempting to override encrypt()/decrypt() and write your own loops for this. Resist this urge and take the following approach:

  • Use an internal field associated with each instance of the class to track the position in the password array
  • Override encrypt() to set this field to 0, then call the parent method's version of encrypt()
  • In encrypt1() use the field to determine how much shifting is to be done. Then update the internal password position by incrementing it
  • Take a similar approach for decryption: override decrypt() to set the password position to 0 and use that position during decrypt1().

Full credit solutions will have very short methods with no loops for encrypt and decrypt.

7.3 Fields

  • protected String password The password, used to generate shift values.
  • protected int passwordPos; Records the current location in a String that is being crypted. This is the 'state' that is used in successive encrypt1 calls by encrypt (similarly for decrypting).

7.4 Methods

  • public VigenereCipher(String password,Alphabet alphabet) The constructor initializes the data members. This includes the data member in the parent class SymmetricCipher.
  • public VigenereCipher(String password). The constructor initializes the data members. This includes the data member in the parent class SymmetricCipher, which is initialized to the DEFAULT Alphabet.
  • public String getPassword(). Returns password.
  • protected char encrypt1(char c). Relies upon password and passwordPos to encrypt a single char. Must increment passwordPos. Will throw NotInAlphabetException if any character is found that isn't in the alphabet.
  • protected char decrypt1(char c). Relies upon password and passwordPos to decrypt a single char. Must increment passwordPos. Will throw NotInAlphabetException if any character is found that isn't in the alphabet.
  • public String encrypt(String s). We can't wholly use the inherited version, because we need to know at what position in the string we're encrypting. Initializes passwordPos to 0, and then invokes the parent class's version of encrypt (which in turn uses this class's encrypt1 definition). When characters not in the alphabet are encountered, a NotInAlphabetException is thrown.
  • public String decrypt(String s). We can't wholly use the inherited version, because we need to know at what position in the string we're decrypting. Initializes passwordPos to 0, and then invokes the parent class's version of decrypt (which in turn uses this class's decrypt1 definition). When characters not in the alphabet are encountered, a NotInAlphabetException is thrown.
  • public String toString(). Return a string representation of the cipher in the following format
    Vigenere Cipher (password='Cats')
    

    where the value associated with password is taken from the internal field of the class.

8 Class MorseCipher

Morse code is actually a cipher with encryption and decryption actions. Unusually, the intent is not to hide information, merely to represent it in a form more suited for transit. By representing letters with dashes and dots, it is easy to transmit data through lossy environments such as transmission across wires.

The project pack contains a start for MorseCipher.java which defines the alphabet we will use along with the Morse codes associated with the those letters . You will implement the same encrypt() and decrypt() methods as before. Other than the series of dots and dashes representing each letter, we see three spaces separating each letter of a word and seven spaces separating words. There are no spaces at the end of the message. We must create exactly the correct amount of spacing in our encrypted messages, and we must correctly extract the letters and spaces when decrypting.

8.1 Fields

  • public static final String letters...;
  • public static final String[] codes...;

See the shared MorseCipher.java file in p3pack.zip for the entire representation. It has been shared to avoid copying errors as you implement your project.

8.2 Methods

  • public MorseCipher(). The constructor uses letters to fill in its alphabet field.
  • public String encrypt(String plainText). Converts a string of letters, digits, and spaces to the corresponding morse code representation. All lowercase letters are implicitly converted to uppercase during encryption.
  • public String decrypt(String cryptText). Converts a string of morse code representation back to a string of uppercase letters, digits, and spaces. Any letters that were originally lowercase will only appear as uppercase.

8.3 Strategies for Implementation of Morse Methods

Unlike the other ciphers, the MorseCipher does not involve any secret information. Instead, it is more properly thought of as encoding/decoding based on lookup tables of character equivalences.

In the provided MorseCipher.java file, two tables appear

  • A String called letters which gives the valid characters in the MorseCode. This table is a good candidate for an Alphabet object to ease looking up characters.
  • An array of Strings called codes which give the series of dashes and dots equivalent to each character in the allowed set of characters.

The central strategy while encrypting is simple: for each character in the message, determine the index of that character and output the equivalent dash-dot string. Each dash-dot is separated by 3 spaces and each word is separated by 7 spaces

Message: "ALL YOUR BASE"
A: ".-"   "   "
L: ".-.." "   "
L: ".-.." "   "
_: "       "
Y: "-.--" "   "
O: "---"  "   "
U: "..-"  "   "
R: ".-."  "   "
_: "       "
B: "-..." "   "
A: ".-"   "   "
S: "..."  "   "
E: "."

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .

Decrypting is a matter of reading dots/dashes until a space is encountered. The sequence of dashes/dots is looked up in the table of equivalences and the equivalent character is added to the growing message. After this, count how many spaces there are prior to the next dot/dash: 3 spaces means the next character is part of the same word, 7 means the next character starts a new word and a space should be put into the growing decoded message.

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:  ^
code: ""
mesg: ""

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:    ^
code: ".-" -> A
mesg: "A"

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:       ^
code: "" -> 3 spaces, same word
mesg: "A"

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:           ^
code: ".-.." -> L
mesg: "AL"

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:              ^
code: "" -> 3 spaces, same word
mesg: "AL"

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:                  ^
code: ".-.." -> L
mesg: "ALL"

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:                         ^
code: "" -> 7 spaces, new word
mesg: "ALL "

Encrypted: .-   .-..   .-..       -.--   ---   ..-   .-.       -...   .-   ...   .
position:                             ^
code: "-.--" -> Y
mesg: "ALL Y"
...

The code to accomplish decryption is more intricate than other processes we have previously dealt with. It may be helpful to establish private helper methods to perform tasks such as

  • Scan through spaces in a string until the next non-space character and return the number of spaces.
  • Scan all non-space characters from a given start position and return a string of those non-space characters.

While not strictly necessary, such little methods can simplify your the logic of your decryption routine to make it "prettier". Make sure to document any helper methods you use.

9 Honors Section Problem: None

There is no honors problem for this assignment.

10 (50%) Automated Tests   grading

  • We are providing a battery of unit tests in the various files, all of which may be run via P3Tests.java which will be used by graders to evaluate your code. You may want to begin studying how these tests work and experiment with your own. (See the specification for project one for instructions on running JUnit tests.)
  • Tests may be expanded as the HW deadline approaches.
  • It is your responsibility to get and use the freshest set of tests available.
  • Tests will be provided in source form so that you will know what tests are doing and where you are failing.
  • Code that does not compile and run tests according to the specified command line invocation may lose all automated testing credit. Graders will usually try to fix small compilation errors such as bad directory structures or improper use of packages. Such corrections typically result in a loss of 5-10% credit on automated testing. However, if more than a small amount of error to fix problems seems required, no credit will be given.

11 (50%) Manual Inspection Criteria

The following features will be evaluated during manual inspection of your code and analysis documents. It is worth a total of 50% of the overall grade for this project.

11.1 (5%) Adherence to Alphabet Abstraction Barriers    grading

The purpose of defining classes such as Alphabet is to simplify the process of translating characters to integers and back again. Methods like Alphabet.get() and Alphabet.indexOf() should be employed to perform such translations rather than attempting to directly manipulate the fields of that class. Translation primarily occurs in encrypt1()/decrypt1() where the methods of Alphabet should be employed.

11.2 (20%) Proper Use of Inheritance for Code Re-Use   grading

SymmetricCipher (5%)
Implement encrypt / decrypt. Use calls to encrypt1 / decrypt1 which will be defined in child classes.
Caesar Encryption (5%)
Override encrypt1()/decrytp1() and correctly leverage the inherited definition of encrypt()/decrypt().
Vigenere Encryption (5%)
Override encrypt1()/decrytp1() and correctly leverage the inherited definition of encrypt()/decrypt().
Avoid Variable Shadowing (5%)
If a parent class already has a field, there is no need for child class to have a similar field. Do not duplicate fields.

11.3 (5%) Inheritance Correctness and Safety   grading

Constructors (3%)
Child classes appropriately use parent class constructors to create objects.
@Override tags everywhere they can be (2%)
Every time we override a method, we should label it with @Override so that the compiler can ensure we are actually overriding a method as expected. There is an exact list of places this must occur, and it is up to you to discern where.

For a short discussion of the @Override tag, have a look at this tutorial site. It is useful as it enables the compiler to flag errors when a method is meant to override a parent but due to typing errors it is not.

11.4 (5%) MorseCipher Elegance

The encrypt() method of MorseCipher should be relatively short (20ish lines) and be fairly easy to follow. Comment it a little if you see the need to explain your reasoning.

The decrypt() method is comparatively gnarly. It involves some significant looping, conditionals, and cleverness. Make your code as clean as possible and include some comments to describe your approach in each section. Line by line descriptions are not needed but comments like "find the character that matches the code", "check whether code is finished" and "advance past spaces" help a reader to discern what each portion is doing.

11.5 (10%) Coding Style and Readability   grading

There are a few more points for coding style and readability this time. You are creating many more files from scratch this time, and we want to see you creating well organized, readable code. Comment meaningfully and adequately. We won't deduct points for too many comments and will give feedback if comments are too abundant and not meaningful.

Class Documentation (2%)
Each class has an initial comment indicating its intended purpose, how it works, and how it relates to or uses other classes in the project.
Field Documentation (2%)
Each field of a class is documented to indicate what piece of data is tracked and how it will be used. Both public and private fields are documented.
Method documentation (3%)
Each method has a short description indicating its intended purpose and how it gets its job done.
Code Cleanliness (3%)
Indent and {bracket} code uniformly throughout the program to improve readability.

11.6 (5%) Correct Project Setup   grading

Correctly setting up the directory structure for each project greatly eases the task of grading lots of projects. Graders get cranky when strange directory structures or missing files are around and you do not want cranky graders. The following will be checked for setup on this project.

  • The Setup instructions were followed closely
  • The Project Directory is named according to the specification
  • There is an Identity Text File present with the required information in it
  • Code can be compiled and tests run from the command line

12 Project Submission

12.1 Provided Files

Download the zip file p3pack.zip which contains necessary files for this assignment. Unzip this file which will create a folder which you should as you project directory as described below.

12.2 Project Directory

Rename the pXpack directory (folder) according to the project directory convention: NetID-LabSecNum-pNum where

  • masonID is your GMU Net ID, the first part of your GMU email address.
  • labSecNum is your lab section number, such as 201, 202, 2H1, etc.
  • pNum represents the project number, such as p1.

Examples

  • msnyde14-2H1-pX for Mark Snyder in lab section 2H1
  • ckauffm2-205-pX for Chris Kauffman in lab section 205

All work you do should be saved in your project directory for eventual zipping and submission.

12.3 Identity Text File

Create a text file in your project directory called ID.txt which has identifying information in it. A sample ID.txt looks like this:

Mark Snyder
msnyde14
G0071234
Lecture: 002
Lab: 205

It contains a full name, mason ID, G#, lecture section, and lab section in it.

Failure to submit your work in an appropriately named directory and with the ID.txt file in it with correct information will result in the loss of 5%.

12.4 Submission

Create a zip file of your homework directory and submit it to the course Blackboard page. Do not e-mail the professor or TAs your code.

Create a .zip file of your directory as you will learn in the first programming lab.

To submit your zip to Blackboard:

  • Click on the Projects Link
  • Click on Project 2
  • Scroll down to "Attach a File"
  • Click "Browse My Computer"
  • Select your Zip file

You may submit as many times as you like; only the final valid submission will be graded. If you turn in late work within 48 hours, it will consume late tokens. If you turn in late work beyond 48 hours, that particular submission is invalid and will be ignored.

You should always verify that your submission is successful. Go back to the assignment and download your last submission; is there actually a file there? Can you unzip that file, and see .java files in there as you'd expect? Can you run those files? It turns out you can do quite a bit to ensure you've properly submitted your work. Don't get a zero just because of a failure to actually turn in the right files! It is your responsibility to correctly turn in your work.


Author: Mark Snyder, Chris Kauffman (msnyde14@gmu.edu, kauffman@cs.gmu.edu)
Date: 2016-02-28 Sun 22:09