Skip to content

added submission for jlwatson #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jlwatson-assign1/caesar-cipher.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SBWKRQ
1 change: 1 addition & 0 deletions jlwatson-assign1/caesar-plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PYTHON
338 changes: 338 additions & 0 deletions jlwatson-assign1/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
"""
Assignment 1: Cryptography
Course: CS 92SI
Name: Jean-Luc Watson
Date: October 9th, 2015

Three-function cryptography suite: implements Caesar, Vigenere, and Railfence
encryption and decryption, as well as a text-based user interface for repeated use.
"""
import math

# base for character shifts
ascii_base = ord('A')
# map comprehension for caesar cipher character encryption
caesar_encryption_mapping = {chr(c): chr(ascii_base + ((c - ascii_base + 3) % 26)) for c in range(ascii_base, ascii_base + 26)}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done! Would be nice to factor out 3 and 26. I also think it would've been a little cleaner to use string's native ascii_uppercase property (it just spits out all of the uppercase characters)

# map comprehension for caesar cipher character decryption
caesar_decryption_mapping = {chr(c): chr(ascii_base + ((c - ascii_base - 3) % 26)) for c in range(ascii_base, ascii_base + 26)}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! You also could do a quick pythonic swapping of the keys and values :p
source



def encrypt_caesar(plaintext):
"""
Encrypts plaintext using a Caesar cipher.
Uses the precomputed plain -> encrypted character map to calculate each character
of the resulting ciphertext
"""
ciphertext = ""
for char in plaintext.upper():
ciphertext += caesar_encryption_mapping[char]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is well done - but a more pythonic way might have been to put it within a list comprehension

return ciphertext


def decrypt_caesar(ciphertext):
"""
Decrypts a ciphertext using a Caesar cipher.
Uses the precomputed encrypted -> plain character map to calculate each character
of the resulting plaintext
"""
plaintext = ""
for char in ciphertext.upper():
plaintext += caesar_decryption_mapping[char]
return plaintext


def encrypt_vigenere(plaintext, keyword):
"""
Encrypts plaintext using a Vigenere cipher with a keyword.
Generates the length-matching keyword with the appropriate list slice and shifts
the characters by total sum of the keyword character and the plaintext character
at a specific index (e.g. A -> B if the total sum is 1). The characters wrap around
if too large.
"""

plaintext = plaintext.upper()
keyword = keyword.upper()
matching_length_key = (keyword * math.ceil(len(plaintext) / len(keyword)))[:len(plaintext)]

ciphertext = ""
for index, char in enumerate(plaintext):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job using enumerate

total_value = (ord(char) - ascii_base) + (ord(matching_length_key[index]) - ascii_base)
ciphertext += chr(ascii_base + (total_value % 26))
return ciphertext


def decrypt_vigenere(ciphertext, keyword):
"""
Decrypts ciphertext using a Vigenere cipher with a keyword.
Generates the corresponding plaintext using the exact opposite process as that used above,
subtracting the total key value from the character value to reverse the process.
"""
ciphertext = ciphertext.upper()
keyword = keyword.upper()
matching_length_key = (keyword * math.ceil(len(ciphertext) / len(keyword)))[:len(ciphertext)]

plaintext = ""
for index, char in enumerate(ciphertext):
total_value = (ord(char) - ascii_base) - (ord(matching_length_key[index]) - ascii_base)
plaintext += chr(ascii_base + (total_value % 26))
return plaintext


def num_letters_in_rail(text, rail, num_rails):
"""
Calculates the number of letters in a specific rail index for the railfence cipher.
Allocates one character per 'up' or 'down' sequence of characters to each of the
intermediary rails and half of that number to the first and last rails. Then distributes
the extra characters at the end of the string to the appropriate rails.
"""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job with the documentation!


num_segments = len(text) // (num_rails - 1)
if rail == 0:
letters_in_rail = math.ceil(num_segments / 2)
elif rail == num_rails - 1:
letters_in_rail = num_segments // 2
else:
letters_in_rail = num_segments

extra_letters = len(text) % (num_rails - 1)
if extra_letters != 0 and ((num_segments % 2 == 0 and rail < extra_letters) or \
(num_segments % 2 == 1 and rail >= num_rails - extra_letters)):
letters_in_rail += 1
return letters_in_rail


def encrypt_railfence(plaintext, num_rails):
"""
Encrypts plaintext using a railfence cipher.
For the first and last rail, takes a slice of the plaintext of characters every
(n_rails -1)*2 indices. For each of the intermediate rails, the width of the slice
is the same, but two slices must be made and interwoven to create the complete
representation of the rail
"""
if num_rails == 1:
return plaintext

ciphertext = ""
for r in range(num_rails):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra line here

rail = [None] * num_letters_in_rail(plaintext, r, num_rails)
if r > 0 and r < num_rails - 1: # not the last rail or the first one
rail[::2] = plaintext[r::(num_rails - 1)*2]
rail[1::2] = plaintext[r + (num_rails - r - 1)*2::(num_rails - 1)*2]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done here!

else:
rail[::] = plaintext[r::(num_rails - 1)*2]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job here!


ciphertext += "".join(rail)

return ciphertext


def decrypt_railfence(ciphertext, num_rails):
"""
Decrypts ciphertext using a railfence cipher.
Calculates the length of each rail, and assigns the slice of the ciphertext representing
the rail to be staggered by (num_rails-1)*2 indices in the plaintext. Does two passes
over intermediary rails and one over the first and last rails.
"""
if num_rails == 1:
return ciphertext

plaintext_arr = [None] * len(ciphertext)
cipher_index = 0

for r in range(num_rails):
num_letters = num_letters_in_rail(ciphertext, r, num_rails)
if r > 0 and r < num_rails - 1:
plaintext_arr[r::(num_rails - 1)*2] = ciphertext[cipher_index:cipher_index+num_letters:2]
plaintext_arr[r + (num_rails - r - 1)*2::(num_rails - 1)*2] = ciphertext[cipher_index + 1:cipher_index+num_letters:2]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah very nice, very pythonic!

else:
plaintext_arr[r::(num_rails - 1)*2] = ciphertext[cipher_index:cipher_index+num_letters]

cipher_index += num_letters

return "".join(plaintext_arr)


def read_from_file(filename):
"""
Reads and returns content from a file.
Combines all lines, strips all non-alpha characters, and uppercases all characters.
Returns the parsed contents of the file in one string.
"""
with open(filename, 'r') as f:
lines = f.readlines()
return strip_nonalpha("".join(lines)).upper()


def write_to_file(filename, content):
"""
Writes content to a file.
The content is written as is to the file.
"""
with open(filename, 'w') as f:
f.write(content)


def strip_nonalpha(str):
"""
Strips all non-alpha characters from the given string
"""
return "".join([c for c in str if c.isalpha()])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicely done here - filter might be a little more appropriate



def run_crypto_algorithm(encrypt, text, filename):
"""
Prompts the user to run a specific cryptography algorithm, one of Caesar, Vigenere, or Railfence.
Reprompts until it receives valid input.
param encrypt: Boolean, indicates whether to encrypt or decrypt the input
param text: input value to be encrypted/decrypted
param filename: if the input was read from a file, the filename is given
"""
while True:
encryption_type = strip_nonalpha(input("(C)aesar, (V)igenere, or (R)ailfence? ")).upper()
if encryption_type == "C":
return run_caesar(encrypt, text, filename)
elif encryption_type == "V":
return run_vigenere(encrypt, text, filename)
elif encryption_type == "R":
return run_railfence(encrypt, text, filename)


def run_caesar(encrypt, text, filename):
"""
Runs either caesar encryption or decryption on the input text for the suite
"""
if encrypt:
if filename:
print("Encrypting contents of "+filename+" using Caesar cipher")
else:
print("Encrypting "+text+" using Caesar cipher")
print("...")
return encrypt_caesar(text)
else:
if filename:
print("Decrypting contents of "+filename+" using Caesar cipher")
else:
print("Decrypting "+text+" using Caesar cipher")
print("...")
return decrypt_caesar(text)


def run_vigenere(encrypt, text, filename):
"""
Runs either Vigenere encryption or decryption on the input text. Also asks the user for a valid
passkey, which is stripped of non-alpha and uppercased.
"""

passkey = strip_nonalpha(input("Passkey? ")).upper()
if encrypt:
if filename:
print("Encrypting contents of "+filename+" using Vigenere cipher with key "+passkey)
else:
print("Encrypting "+text+" using Vigenere cipher with key "+passkey)
print("...")
return encrypt_vigenere(text, passkey)
else:
if filename:
print("Decrypting contents of "+filename+" using Vigenere cipher with key "+passkey)
else:
print("Decrypting "+text+" using Vigenere cipher with key "+passkey)
print("...")
return decrypt_vigenere(text, passkey)


def run_railfence(encrypt, text, filename):
"""
Runs either railfence encryption or decyrption on the input text. Also asks the user for a valid
positive number of rails.
"""

num_rails = int(input("Positive number of rails? "))
if encrypt:
if filename:
print("Encrypting contents of "+filename+" using Railfence cipher with "+str(num_rails)+" rails")
else:
print("Encrypting "+text+" using Railfence cipher with "+str(num_rails)+" rails")
print("...")
return encrypt_railfence(text, num_rails)
else:
if filename:
print("Decrypting contents of "+filename+" using Railfence cipher with "+str(num_rails)+" rails")
else:
print("Decrypting "+text+" using Railfence cipher with "+str(num_rails)+" rails")
print("...")
return decrypt_railfence(text, num_rails)


def run_suite():
"""
Runs a single iteration of the cryptography suite.

Asks the user for input text from a string or file, whether to encrypt
or decrypt, what tool to use, and where to show the output.
"""
while True:
print("*Input*")
filename = ""
while True:
option = strip_nonalpha(input("(F)ile or (S)tring? ")).upper()
if option == "F":
filename = input("Filename? ")
input_text = read_from_file(filename)
inputIsFile = True
break
elif option == "S":
input_text = strip_nonalpha(input("Enter the string to encrypt: ")).upper()
break

print("*Transform*")
while True:
option = strip_nonalpha(input("(E)ncrypt or (D)ecrypt? ")).upper()
if option == "E" or option == "D":
output_text = run_crypto_algorithm(option == "E", input_text, filename)
break

print("*Output*")
while True:
option = strip_nonalpha(input("(F)ile or (S)tring? ")).upper()
if option == "F":
filename = input("Filename? ")
print("Writing ciphertext to "+filename+"...")
write_to_file(filename, output_text)
break
elif option == "S":
print("The plaintext is:", output_text)
break

again = strip_nonalpha(input("Again (Y/N)? ")).upper()
if again == "N":
break


# Do not modify code beneath this point.
def should_continue():
"""
Asks the user whether they would like to continue.
Responses that begin with a `Y` return True. (case-insensitively)
Responses that begin with a `N` return False. (case-insensitively)
All other responses (including '') cause a reprompt.
"""
choice = input("Again (Y/N)? ").upper()
while not choice or choice[0] not in ['Y', 'N']:
choice = input("Please enter either 'Y' or 'N'. Again (Y/N)? ").upper()
return choice[0] == 'Y'


def main():
"""Harness for the Cryptography Suite"""
print("Welcome to the Cryptography Suite!")
run_suite()
while should_continue():
run_suite()
print("Goodbye!")


if __name__ == '__main__':
"""This block is run if and only if the Python script is invoked from the
command line."""
main()
Binary file added jlwatson-assign1/crypto.pyc
Binary file not shown.
22 changes: 22 additions & 0 deletions jlwatson-assign1/feedback.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Name: Jean-Luc Watson
1) How long did this assignment take you to complete?
It took me about 5 or 6 hours, mostly because I got a little stuck on the Railfence algorithm and I spent some good amount of time trying to decrypt one of the vigenere file with the wrong key :D

2) What has been the best part of the class so far?
Definitely the help in class - during both the lectures and the labs, it's really nice to be able to get a second look at a problem or attempt at writing code.




3) What can we do to make this class more enjoyable for you?
Keep up the good work!




4) What types of assignments would excite you in this class?
I'm really excited to work on whatever assignments you come up with, but maybe something that edits sound waves/music to get some cool effects.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! We appreciate the candid feedback!





1 change: 1 addition & 0 deletions jlwatson-assign1/railfence-cipher.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WECRLTEERDSOEEFEAOCAIVDEN
1 change: 1 addition & 0 deletions jlwatson-assign1/railfence-plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WE ARE DISCOVERED. FLEE AT ONCE
1 change: 1 addition & 0 deletions jlwatson-assign1/secret_message.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
KHOORZRUOG
1 change: 1 addition & 0 deletions jlwatson-assign1/vigenere-cipher.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LXFOPVEFRNHR
1 change: 1 addition & 0 deletions jlwatson-assign1/vigenere-plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Attack At Dawn!