Assignment 2 - Keeping Secrets

Due: Thursday, January 16, 2020, at 5pm

Due: Saturday, January 18, 2020, at 5pm (updated!)

You may work alone or with a partner, but you must type up the code yourself. You may also discuss the assignment at a high level with other students. You should list any student with whom you discussed the assignment, and the manner of discussion (high-level, partner, etc.) in a readme.txt file that you include with your submission.

You should submit your assignment as a .zip file on Moodle.

Parts of this assignment:

Problem 1: The random library

# You should be equipped to complete this problem after lesson 3 (Friday Jan. 10).

Similar to the math library, Python provides some useful functionality for pseudo-random numbers in a library called random.

Here are some example functions, taken from the Python documentation:

>>> import random                              # Import the random library

>>> random.random()                            # Random float:  0.0 <= x < 1.0
0.37444887175646646

>>> random.uniform(2.5, 10.0)                  # Random float:  2.5 <= x < 10.0
3.1800146073117523

>>> random.randrange(10)                       # Integer from 0 to 9 inclusive
7

>>> random.randrange(0, 101, 2)                # *Even* integer from 0 to 100 inclusive
26

>>> random.choice(['win', 'lose', 'draw'])     # Single random element from a sequence
'draw'

>>> deck = 'ace two three four'.split()
>>> random.shuffle(deck)                       # Shuffle a list
>>> deck
['four', 'two', 'ace', 'three']

>>> random.sample([10, 20, 30, 40, 50], k=4)   # Four samples without replacement
[40, 10, 50, 30]

The random-number generators used by the random module are not truly random. Rather, they are pseudo random (so don’t ever use them to write security software!). We can initialize the random-number generators using the seed function; if you do this with the same seed each time, you get reproduceable results. This can be especially handy for debugging.

>>> import random
>>> random.seed(111)
>>> for i in range(5):
	random.random()

0.827170565342314
0.21276311517617263
0.9425194436011797
0.49391971673226975
0.3975871534419906
>>> random.seed(0)       # choose a new seed
>>> for i in range(5):
	random.random()

0.8444218515250481
0.7579544029403025
0.420571580830845
0.25891675029296335
0.5112747213686085
>>> random.seed(111)      # re-seed to the original value
>>> for i in range(5):
	random.random()

0.827170565342314
0.21276311517617263
0.9425194436011797
0.49391971673226975
0.3975871534419906

To make something closer to actual randomness, you an call seed without any arguments. In that case, it uses the computer’s system time, which is not constant.

Trying out pseudo-randomness

Your friend doesn’t think that Python’s random library is that random. To prove that it behaves fairly randomly, you decide to write a program to prove them wrong (or at least, prove as much as you can after your first week of CS 111).

You decide to compute the following metrics:

  • minimum
  • maximum
  • average

Your program should generate a user-specified number of random integers and compute some statistics on them. You should also ask the user for a maximum and minimum integer. Put your code in a file called testingRandom.py.

(Hint: Python has built-in functions min(..) and max(..) that you might find helpful.)

Here is some “skeleton code” to get you started:

# testingRandom.py
# Generates a user-specified number of integers within a user-specified
# range and calculates some statistics for those numbers.
#
# Inputs:
#   * number of integers to generate (int)
#   * bottom of range (int)
#   * top of range (int)

import random

def main():
    numInts = int(input("How many integers should I generate? "))

    ## TODO: the rest of your code here

    print("Statistics:")

    print("The minimum value was", minSeen)
    print("The maximum value was", maxSeen)
    print("The average value was", averageSeen)

main()

Here is some example output:

How many integers should I generate? 40
What is the minimum integer? 20
What is the maximum integer? 80

Statistics:
The minimum value was 25
The maximum value was 80
The average value was 51.625

Here is more, with more data points (one million data points can take a few seconds to run):

How many integers should I generate? 1000000
What is the minimum integer? 20
What is the maximum integer? 80

Statistics:
The minimum value was 20
The maximum value was 80
The average value was 49.98235

Problem 2: An enigmatic Caesar cipher

# You should be equipped to complete parts (a) and (b) of this problem after lesson 4 (Monday Jan. 13), and part (c) after lesson 5 (Wednesday Jan. 15).

2a: Keeping simple secrets

A Caesar cipher is a simple substitution cipher based on the idea of shifting each letter of the plaintext message a fixed number (called the key) of positions in the alphabet. For example, if the key value is 2, the word “Banana” would be encoded as “Dcpcpc”. The original message can be recovered by “re-encoding” it using the negative of the key (e.g., -2).

