Wednesday, April 22nd, 2026¶

On Monday, we discussed the encryption/decryption scheme used for our next project.

Project 5 - Code breakers¶

We developed functions to convert strings to sequences of ASCII codes and vice versa.

In [1]:
def str_to_ascii(s):
    ascii_codes = []
    for c in s:
        ascii_codes.append(ord(c))
    return ascii_codes
In [2]:
def ascii_to_str(ascii_codes):
    char_list = []
    for n in ascii_codes:
        char_list.append(chr(n))
    return ''.join(char_list)

We can test these by converting a string to a sequence of ASCII codes and converting the resulting list back into a string, which should be the same as the original string.

In [3]:
s = 'This is MTH 337!'

ascii_codes = str_to_ascii(s)
new_s = ascii_to_str(ascii_codes)

print(s)
print(new_s)
This is MTH 337!
This is MTH 337!

Recall the encryption scheme described in the project page:

  • One selects a secret key, which is sequence of characters. This key is used to both encrypt and decrypt the message.
  • Characters of the secret key and characters of the message are converted into ASCII codes. In this way the key is transformed into a sequence of integers $(k_1, k_2, \dots, k_r)$, and the message becomes another sequence of integers $(m_1, m_2, \dots, m_s)$. If $r<s$, then the secret key sequence is extended by repeating it as many times as necessary until it matches the length of the message.
  • Let $c_i$ be the reminder from the division of $m_i + k_i$ by $128$. The sequence of numbers $(c_1, c_2, \dots, c_s)$ is the encrypted message.

We discussed the need for a get_padded_key_ascii function that can be used to lengthen our key to match the length of the message, along with a few approaches for how to accomplish this.

Exercise: Write a function get_padded_key_ascii that takes in arguments key_ascii and length and returns a padded version of key_ascii of length length, obtained by repeating key_ascii as many times as necessary.

In [ ]:
 
In [ ]:
 

Exercise: Write a function encrypt(message_ascii, key_ascii) that return the encrypted version of message_ascii using the secret key key_ascii (based on the code above).

In [ ]:
 
In [ ]:
 

Exercise: Write a function decrypt(encrypted_ascii, key_ascii) that returns the decrypted message.

In [ ]:
 
In [ ]:
 
In [ ]:
 

Exercise: Test your functions by defining your own message and key strings (with message longer than key), and:

  1. Converting both strings into sequences of ASCII codes (using str_to_ascii function),
  2. Generating the encrypted message as a sequence of ASCII codes (using the encrypt function),
  3. Decrypting the encrypted message (using the decrypt function),
  4. Converting the decrypted message back into a string (using the ascii_to_str function).

The final string should match the original message string.

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

We also discussed how to load your assigned file into Python and obtain the corresponding list of ASCII codes.

Exercise: Read the contents of your assigned file and generate the corresponding list of ASCII codes.

In [ ]:
 
In [ ]:
 
In [ ]:
 

Finally, we discussed a possible strategy for breaking the encryption. In particular, we discussed:

  1. Iterating through all of the possible key words that may have been used to encrypt the original message (coming from the dictionary.txt file) and decrypting the assigned message with each word,
  2. Quantifying how "good" each of these different decrypted messages are, and
  3. Choosing the key word that gave the "best" decryption.

One way to quantify how "good" a given decryption is is to count how many actual English words appear in the decrypted message (using the words in dictionary.txt again as a collection of English words). In other words, we can iterate through each "word" from our decrypted message and test whether that "word" is in our list of English words.

Note: We saw that it is much faster to check for membership in a Python set than in a Python list, so we will want to generate a set version of our collection of English words.

Exercise: Iterate through all possible keywords and decrypt your assigned message for each keyword. Generate a dictionary where the keys are the keywords used to decrypt and the values are "how good" the decryption is. Then find the keyword that gives the "best" decryption.

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: