386 lines
16 KiB
Python
386 lines
16 KiB
Python
#!/usr/bin/python3
|
|
|
|
################################################
|
|
### example program for VTI class
|
|
### caesar cipher encryption tool with tk UI
|
|
### licensed under WTFPL, feel free to steal
|
|
### http://www.wtfpl.net/about/
|
|
################################################
|
|
|
|
|
|
from tkinter import *
|
|
from tkinter import messagebox, scrolledtext
|
|
from collections import OrderedDict, Counter
|
|
from itertools import islice, cycle
|
|
import re
|
|
|
|
class EncryptionTools(object):
|
|
def __init__(self):
|
|
self.characters = ["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"]
|
|
|
|
def make_cipher(self, shift):
|
|
alphabet = OrderedDict() #ordered dict is used to allow for shifting and to remember character positions
|
|
for char in self.characters:
|
|
alphabet[char] = char #first an unshifted alphabet dict is created
|
|
|
|
shift %= len(alphabet) #mod shift value by alphabet length to keep shift in range of alphabet in case of shift input as number >26 or negative number
|
|
cipher_alphabet = OrderedDict(
|
|
(k, v)
|
|
for k, v in zip(alphabet.keys(), islice(cycle(alphabet.values()), shift, None)) #build cipher dict by shifting values of alphabet dict
|
|
)
|
|
|
|
return cipher_alphabet
|
|
|
|
def make_reverse_cipher(self, alphabet):
|
|
inverse_alphabet = OrderedDict()
|
|
for key, value in alphabet.items():
|
|
inverse_alphabet[value] = key #keys and values are swapped to create inverse alphabet
|
|
|
|
return inverse_alphabet
|
|
|
|
def encrypt_text(self, text, shift):
|
|
alphabet = self.make_cipher(shift)
|
|
text = re.sub(r"[^A-Z]", "", text.upper()) #strip everything that is not uppercase letters
|
|
encrypted_text = []
|
|
|
|
for letter in text: #convert text to uppercase only
|
|
encrypted_text.append(alphabet.get(letter, letter)) #replace each letter with the corresponding cipher letter from the shifted alphabet
|
|
|
|
return str("".join(encrypted_text)) #since the string was processed letter by letter, it needs to be joined back up
|
|
|
|
def custom_dict_encrypt(self, text, custom_dict): #functionally the same, uses user entered dictionary as cipher
|
|
alphabet = custom_dict
|
|
text = re.sub(r"[^A-Z]", "", text.upper())
|
|
encrypted_text = []
|
|
|
|
for letter in text.upper():
|
|
encrypted_text.append(alphabet.get(letter, letter))
|
|
|
|
return str("".join(encrypted_text))
|
|
|
|
#It is possible to use the same function for both encryption and decryption of text just by shifting the alphabet a negative amount
|
|
#since such input is sanitized in the cipher making function.
|
|
#However, to avoid any confusion, here is a separate decryption function (and separate decryption mode).
|
|
|
|
def decrypt_text(self, text, shift):
|
|
inverse_alphabet = self.make_reverse_cipher(self.make_cipher(shift))
|
|
text = re.sub(r"[^A-Z]", "", text.upper())
|
|
decrypted_text = []
|
|
|
|
for letter in text.upper():
|
|
decrypted_text.append(inverse_alphabet.get(letter, letter))
|
|
|
|
return str("".join(decrypted_text))
|
|
|
|
def guess_rot(self, text):
|
|
text = re.sub(r"[^A-Z]", "", text.upper())
|
|
most_common_letters = Counter(text.upper()).most_common(1) #find the most common ciphertext character, this is very likely E in english texts
|
|
#assuming the most frequent character in ciphertext to indeed represent plaintext E, calculate its offset from plaintext alphabet E.
|
|
#if the assumption is correct, we get the encoding offset
|
|
#guessed offset is calculated with mod 26 to keep the resulting integer positive in case of large offsets
|
|
# print(most_common_letters)
|
|
possible_rot = (self.characters.index(str(most_common_letters[0][0])) - 4) % len(self.characters)
|
|
# print(possible_rot)
|
|
|
|
return possible_rot
|
|
|
|
#following code is for the Tk gui and some auxiliary functions
|
|
|
|
class Window():
|
|
def __init__(self):
|
|
self.window = Tk()
|
|
self.window.title("Caesar cipher text \"encryption\" tool")
|
|
self.window.geometry("800x600")
|
|
self.window.resizable(0, 0)
|
|
self.enc_tools = EncryptionTools()
|
|
self.active_screen = EncScreen(self)
|
|
self.active_screen.render()
|
|
|
|
def switch_screen(self, screen):
|
|
self.active_screen.input_frame.pack_forget()
|
|
self.active_screen.option_frame.pack_forget()
|
|
self.active_screen.output_frame.pack_forget()
|
|
|
|
if screen == "encrypt":
|
|
self.active_screen = None
|
|
self.active_screen = EncScreen(self)
|
|
elif screen == "decrypt":
|
|
self.active_screen = None
|
|
self.active_screen = DecScreen(self)
|
|
|
|
self.active_screen.render()
|
|
|
|
def run(self):
|
|
self.window.mainloop()
|
|
|
|
class GoButton(Button):
|
|
def __init__(self, parent, name, action):
|
|
super().__init__(parent, text=name, command=action)
|
|
|
|
def render(self):
|
|
self.pack(side=LEFT, padx=10, pady=5)
|
|
|
|
class SwitchButton(Button):
|
|
def __init__(self, parent, name, action):
|
|
super().__init__(parent, text=name, command=action)
|
|
|
|
def render(self):
|
|
self.pack(side=RIGHT, padx=10, pady=5)
|
|
|
|
class EncInputFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.input_label = Label(self, text="Enter text to encrypt")
|
|
self.text_input = scrolledtext.ScrolledText(self, width=100, height=15)
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.input_label.pack(anchor=N, pady=2.5)
|
|
self.text_input.pack(anchor=N, padx=15, pady=2.5)
|
|
self.text_input.focus_set()
|
|
|
|
class EncOptionFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.parent = parent
|
|
self.sel_option = StringVar()
|
|
self.sel_option.set("shift")
|
|
self.option_label = Label(self, text="Choose encryption options")
|
|
self.dict_label = Label(self, text="Input custom dictionary as A:N, B:O, ... with value for each letter")
|
|
self.cipher_shift_inp = Entry(self, width=5)
|
|
self.encryption_go_button = GoButton(self, "Encrypt", self.encrypt_and_print)
|
|
self.switch_screen_button = SwitchButton(self, "Switch mode", self.make_switch)
|
|
self.custom_dict_entry = Entry(self, width=30)
|
|
self.choose_shift_rbutton = Radiobutton(self, text="Alphabet shift", variable=self.sel_option, value="shift", command=self.select_shift)
|
|
self.choose_dict_rbutton = Radiobutton(self, text="Custom dictionary", variable=self.sel_option, value="dict", command=self.select_dict)
|
|
|
|
def select_shift(self):
|
|
self.dict_label.place_forget()
|
|
self.cipher_shift_inp.configure(state="normal")
|
|
self.cipher_shift_inp.focus_set()
|
|
self.custom_dict_entry.configure(state="disabled")
|
|
|
|
def select_dict(self):
|
|
self.dict_label.place(x=210, y=15)
|
|
self.cipher_shift_inp.configure(state="disabled")
|
|
self.custom_dict_entry.configure(state="normal")
|
|
self.custom_dict_entry.focus_set()
|
|
|
|
def make_switch(self):
|
|
self.parent.switch_screen("decrypt")
|
|
|
|
def parse_custom_dict(self):
|
|
inp_raw = self.parent.active_screen.option_frame.custom_dict_entry.get().replace(" ", "") #read from input field, strip all whitespace
|
|
try:
|
|
inp_pairs = inp_raw.split(",") #split input into "key:value" strings
|
|
inp_kvpairs = []
|
|
|
|
for i in inp_pairs:
|
|
inp_kvpairs.append(inp_pairs[inp_pairs.index(i)].split(":")) #split "key:value" strings into separate ["key", "value"] pairs
|
|
except Exception as e:
|
|
messagebox.showerror("Error", "Problem processing dictionary input.\n%s" % e)
|
|
return
|
|
|
|
try:
|
|
custom_dict = OrderedDict(
|
|
(k.upper(), v.upper()) #convert custom dictionary keys and values to uppercase
|
|
for k, v in inp_kvpairs) #assign keys and values
|
|
|
|
filtered_dict = OrderedDict()
|
|
duplicates = []
|
|
|
|
#filter out duplicate values. Duplicate keys are not a problem, each reassignment only changes the value
|
|
for k, v in custom_dict.items():
|
|
if k.isalpha() == 0 or v.isalpha() == 0:
|
|
messagebox.showerror("Error", "Only alphabetic characters are accepted for custom dictionary")
|
|
return
|
|
if v not in filtered_dict.values():
|
|
filtered_dict[k] = v
|
|
else:
|
|
duplicates.append(k)
|
|
|
|
#warn user if duplicates are detected but proceed anyway
|
|
if len(duplicates) > 0:
|
|
messagebox.showwarning("Warning", "Some keys have duplicate values. They will not be used.\nDuplicates: %s" % str(duplicates))
|
|
|
|
return(filtered_dict)
|
|
except Exception as e:
|
|
messagebox.showerror("Error", "Problem parsing custom dictionary.\nPlease check your input.\n%s" % e)
|
|
return
|
|
|
|
def encrypt_and_print(self):
|
|
if self.sel_option.get() == "shift":
|
|
try:
|
|
shift = int(self.cipher_shift_inp.get().replace(" ", ""))
|
|
except:
|
|
messagebox.showerror("Error", "Please input a numeric value for alphabet shift")
|
|
self.cipher_shift_inp.delete(0, END)
|
|
return
|
|
|
|
text = self.parent.active_screen.input_frame.text_input.get("1.0", END)
|
|
payload = self.parent.enc_tools.encrypt_text(text, shift)
|
|
|
|
elif self.sel_option.get() == "dict":
|
|
custom_dict = self.parse_custom_dict()
|
|
if custom_dict is not None:
|
|
try:
|
|
text = self.parent.active_screen.input_frame.text_input.get("1.0", END)
|
|
payload = self.parent.enc_tools.custom_dict_encrypt(text, custom_dict)
|
|
except Exception as e:
|
|
messagebox.showerror("Error", "A problem has occured:\n%s" % e)
|
|
return
|
|
else:
|
|
return
|
|
|
|
self.parent.active_screen.output_frame.text_output.configure(state="normal")
|
|
self.parent.active_screen.output_frame.text_output.delete("1.0", END)
|
|
self.parent.active_screen.output_frame.text_output.insert("end", payload)
|
|
self.parent.active_screen.output_frame.text_output.see("end")
|
|
self.parent.active_screen.output_frame.text_output.configure(state="disabled")
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.option_label.pack(anchor=NW, padx=15, pady=5)
|
|
self.choose_shift_rbutton.pack(side=LEFT, padx=15)
|
|
self.cipher_shift_inp.pack(side=LEFT, padx=0)
|
|
self.choose_dict_rbutton.pack(side=LEFT)
|
|
self.custom_dict_entry.pack(side=LEFT, padx=1, pady=5)
|
|
self.custom_dict_entry.configure(state="disabled")
|
|
self.encryption_go_button.render()
|
|
self.switch_screen_button.render()
|
|
|
|
class EncOutputFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.input_label = Label(self, text="Encrypted text")
|
|
self.text_output = scrolledtext.ScrolledText(self, width=100, height=15)
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.input_label.pack(anchor=N, pady=2.5)
|
|
self.text_output.pack(anchor=S, padx=15, pady=2.5)
|
|
self.text_output.configure(state="disabled")
|
|
self.text_output.bind("<1>", lambda event: self.text_output.focus_set()) #set to allow selecting and copying text from the frame
|
|
|
|
class DecInputFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.input_label = Label(self, text="Enter text to decrypt")
|
|
self.text_input = scrolledtext.ScrolledText(self, width=100, height=15)
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.input_label.pack(anchor=N, pady=2.5)
|
|
self.text_input.pack(anchor=N, padx=15, pady=2.5)
|
|
self.text_input.focus_set()
|
|
|
|
class DecOptionFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.parent = parent
|
|
self.sel_option = StringVar()
|
|
self.option_label = Label(self, text="Choose decryption options")
|
|
self.cipher_shift_inp = Entry(self, width=5)
|
|
self.decryption_go_button = GoButton(self, "Decrypt", self.decrypt_and_print)
|
|
self.switch_screen_button = SwitchButton(self, "Switch mode", self.make_switch)
|
|
self.choose_shift_rbutton = Radiobutton(self, text="Alphabet shift", variable=self.sel_option, value="shift", command=self.select_shift)
|
|
self.choose_guess_rbutton = Radiobutton(self, text="Guess shift", variable=self.sel_option, value="guess", command=self.select_guess)
|
|
|
|
def make_switch(self):
|
|
self.parent.switch_screen("encrypt")
|
|
|
|
def select_shift(self):
|
|
if self.parent.active_screen.output_frame.type_label is not None:
|
|
self.parent.active_screen.output_frame.type_label.place_forget()
|
|
self.cipher_shift_inp.configure(state="normal")
|
|
|
|
def select_guess(self):
|
|
self.cipher_shift_inp.configure(state="disabled")
|
|
|
|
def guess_and_decrypt(self):
|
|
self.decrypt_and_print(self.parent.enc_tools.guess_rot(self.parent.active_screen.input_frame.text_input.get("1.0", END)))
|
|
|
|
def decrypt_and_print(self):
|
|
|
|
text = self.parent.active_screen.input_frame.text_input.get("1.0", END)
|
|
|
|
if self.sel_option.get() == "shift":
|
|
try:
|
|
shift = int(self.cipher_shift_inp.get().replace(" ",""))
|
|
except:
|
|
messagebox.showerror("Error", "Please input a numeric value for alphabet shift")
|
|
self.cipher_shift_inp.delete(0, END)
|
|
return
|
|
elif self.sel_option.get() == "guess":
|
|
try:
|
|
shift = self.parent.enc_tools.guess_rot(text)
|
|
except Exception as e:
|
|
messagebox.showerror("Error", "Problem guessing encoding\n%s" % e)
|
|
return
|
|
|
|
payload = self.parent.enc_tools.decrypt_text(text, shift)
|
|
|
|
if self.parent.active_screen.output_frame.type_label is not None:
|
|
self.parent.active_screen.output_frame.type_label.place_forget()
|
|
if self.sel_option.get() == "guess":
|
|
self.parent.active_screen.output_frame.type_label = Label(self, text="Text was (likely) encoded with rot%s" % str(shift))
|
|
self.parent.active_screen.output_frame.type_label.place(x=390, y=39)
|
|
self.parent.active_screen.output_frame.text_output.configure(state="normal")
|
|
self.parent.active_screen.output_frame.text_output.delete("1.0", END)
|
|
self.parent.active_screen.output_frame.text_output.insert("end", payload)
|
|
self.parent.active_screen.output_frame.text_output.see("end")
|
|
self.parent.active_screen.output_frame.text_output.configure(state="disabled")
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.option_label.pack(anchor=NW, padx=15, pady=5)
|
|
self.choose_shift_rbutton.pack(side=LEFT, padx=15)
|
|
self.cipher_shift_inp.pack(side=LEFT, padx=0)
|
|
self.choose_guess_rbutton.pack(side=LEFT)
|
|
self.decryption_go_button.render()
|
|
self.switch_screen_button.render()
|
|
self.sel_option.set("shift")
|
|
|
|
class DecOutputFrame(Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent.window)
|
|
self.input_label = Label(self, text="Decrypted text")
|
|
self.text_output = scrolledtext.ScrolledText(self, width=100, height=15)
|
|
self.type_label = None
|
|
|
|
def render(self):
|
|
self.pack(fill=X)
|
|
self.input_label.pack(anchor=N, pady=2.5)
|
|
self.text_output.pack(anchor=S, padx=15, pady=2.5)
|
|
self.text_output.configure(state="disabled")
|
|
self.text_output.bind("<1>", lambda event: self.text_output.focus_set())
|
|
|
|
class EncScreen():
|
|
def __init__(self, parent):
|
|
self.input_frame = EncInputFrame(parent)
|
|
self.option_frame = EncOptionFrame(parent)
|
|
self.output_frame = EncOutputFrame(parent)
|
|
|
|
def render(self):
|
|
self.input_frame.render()
|
|
self.option_frame.render()
|
|
self.output_frame.render()
|
|
|
|
class DecScreen():
|
|
def __init__(self, parent):
|
|
self.input_frame = DecInputFrame(parent)
|
|
self.option_frame = DecOptionFrame(parent)
|
|
self.output_frame = DecOutputFrame(parent)
|
|
|
|
def render(self):
|
|
self.input_frame.render()
|
|
self.option_frame.render()
|
|
self.output_frame.render()
|
|
|
|
def main():
|
|
window = Window()
|
|
window.switch_screen("encrypt") #start in encryption mode
|
|
window.run()
|
|
|
|
if __name__ == "__main__":
|
|
main() |