Substitution Cipher - Solver/Decryption
This one was a bit harder to do as its clearly taken alot longer (mostly because the logic of my code was not correct 😢). However now it works relatively well it should be able to decrypt most substitution ciphers. From what I see the longer your ciphertext the less variation in the scores outputted at the end. With shorter texts it might not work as well. So below is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
import re, random from ngram_class import nGramInfo #substitution cipher def main(): # grabs input and makes it lower case ciphertext = input("Enter the message: ") ciphertext = ciphertext.lower() # removes everything except for letters this is used for calculating the healthiness ciphertext_only_letters = re.sub("[^a-z]+", "", ciphertext) alphabet = list("abcdefghijklmnopqrstuvwxyz") #randomly shuffles the alphabet random.shuffle(alphabet) starter_key = "".join(alphabet) #starter key is random arrangement of alphabet # calculates the best keys and prints them out in a decending order of healthiness # highest healthiness (most correct) will be at the bottom best_keys = calculate_best_key(ciphertext_only_letters, starter_key, ciphertext) best_keys = sorted(best_keys) for i in range(len(best_keys)): print("================\n"+"Score:", best_keys[i][0], "\nKey:", best_keys[i][1], "\nDecryption:", substitution(ciphertext, best_keys[i][1])) # print(best_keys) #prints out the best guess #its just the last thing anyway print("\n======== BEST GUESS ========") print("Score:", best_keys[-1][0]) print("Key:", best_keys[-1][1]) print("Decryption:", substitution(ciphertext, best_keys[-1][1])) def calculate_best_key(ciphertext_only_letters, starter_key, ciphertext): #creates object to calculate fitness calculator = create_nGramInfo_class() print("\n\n\nCiphertext:", ciphertext) print("Ciphertext Healthiness Score:", calculator.calculate_fitness_score(ciphertext)) highest = [] #highest holds the highest scores and the keys associated with the scores # this is the key that is shuffled into a new key every iteration key = list('abcdefghijklmnopqrstuvwxyz') # if the thing cant be solved iterations should be increased first iteration = 0 while iteration < 15: print("Iteration:", iteration) #generates new key and calculates the fitness of the keys associated possible plaintext random.shuffle(key) new_key = "".join(key) print("new key:", new_key) high_score = calculator.calculate_fitness_score(substitution(ciphertext_only_letters, new_key)) best_key = new_key highest.append([high_score, best_key]) iteration += 1 i = 0 # this can be increased if solver not solving however changing iteration number should be first while i < 10000: prev_key = new_key #generates 2 random numbers (musnt be the same) num1 = num2 = 0 while num1 == num2: num1 = random.randint(0, 25) num2 = random.randint(0, 25) #swaps the letters new = list(new_key) new[num1], new[num2] = new[num2], new[num1] new_key = "".join(new) #creates the possible plaintext and calculates fitness plaintext = substitution(ciphertext_only_letters, new_key) new_score = calculator.calculate_fitness_score(plaintext) #if new score is higher than high score new score becomes high score and is added to highest list if new_score > high_score: high_score = new_score best_key = new_key highest.append([high_score, best_key]) if len(highest) > 20: highest = sorted(highest) highest = highest[5:] else: #if score doesnt increase it goes back to previous key new_key = prev_key i += 1 # summarises information in every iteration print("Current highest score is", high_score, "on iteration", iteration) print(best_key) print("This decodes to", substitution(ciphertext_only_letters, best_key)) print("\n\n") return highest def create_nGramInfo_class(): print("Enter '1' for monograms") print("Enter '2' for bigrams") print("Enter '3' for trigrams") print("Enter '4' for quadgrams") print("Note: quadgrams can only do analysis on messages >= 4 characters\ntrigrams for >= 3 and so on \n(if you need a program to help decipher a < 4 letter caesar cipher RIP)") mode = input("Enter Mode: ") mode = mode.strip().lower() if mode == '1': file_name = "ngrams/english_monograms.txt" elif mode == '2': file_name = "ngrams/english_bigrams.txt" elif mode == '3': file_name = "ngrams/english_trigrams.txt" elif mode == '4': file_name = "ngrams/english_quadgrams.txt" else: print("Make sure input is correct") exit() return nGramInfo(file_name) def substitution(ciphertext_only_letters, key): plaintext = "" for letter in ciphertext_only_letters: if letter.isalpha() == True: plaintext += key[ord(letter) - ord("a")] else: plaintext += letter return plaintext if __name__ == "__main__": main()
Here are some tests using it.
Test 1
Message we are encrypting: “short message test”
Key used to encrypt: “qwertyuiopasdfghjklzxcvbnm”
basically just left to right on the keyboard
Ciphertext: “ligkz dtllqut ztlz”
Program ouputs:
Running the program 2 times gives us nothing significant it is mostly just gibberish however imagine someone with more computing power than me they could run multiple versions of this program while increasing the number of iterations the program goes through. It could easily decipher these messages with little issue.
Test 2
Message we are encrypting: “longer messages should work much better with these substitution cipher solvers however i mean who is even trying to solve the shorter ones using a computer”
Key used to encrypt: “qwertyuiopasdfghjklzxcvbnm”
same one as before
Ciphertext: “sgfutk dtllqutl ligxsr vgka dxei wtzztk vozi zitlt lxwlzozxzogf eohitk lgsctkl igvtctk o dtqf vig ol tctf zknofu zg lgsct zit ligkztk gftl xlofu q egdhxztk”
Program ouputs:
Guess what it was solved on the first go despite having such a large keyspace it is still relatively easy to crack a substitution cipher
From this I was legitimately surprised at how fast the substitution cipher could be cracked even with the resources I had. As we had to do many cryptograms, which are essentially substitution ciphers, I thought that this would be harder for a computer to do because even for us it would take a decent amount of time to decrypt them. Its shocking how fast a computer can do this just be randomly generating keys and choosing the better ones.
Note: once again i don’t know if the code is visible so will leave it below (all the code is on the github anyway)
import re, random
from ngram_class import nGramInfo
#substitution cipher
def main():
# grabs input and makes it lower case
ciphertext = input("Enter the message: ")
ciphertext = ciphertext.lower()
# removes everything except for letters this is used for calculating the healthiness
ciphertext_only_letters = re.sub("[^a-z]+", "", ciphertext)
alphabet = list("abcdefghijklmnopqrstuvwxyz")
#randomly shuffles the alphabet
random.shuffle(alphabet)
starter_key = "".join(alphabet) #starter key is random arrangement of alphabet
# calculates the best keys and prints them out in a decending order of healthiness
# highest healthiness (most correct) will be at the bottom
best_keys = calculate_best_key(ciphertext_only_letters, starter_key, ciphertext)
best_keys = sorted(best_keys)
for i in range(len(best_keys)):
print("================\n"+"Score:", best_keys[i][0], "\nKey:", best_keys[i][1], "\nDecryption:", substitution(ciphertext, best_keys[i][1]))
# print(best_keys)
#prints out the best guess
#its just the last thing anyway
print("\n======== BEST GUESS ========")
print("Score:", best_keys[-1][0])
print("Key:", best_keys[-1][1])
print("Decryption:", substitution(ciphertext, best_keys[-1][1]))
def calculate_best_key(ciphertext_only_letters, starter_key, ciphertext):
#creates object to calculate fitness
calculator = create_nGramInfo_class()
print("\n\n\nCiphertext:", ciphertext)
print("Ciphertext Healthiness Score:", calculator.calculate_fitness_score(ciphertext))
highest = [] #highest holds the highest scores and the keys associated with the scores
# this is the key that is shuffled into a new key every iteration
key = list('abcdefghijklmnopqrstuvwxyz')
# if the thing cant be solved iterations should be increased first
iteration = 0
while iteration < 15:
print("Iteration:", iteration)
#generates new key and calculates the fitness of the keys associated possible plaintext
random.shuffle(key)
new_key = "".join(key)
print("new key:", new_key)
high_score = calculator.calculate_fitness_score(substitution(ciphertext_only_letters, new_key))
best_key = new_key
highest.append([high_score, best_key])
iteration += 1
i = 0
# this can be increased if solver not solving however changing iteration number should be first
while i < 10000:
prev_key = new_key
#generates 2 random numbers (musnt be the same)
num1 = num2 = 0
while num1 == num2:
num1 = random.randint(0, 25)
num2 = random.randint(0, 25)
#swaps the letters
new = list(new_key)
new[num1], new[num2] = new[num2], new[num1]
new_key = "".join(new)
#creates the possible plaintext and calculates fitness
plaintext = substitution(ciphertext_only_letters, new_key)
new_score = calculator.calculate_fitness_score(plaintext)
#if new score is higher than high score new score becomes high score and is added to highest list
if new_score > high_score:
high_score = new_score
best_key = new_key
highest.append([high_score, best_key])
if len(highest) > 20:
highest = sorted(highest)
highest = highest[5:]
else: #if score doesnt increase it goes back to previous key
new_key = prev_key
i += 1
# summarises information in every iteration
print("Current highest score is", high_score, "on iteration", iteration)
print(best_key)
print("This decodes to", substitution(ciphertext_only_letters, best_key))
print("\n\n")
return highest
def create_nGramInfo_class():
print("Enter '1' for monograms")
print("Enter '2' for bigrams")
print("Enter '3' for trigrams")
print("Enter '4' for quadgrams")
print("Note: quadgrams can only do analysis on messages >= 4 characters\ntrigrams for >= 3 and so on \n(if you need a program to help decipher a < 4 letter caesar cipher RIP)")
mode = input("Enter Mode: ")
mode = mode.strip().lower()
if mode == '1':
file_name = "ngrams/english_monograms.txt"
elif mode == '2':
file_name = "ngrams/english_bigrams.txt"
elif mode == '3':
file_name = "ngrams/english_trigrams.txt"
elif mode == '4':
file_name = "ngrams/english_quadgrams.txt"
else:
print("Make sure input is correct")
exit()
return nGramInfo(file_name)
def substitution(ciphertext_only_letters, key):
plaintext = ""
for letter in ciphertext_only_letters:
if letter.isalpha() == True:
plaintext += key[ord(letter) - ord("a")]
else:
plaintext += letter
return plaintext
if __name__ == "__main__":
main()
15 notes
·
View notes
...the trail leads cold as the signal seemingly stops to a halt, upon reaching the location Robyn discovers the fate that happened to her dear Seven... Nothing more but a hollow shell, cleansed of all free thinking and memories of her former life...
“S-seven... please.... dont... dont leave.. i...”
“...why did you ever take the job...”
unfortunately the words are fallen on to deaf ears... and the sunken and absent expression shows no reaction or care to anything around them the lights begin to flicker and fade as the subway metro grows dim, leaving the two in ones arms, desperately begging for the other’s return... pleads and wishes cry out down the desolate metro, a sudden buzz rings from Seven’s phone, ringing out:
“Subject: 100754. status: cleansed, preparing for pick up and retrieval”
(page 4...End..)
And that is all she wrote.. Well things certainly didnt end well for either of those two, but Hope that you all enjoyed it! had been extremely nervous while working upon this comic, especially trying to live up to my gal’s ideas going into this comic, but hope that you all enjoyed the lil thing here! and once again i wanna just thank @malachite-squid for the huge help in support during this project, seriously machoot, Bless ya :D
9 notes
·
View notes