Skip to content

Instantly share code, notes, and snippets.

@gruvw
Last active March 24, 2022 16:25
Show Gist options
  • Save gruvw/d97ea3e8cd5b198b45095c40bdbb15b7 to your computer and use it in GitHub Desktop.
Save gruvw/d97ea3e8cd5b198b45095c40bdbb15b7 to your computer and use it in GitHub Desktop.
Decipher Vigenère from partial plaintext
# Alphabet
a = "abcdefghijklmnopqrstuvwxyz"
# Given data
cipher = "ebgrycxgbghitursyneavcgbgryv"
partial_text = "t*****************u**i**i***" # supposed valid and not empty (only *)
# Function (single char) that does C,T -> K or C,K -> T
clear = lambda c, tk: (i := (a.find(c)-a.find(tk)) % len(a), a[i]) # (index, letter)
# Function that tries to extract a key (of given size) from cipher using partial plaintext
def extract_key(cipher, partial_text, key_size):
key = ["?"] * key_size # "?" for unknown char in the key
# char by char: index in the text, (cipher, partial_text != *)
for i, (c, t) in [e for e in enumerate(zip(cipher, partial_text)) if e[1][1] != "*"]:
if key[i % key_size] != "?":
if clear(c, t)[1] != key[i % key_size]: return False, [] # invalid key size
else: key[i % key_size] = clear(c, t)[1]
return True, key
# Step 1: brute force key size
key = "" # unknown for the moment
candidate_keys = ["".join(k[1]) for s in range(1, len(cipher)) if (k := extract_key(cipher, partial_text, s))[0]]
if candidate_keys:
if keys := [k for k in candidate_keys if "?" not in k]:
print("Found key:", key := min(keys, key=len)) # enough information to directly find the key
else:
print("Candidate keys:")
isUseful = lambda K: not any([len(k) < len(K) and all([K[i] == s for i, s in enumerate(k) if s != "?"]) for k in candidate_keys])
for k in filter(isUseful, candidate_keys): print(k) # display every candidate
else: raise Exception("Invalid data!")
# Step 2: select a key (user must complete one of the candidates if not enough information in partial text)
# best candidate: key size of 5 -> lu?ky => lucky
if not key: key = input("Type chosen key: ")
# Step 2: Decipher
repeated_key = key * int(len(cipher) / len(key) + 1) # enough repetitions
text = [clear(c, k)[1] for c, k in zip(cipher, repeated_key)]
print("Plaintext:", "".join(text)) # => the harder i work the luckier i get
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment