Project-3 / Python Cypher / a1.py
a1.py
Raw
from math import log2


ALPHABET = "\n !\"'(),-.0123456789:;?ABCDEFGHIJKLMNOPQRSTUVWXYZ"


def encrypt(text, key):
    text = text.upper()
    key = key.upper()

    encrypted = ""
    for position in range(len(text)):
        text_character = ALPHABET.index(text[position])
        key_character = ALPHABET.index(key[position % len(key)])
        encrypted_char = (text_character + key_character) % len(ALPHABET)
        encrypted += ALPHABET[encrypted_char]
    return encrypted


def get_frequencies(text):
    text = text.upper()
    freq = 1 / len(text)
    frequencies = {}

    for char in text:
        if frequencies.get(char):
            frequencies[char] += freq
        else:
            frequencies[char] = freq
    return frequencies


def cross_entropy(freqs1, freqs2):
    freqs1_chars = set(freqs1.keys())
    freqs2_chars = set(freqs2.keys())

    all_chars_union = list(set.union(freqs1_chars, freqs2_chars))

    total = 0.0

    min_freq1 = 1
    for value in freqs1.values():
        if min_freq1 > value:
            min_freq1 = value

    min_freq2 = 1
    for value in freqs2.values():
        if min_freq2 > value:
            min_freq2 = value

    for char in all_chars_union:
        char_in_freqs1 = freqs1.get(char)
        char_in_freqs2 = freqs2.get(char)

        if char_in_freqs1 and not char_in_freqs2:
            freqs2[char] = min_freq2

        elif not char_in_freqs1 and char_in_freqs2:
            freqs1[char] = min_freq1

        total -= freqs1[char] * log2(freqs2[char])

    return total


def guess_key(encrypted):
    q = ''
    with open('frank.txt') as f:
        q = f.read()

    english_frequencies = get_frequencies(q)

    cipher = ['', '', '']
    key = ['', '', '']
    i = 0
    for char in encrypted:
        cipher[i % 3] += char
        i += 1
    
    for index in range(len(cipher)):
        min_val = 99999
        for char in ALPHABET:
            val = cross_entropy(english_frequencies, get_frequencies(decrypt(cipher[index], char)))

            if val > 0 and val < min_val:
                min_val = val
                key[index] = char

    return ''.join(key)


def crack(encrypted_text):
    key = guess_key(encrypted_text)
    return decrypt(encrypted_text, key)


def decrypt(text, key):
    text = text.upper()
    key = key.upper()

    decrypted = ""
    for position in range(len(text)):
        text_character = ALPHABET.index(text[position])
        key_character = ALPHABET.index(key[position % len(key)])
        decrypted_char = (text_character - key_character) % len(ALPHABET)
        decrypted += ALPHABET[decrypted_char]
    return decrypted


if __name__ == '__main__':

    encrypted = ''
    with open('secret1_encrypted.txt') as f:
        encrypted  = f.read()

    print(crack(encrypted[:1000]))