252 lines
10 KiB
Python
Executable File
252 lines
10 KiB
Python
Executable File
'''
|
|
6-Letter Pypher 1.0
|
|
Copyright (c) 2015 Ben Goldsworthy (rumps)
|
|
|
|
A program to encode and decode text using a 6-letter cipher and given
|
|
keyword.
|
|
|
|
6-Letter Pypher is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
6-Letter Pypher is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with 6-Letter Pypher. If not, see <http://www.gnu.org/licenses/>.
|
|
'''
|
|
|
|
import numpy as np
|
|
import string
|
|
import random
|
|
import re
|
|
import pickle
|
|
import operator
|
|
|
|
# A dictionary used for indexing characters in the initial alphanumeric grid
|
|
alphaNumCoords = {0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F'}
|
|
|
|
def encodePlaintext():
|
|
"""Coordinates the steps involved in encoding plaintext and prints results."""
|
|
fileName = genFileName(4)
|
|
alphaNumGrid = makeAlphaNumGrid(fileName)
|
|
|
|
plaintext = enterPlaintext()
|
|
keyword = sorted(enterKeyword(len(plaintext)))
|
|
print "\nEncoding", plaintext, "with keyword", getKeywordString(keyword), "...\n"
|
|
print "--Alphanumeric grid--\n", alphaNumGrid
|
|
|
|
ciphertext = getTransitionalCiphertext(plaintext, alphaNumGrid)
|
|
print "Transitional ciphertext:", ciphertext, "\n"
|
|
|
|
keywordGrid = getKeywordGrid(ciphertext, keyword, 'encode')
|
|
print "--Keyword grid--\n", keywordGrid
|
|
ciphertext = getCiphertext(keywordGrid)
|
|
print "Final ciphertext:", ciphertext, "\nEncoded using grid", fileName
|
|
def decodeCiphertext():
|
|
"""Coordinates the steps involved in decoding ciphertext and prints results."""
|
|
filename = raw_input("Enter grid code: ")
|
|
alphaNumGrid = pickle.load(open("Ciphers/"+filename+".p", "rb" ))
|
|
|
|
ciphertext = enterCiphertext()
|
|
keyword = enterKeyword(len(ciphertext)/2)
|
|
keywordGrid = getKeywordGrid(ciphertext, keyword, 'decode')
|
|
print "\nDecoding", ciphertext, "with keyword", getKeywordString(keyword), "using grid", filename, "...\n"
|
|
print "--Keyword grid--\n", keywordGrid
|
|
|
|
plaintext = getTransitionalPlaintext(keywordGrid)
|
|
print "Transitional plaintext:", plaintext, "\n"
|
|
|
|
print "--Alphanumeric grid--\n", alphaNumGrid
|
|
plaintext = getPlaintext(plaintext, alphaNumGrid)
|
|
print "Final plaintext:", plaintext
|
|
def showCredits():
|
|
"""Prints the credits and copyright information."""
|
|
print "*" * 80
|
|
print "\t\t\t6-Letter Pypher 1.0"
|
|
print "\t\tCopyright (c) 2015 Ben Goldsworthy (rumps)\n"
|
|
print " A program to encode and decode text using a 6-letter cipher and given\n keyword.\n "
|
|
print " 6-Letter Pypher is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n "
|
|
print " 6-Letter Pypher is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU General Public License for more details.\n "
|
|
print " You should have received a copy of the GNU General Public License\n along with 6-Letter Pypher. If not, see <http://www.gnu.org/licenses/>."
|
|
print "*" * 80
|
|
|
|
def makeAlphaNumGrid(fileName):
|
|
"""Generates an alphanumeric grid and pickles it for later consumption."""
|
|
vals = list(string.ascii_uppercase + string.digits)
|
|
random.shuffle(vals)
|
|
grid = np.array(vals).reshape(6,6)
|
|
pickle.dump(grid, open("Ciphers/"+fileName+".p", "wb"))
|
|
return grid
|
|
def genFileName(n):
|
|
"""Generates a randomised filename for the pickle."""
|
|
name = ""
|
|
for x in range(n): name += random.choice(string.letters + string.digits)
|
|
return name
|
|
def findLetter(letter, x):
|
|
"""Finds a letter in the alphanumeric grid and returns its coordinates."""
|
|
for i in range(6):
|
|
for j in range(6):
|
|
if x[i][j] == letter:
|
|
return alphaNumCoords[j] + alphaNumCoords[i]
|
|
return ''
|
|
|
|
def enterPlaintext():
|
|
"""Takes and validates plaintext entry from the user. Returns valid entry
|
|
stripped of whitespace.
|
|
"""
|
|
plaintext = ""
|
|
# RegEx for non-alphanumeric characters
|
|
alphaNum = re.compile(r'[^a-zA-Z0-9 ]')
|
|
|
|
while plaintext == "":
|
|
err = False
|
|
plaintext = raw_input("What is your plaintext? ").upper()
|
|
if alphaNum.search(plaintext) != None:
|
|
print "ERR: Plaintext must contain characters A-Z or 0-9"
|
|
err = True
|
|
if err == True: plaintext = ""
|
|
# Includes RegEx to strip whitespace
|
|
return re.sub(r'\W+', '', plaintext)
|
|
def enterKeyword(plaintextLen):
|
|
"""Takes and validates keyword entry from the user. Returns an list of
|
|
indexed tuples representing the keyword string.
|
|
"""
|
|
keyword = ""
|
|
# RegEx for detecting repeated or invalid characters
|
|
rpt = re.compile(r'.*(.).*\1.*')
|
|
alpha = re.compile(r'[^a-zA-Z]')
|
|
|
|
while keyword == "":
|
|
err = False
|
|
keyword = raw_input("What is the keyword? ").upper()
|
|
if alpha.search(keyword) != None:
|
|
print "ERR: Keyword must contain characters A-Z"
|
|
err = True
|
|
if rpt.match(keyword):
|
|
print "ERR: Keyword must contain no repeating characters"
|
|
err = True
|
|
if plaintextLen % len(keyword) != 0:
|
|
print "ERR: Length of plaintext (", plaintextLen, \
|
|
") must be divisible by length of keyword (", len(keyword), ")"
|
|
err = True
|
|
if err == True: keyword = ""
|
|
return getKeyword(keyword)
|
|
def enterCiphertext():
|
|
"""Takes and validates ciphertext entry from the user."""
|
|
ciphertext = ""
|
|
# RegEx to detect invalid characters
|
|
alphaNum = re.compile(r'[^A-F]')
|
|
|
|
while ciphertext == "":
|
|
err = False
|
|
ciphertext = raw_input("Enter ciphertext: ").upper()
|
|
if alphaNum.search(ciphertext) != None:
|
|
print "ERR: Ciphertext must contain characters A-F"
|
|
err = True
|
|
if err == True: ciphertext = ""
|
|
return ciphertext
|
|
|
|
def getKeyword(keyword):
|
|
"""Returns a sorted list of tuples representing the keyword string."""
|
|
return sorted(enumerate(keyword), key=operator.itemgetter(1))
|
|
def getKeywordGrid(ciphertext, keyword, mode):
|
|
"""Generates the keyword grid from the entered keyword, then returns it
|
|
sorted appropriately (either alphabetically for encoding or back to keywordly
|
|
for decoding).
|
|
"""
|
|
keywordCipher = [x[1] for x in keyword]
|
|
keywordLength = len(keywordCipher)
|
|
for letter in ciphertext:
|
|
keywordCipher.append(letter)
|
|
|
|
yLength = len(ciphertext)/keywordLength + 1
|
|
keywordGrid = np.array(keywordCipher).reshape(yLength, keywordLength)
|
|
|
|
if mode == 'encode':
|
|
return keywordGrid[:, np.array(orderKeyword([x[1] for x in keyword]))]
|
|
elif mode == 'decode':
|
|
return keywordGrid[:, np.array([x[1] for x in getUnorderedKeyword(keyword)])]
|
|
def orderKeyword(keyword):
|
|
"""Orders the keyword characters alphabetically and returns the new
|
|
indicies.
|
|
"""
|
|
orderedKeyword = sorted(enumerate(keyword), key=operator.itemgetter(1))
|
|
return [x[0] for x in orderedKeyword]
|
|
def getUnorderedKeyword(keyword):
|
|
originalKeyword = sorted(keyword)
|
|
currIndex = 0
|
|
mylist = []
|
|
for i in range(len(getKeywordString(keyword))):
|
|
mylist.append((keyword[i][0], originalKeyword[i][0]))
|
|
return sorted(mylist)
|
|
|
|
def getKeywordString(keyword):
|
|
"""Extracts the keyword string from the list of tuples representing it."""
|
|
keywordString = ""
|
|
for x in sorted(keyword): keywordString += x[1]
|
|
return keywordString
|
|
def getTransitionalCiphertext(plaintext, alphaNumGrid):
|
|
"""Gets the ciphertext created by applying the alphanumeric grid to the
|
|
entered plaintext.
|
|
"""
|
|
ciphertext = ""
|
|
for letter in plaintext:
|
|
ciphertext += findLetter(letter, alphaNumGrid)
|
|
return ciphertext
|
|
def getCiphertext(keywordGrid):
|
|
"""Gets the final ciphertext created by applying the keyword grid to the
|
|
transitional ciphertext.
|
|
"""
|
|
ciphertext = ""
|
|
for row in keywordGrid[1:].tolist():
|
|
for letter in row:
|
|
ciphertext += letter
|
|
return ciphertext
|
|
def getTransitionalPlaintext(keywordGrid):
|
|
"""Splits the transitional plaintext into 2-character chunks."""
|
|
tpt = ""
|
|
double = 0
|
|
for x in keywordGrid[1:]:
|
|
for x in x:
|
|
if double == 2:
|
|
double = 0
|
|
tpt += " "
|
|
tpt += x
|
|
double += 1
|
|
return tpt
|
|
def getPlaintext(plaintext, alphaNumGrid):
|
|
"""Gets the final plaintext created by reversing the application of the
|
|
alphanumeric grid to the original plaintext.
|
|
"""
|
|
finalPlaintext = ""
|
|
for x in plaintext.split():
|
|
for k,v in alphaNumCoords.iteritems():
|
|
if v == x[1]: xcoord = k
|
|
if v == x[0]: ycoord = k
|
|
finalPlaintext += alphaNumGrid[xcoord][ycoord]
|
|
return finalPlaintext
|
|
|
|
def menu():
|
|
"""Displays the option menu for the user and takes input."""
|
|
option = ''
|
|
while option != 4:
|
|
print "\nSelect action:"
|
|
print "\t1. Encode plaintext"
|
|
print "\t2. Decode plaintext"
|
|
print "\t3. License & Credits"
|
|
print "\t4. Exit\n"
|
|
option = raw_input("Choice: ")
|
|
if option == '1':
|
|
encodePlaintext()
|
|
elif option == '2':
|
|
decodeCiphertext()
|
|
elif option == '3':
|
|
showCredits()
|
|
|
|
# Calls the menu on program start
|
|
menu()
|