Write a program (simpleCaesar.py) that can encode and decode Caesar ciphers. The input to the program will be a string of plaintext and the value of the key. The output will be an encoded message where each character in the original message is replaced by shifting it key characters in the Unicode character set.

Here is some starter code:

# simplecaesar.py
# Encodes or decodes a message using a Caesar cipher.
#
# Inputs:
#   * string to encode/decode (str)
#   * key (int)

def main():
    # Get the plaintext message and the key from the user
    plaintext = # TODO
    key = # TODO

    # Initialize the variable to store the encrypted message in
    msg = # TODO

    # Build the encrypted message using the accumulator pattern
    # TODO

    # Display the result to the user
    print("The encrypted message is:\n" + msg)

main()

Here is some example output:

Please enter a string to encrypt: Apple banana cat dog elephant fish
Please enter a key to shift by (an integer): 10

The encrypted message is:
Kzzvo*lkxkxk*mk~*nyq*ovozrkx~*ps}r

We can check that decryption works, too:

Please enter a string to encrypt: Kzzvo*lkxkxk*mk~*nyq*ovozrkx~*ps}r
Please enter a key to shift by (an integer): -10

The encrypted message is:
Apple banana cat dog elephant fish

2b: Going around in circles

One problem with the program in part (a) is that it does not deal with the case when we “drop off the end” of the alphabet. A true Caesar cipher does the shifting in a circular fashion where the next character after “z” is “a”.

Copy your solution from part (a) to a new file called circularCaesar.py. Modify this code to make it circular. You may assume that the input consists only of English letters (uppercase and lowercase) and spaces.

(Hint: Make a string containing all of the characters of your alphabet and use positions in this string as your code, rather than using ord and chr. You do not necessarily have to shift “z” into “a”, just make sure that you use a circular shift over the entire sequence of characters in your alphabet string. You might find the string function index helpful. Also, there are constants in the string module that might be of help to you…)

Here is some example output for this new cipher program. Note that your output may vary slightly depending on how you handle spaces and capital/lowercase letters.

Please enter a string to encrypt: Apple banana cat dog elephant fish
Please enter a key to shift by (an integer): 10

The encrypted message is:
KzzvojlkxkxkjmkDjnyqjovozrkxDjpsCr
Please enter a string to encrypt: KzzvojlkxkxkjmkDjnyqjovozrkxDjpsCr
Please enter a key to shift by (an integer): -10

The encrypted message is:
Apple banana cat dog elephant fish

2c: A cipher wrapped in an enigma

We can extend our Caesar cipher even further by using pseudo-randomness to modify the key after each character is encrypted. This is based (somewhat loosely) on the idea of the Enigma machine, which was used by the Germans in World War II to encrypt their messages.

For this part, you only need to write the encryption function; save your code for this part as enigmaticCaesarEncrypter.py. Rather than asking the user for the key, your code should ask the user for a random seed. You can build a list of possible keys (think about how many there could be for a given input message), and use a function in the random library to randomly reorder the key list.

After each character your code enrypts, it should use the current key as an index into the key list. The value at that position will be the key for the next character.

Here is an example of using the current key to look up the next key. Make sure you have working code for this before trying to get the enigmatic cipher working!

>>> listToWalk = [1,5,0,2,3,4]
>>> 
>>> key = listToWalk[0]
>>> # your code here

1
5
4
3
2
0
1
5
4
3

Here is an example of encrypting using an enigmatic Caesar cipher (once again, your output may vary slightly depending on how you handle spaces and capital/lowercase letters):

Please enter a string to encrypt: Apple banana cat dog elephant fish
Please enter a pseudo-random seed (an integer): 111

The encrypted message is:
GQgvaExskulneSWFwkomAWvaUDskNYsnhc

What you should submit

You should submit a single .zip file on Moodle. It should contain the following files:

  • readme.txt (collaboration statement listing collaborators and form of collaboration)
  • testingRandom.py (problem 1)
  • simpleCaesar.py (problem 2a)
  • circularCaesar.py (problem 2b)
  • enigmaticCaesarEncrypter.py (problem 2c)

OPTIONAL – Worth 0.5-1.0 extra late days: Further Caesar cipher extensions

This assignment has an optional extension, which can be completed to gain up to one extra late day.