VTI_aux/code/crypto/monoalpha_tool.py

166 lines
7.3 KiB
Python
Executable File

#!/usr/bin/python3
################################################
### example program for VTI class
### caesar cipher encryption tool
### licensed under WTFPL, feel free to steal
### http://www.wtfpl.net/about/
################################################
from collections import OrderedDict, Counter
from itertools import islice, cycle
import re
import argparse
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
possible_rot = (self.characters.index(str(most_common_letters[0][0])) - 4) % len(self.characters)
return possible_rot
def parse_custom_dict(self, key):
inp_raw = key.strip("\"").replace(" ", "") #strip enclosing quotations and remove 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:
print("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:
print("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:
print("Warning, some keys have duplicate values. They will not be used. Duplicates: %s" % str(duplicates))
return(filtered_dict)
except Exception as e:
print("Problem parsing custom dictionary. Please check your input.\n%s" % e)
return
def run_encryption(text, shift):
print(enc_tools.encrypt_text(text, shift))
return 0
def run_custom_encryption(text, key):
custom_key = enc_tools.parse_custom_dict(key)
if custom_key is not None:
print(enc_tools.custom_dict_encrypt(text, custom_key))
return 0
else:
print("Encryption failed\n")
return 1
def run_decryption(text, shift):
print(enc_tools.decrypt_text(text, shift))
return 0
def run_freq_analysis_decryption(text):
print("Trying to decrypt using frequency analysis. This method works best on large ciphertexts.")
shift = enc_tools.guess_rot(text)
print("Text possibly encrypted using rot%s\n" % str(shift))
print(enc_tools.decrypt_text(text, shift))
return 0
enc_tools = EncryptionTools()
argparser = argparse.ArgumentParser(description="Caesar cipher encoding/decoding tool.")
argparser.add_argument("-s", "--shift", type=int, default=13, help="Use custom alphabet shift (default is rot13)")
argparser.add_argument("-k", "--key", help="Use custom alphabet, input as \"A:N, B:O, ...\" with substitution for each character")
argparser.add_argument("-d", "--decrypt", action="store_const", const="decrypt", help="Set mode to decode")
argparser.add_argument("-f", "--force", action="store_const", const="force", help="Try to guess original encryption")
argparser.add_argument("text", type=str, help="Text to be encoded or decoded")
args = argparser.parse_args()
if args.force and not args.decrypt:
print("Frequency analysis can only be used to decrypt text")
elif args.key:
run_custom_encryption(args.text, args.key)
elif args.decrypt and args.force:
run_freq_analysis_decryption(args.text)
elif args.decrypt:
run_decryption(args.text, args.shift)
else:
run_encryption(args.text, args.shift)