VTI_aux/code/crypto/monoalpha_tool_gui.py

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()