Private- or symmetric-key encryption schemes are intuitive ciphers your students may have heard about. These ciphers depend upon a shared secret key. For example, if Alice and Bob are using an encryption system to communicate, they both have to know the secret key. That way, Alice can encipher a message with the key, and Bob can decipher it with the same key. Then Bob can encipher a message with the key, and Alice can decipher it. And so on. But how do Alice and Bob share the key in the first place? What if Alice lives in California and Bob lives in New York? There must be a secure way in which they can share the secret key before being able to use it. Ask your students how they think Alice and Bob should share their secret key and discuss difficulties that might come up.
The following are some examples of symmetric-key encryption systems. Although most of them are considered insecure and are not used for security purposes today, the ideas behind them are fundamental to modern private-key encryption.
It is a good idea to discuss strategies for breaking ciphers. One method is brute-force, which simply means trying all the possibilites for the key. In this case, a brute-force attack would be very simple, as there are only 26 possible keys. This would be doable by a human, and trivial for a computer!
Encoding the plaintext “PROGRAMMING IS FUN” yields the ciphertext “ISKRSMOOFLR FJ NYL”.
Note that the key is a permutation of the alphabet, which means that there are 26! possibilities (assuming that a letter can be encoded as itself.) This is far too many to test with modern computing capabilities, so a brute-force attack is not reasonable. However, this kind of encryption is still not secure. Consider how many people solve cryptograms in newspapers every day! This is because there are other strategies used in solving them. In this system, patterns in the plaintext are preserved in the ciphertext. A simple substitution cipher can be broken by looking for common patterns in the ciphertext and by using frequency analysis.
This programming assignment could be attempted once the students have worked with lists. The common pattern for frequency of letters in the alphabet can be used to put the letters in order from most frequent to least frequent. Then the computer can make a first guess at decrypting the message by fitting letters based on frequency in the ciphertext. Then the student can create a mechanism for swapping two letters in the decrypted pattern.
If you are familiar with Java, the following programs may be useful as simulations the substitution cipher and frequency analysis:
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class Cryptogram {
//instance variables
static final Character[] alpha = {‘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’};
private ArrayList<Character> alphabet;
private ArrayList<Character> key;
private Random gen;
public Cryptogram() {
alphabet = new ArrayList<Character>(Arrays.asList(alpha));
key = new ArrayList<Character>();
gen = new Random();
while(alphabet.size() > 0) {
int i = gen.nextInt(alphabet.size());
int index = Arrays.binarySearch(alpha, alphabet.get(i));
while(index == key.size()) {
i = gen.nextInt(alphabet.size());
index = Arrays.binarySearch(alpha, alphabet.get(i));
}
key.add(alphabet.remove(i));
}
}
public void changeKey() {
alphabet = new ArrayList<Character>(Arrays.asList(alpha));
key = new ArrayList<Character>();
while(alphabet.size() > 0) {
int i = gen.nextInt(alphabet.size());
key.add(alphabet.remove(i));
}
}
public String encipher(String message) {
String newMsg = “”;
message = message.toUpperCase();
int index = -1;
for(int i = 0; i < message.length(); i++) {
index = Arrays.binarySearch(alpha, message.charAt(i));
if(index != -1)
newMsg += key.get(index);
else
newMsg += message.charAt(i);
}
return newMsg;
}
/* main method */
public static void main(String[] args) {
Cryptogram c = new Cryptogram();
Scanner in = new Scanner(System.in);
System.out.println(“Enter a message to encipher: “);
String msg = in.nextLine();
while(!in.nextLine().equals(“END”)) {
msg += in.nextLine();
}
System.out.println(msg.toUpperCase() + “\n”);
String ct = c.encipher(msg);
System.out.println(ct);
}
}
import java.util.ArrayList;
import java.util.Scanner;
public class CryptogramSolver {
static final char[] alpha = {‘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’};
//instance variables
private String ciphertext;
private char[] key = {‘Z’, ‘Q’, ‘X’, ‘J’, ‘K’, ‘V’, ‘B’, ‘P’, ‘Y’, ‘G’, ‘F’, ‘W’, ‘M’, ‘U’, ‘C’, ‘L’, ‘D’, ‘R’, ‘H’, ‘S’, ‘N’, ‘I’, ‘O’, ‘A’, ‘T’, ‘E’};
private CharCount[] counts;
//constructor
public CryptogramSolver(String s) {
this.ciphertext = s.toUpperCase();
counts = new CharCount[26];
for(int i = 0; i < counts.length; i++) {
counts[i] = new CharCount(alpha[i], 0, true);
}
//count frequencies of letters in message
for(int i = 0; i < ciphertext.length(); i++) {
int index = -1;
index = Arrays.binarySearch(alpha, ciphertext.charAt(i));
if(index != -1) {
counts[index].increase();
}
}
Arrays.sort(counts);//ascending order
//this.printKey();
}
public String solve() {
String result = “”;
int index = -1;
for(int i = 0; i < ciphertext.length(); i++) {
index = this.find(ciphertext.charAt(i));
if(index != -1) {
result += key[index];
}
else
result += ciphertext.charAt(i);
}
//this.printKey();
return result;
}
public void setKey(char cipher, char plain) {
int index1 = this.find(cipher); //index of char to be changed
char temp = key[index1]; //value of char to be changed
int index2 = this.find(key, plain); //index of new char
key[index1] = plain;
key[index2] = temp;
//this.printKey();
}
public String getCipherText() {
return ciphertext;
}
private int find(char[] array, char c) {
for(int i = 0; i < array.length; i++) {
if(array[i] == c)
return i;
}
return -1;
}
private int find(char c) {
for(int i = 0; i < counts.length; i++) {
if(counts[i].getLetter() == c)
return i;
}
return -1;
}
private void printKey() {
for(int i = 0; i < counts.length; i++) {
System.out.print(counts[i].getLetter() + “: ” + key[i] + “\t”);
}
System.out.println();
}
/* main method */
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println(“Enter a cryptogram to solve: “);
String ct = in.nextLine();
System.out.println(ct.toUpperCase() + “\n”);
CryptogramSolver cgs = new CryptogramSolver(ct);
System.out.println(cgs.solve());
System.out.println(“Enter a letter to re-map or \”done\”: “);
String input = in.nextLine();
while(!input.equals(“done”)) {
char one = input.toUpperCase().charAt(0);
System.out.println(“Enter the new key for the given letter: “);
input = in.nextLine();
char two = input.toUpperCase().charAt(0);
cgs.setKey(one, two);
System.out.println(ct + “\n”);
System.out.println(cgs.solve() + “\n”);
System.out.println(“Enter a letter to re-map or \”done\”: “);
input = in.nextLine();
}
}
}
public class CryptoTester {
public static void main (String[] args) {
Scanner in = new Scanner(System.in);
String msg = in.nextLine();
Cryptogram cg = new Cryptogram();
msg = cg.encipher(msg);
System.out.println(msg);
System.out.println();
CryptogramSolver cgs = new CryptogramSolver(msg);
msg = cgs.solve();
System.out.println(msg);
boolean done = false;
while(!done){
System.out.println(“Enter character to replace and replacement character or \”quit\” to quit.”);
String one = in.nextLine().toUpperCase();
if(one.equalsIgnoreCase(“quit”)) {
done = true;
break;
}
String two = in.nextLine().toUpperCase();
cgs.setKey(one.charAt(0), two.charAt(0));
msg = cgs.solve();
System.out.println(cgs.getCipherText());
System.out.println();
System.out.println(msg);
}
}
}
The method of using a key that changes helps to mask the patterns in the plaintext. For example, the “MM” becomes “EU”, the “AM” becomes “EE” and the “MI” becomes “UU”. Patterns that exist in the plaintext do not exist in the ciphertext and vice versa. A given letter is not always encrypted the same way. This makes it much more difficult to apply the strategies, such as frequency analysis, and for many years, the Vigenére cipher was considered unbreakable.
Then, a man named Kasiski figured out a method for breaking the cipher. First, he analysed the ciphertext to find repeated patterns. Then, he figured out how far apart those patterns were. Then, he found the greatest common factor of those distances, and treated that as the length of the key. Once the length of the key is known (or guessed), it is possible to apply frequency analysis multiple times. To read more about the Vigenére cipher, including methods for breaking it, see https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
If you are familiar with Java, the following program may be helpful as a simulation to show how the Vigenére cipher works.
import java.util.Arrays;
import java.util.ArrayList;
public class Vigenere {
/* global variables */
static final char[] alpha = {‘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’};
/* instance variables */
private String key;
private int[] keyNums;
/* constructors */
public Vigenere(String key) {
this.key = key.toUpperCase();
this.setKeyArray();
//for(int num: keyNums)
//System.out.println(num);
}
//creates new instance with random key of given length
public Vigenere(int length) {
Random gen = new Random();
this.key = “”;
int index;
for(int i = 0; i < length; i++) {
index = gen.nextInt(alpha.length);
this.key += alpha[index];
}
this.setKeyArray();
}
//creates a new instance with random key of random length given the min and max length
public Vigenere(int min, int max) {
Random gen = new Random();
int length = min – 1 + gen.nextInt(max – min);
this.key = “”;
int index;
for(int i = 0; i < length; i++) {
index = gen.nextInt(alpha.length);
this.key += alpha[index];
}
this.setKeyArray();
}
/* public methods */
public String encipher(String msg) {
msg = msg.toUpperCase();
String res = “”;
int j = 0; //System.out.println(msg.length());
for(int i = 0; i < msg.length(); i++) {
int index = Arrays.binarySearch(alpha, msg.charAt(i)); //System.out.println(index);
if(index >= 0) {
index = (index + keyNums[j]) % 26; //System.out.println(keyNums[j] + “\t” + index);
res += alpha[index];
j++;
if(j == keyNums.length)
j = 0;
}
else
res += msg.charAt(i);
}
return res;
}
public String decipher(String msg) {
msg = msg.toUpperCase();
String res = “”;
int j = 0;
for(int i = 0; i < msg.length(); i++) {
int index = Arrays.binarySearch(alpha, msg.charAt(i));
if(index >= 0) {
index = (index – keyNums[j]) % 26;
if(index < 0)
index = 26 + index;
res += alpha[index];
j++;
if(j == keyNums.length)
j = 0;
}
else
res += msg.charAt(i);
}
return res;
}
/* private method */
private void setKeyArray() {
char c = ‘-‘;
int[] temp = new int[key.length()];
int index = -1;
int count = 0;
for(int i = 0; i < key.length(); i++) {
c = key.charAt(i);
index = Arrays.binarySearch(alpha, c);
if(index != -1)
count++;
temp[i] = index;
}
keyNums = new int[count];
int j = 0;
for(int i = 0; i < temp.length; i++) {
if(temp[i] != -1) {
keyNums[j] = temp[i];
j++;
}
}
}
}
The one-time pad is especially significant in cryptography because it is the only encryption system proven to be PERFECTLY SECRET, an important idea in cryptography. This means, basically, that it is UNBREAKABLE, even with unlimited computing power! This is, however, only true if the following criteria are met:
- the key is at least as long as the message
- the key is random
- the key is kept secret
- the key is NEVER RE-USED
So, if the OTP is perfectly secret, why are there other encryption systems? Why doesn’t everyone use it all the time? The truth is, it is still used, but not by everyone all the time. The reason is that it is not very easy or efficient to use. It requires a different, random, shared key for every message, and the fact that the key is shared means that it has to have been agreed upon at some time, which presents difficulties. The key also has to be at least as long as the message, which can be very long.
See http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html for a stick figure guide to AES. The actual implementation of AES will likely be too complicated for students, but a general idea of the strategies used can be interesting, as they are fairly simple.