Statement of Completion#9613d9c8
Fun and Puzzles
hard
Breaking Caesar's cipher
Resolution
Activities
In [1]:
import string
In [2]:
letters = list(string.ascii_uppercase)
Activities¶
1. Shift letters
by 5
¶
In [4]:
def shift_letters(letters, shift):
"""Shifts an alphabet (list of letters) by a given number of positions.
Args:
letters: A list of letters representing the alphabet.
shift: The number of positions to shift the alphabet (positive for right,
negative for left).
Returns:
A new list containing the shifted alphabet.
"""
shifted_letters = []
for letter in letters:
new_index = (letters.index(letter) + shift) % len(letters)
shifted_letters.append(letters[new_index])
return shifted_letters
letters = list(string.ascii_uppercase) # Get uppercase alphabet as a list
shift_5 = shift_letters(letters.copy(), 5) # Create a copy to avoid modifying letters
# Print the shifted alphabet (optional)
print(shift_5)
['F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D', 'E']
2. Shift letters
by -8
¶
In [6]:
shift_minus_8 = shift_letters(letters.copy(), -8) # Create a copy to avoid modifying letters
# Print the shifted alphabet (optional)
print(shift_minus_8)
['S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R']
3. Shift letters
by 32
¶
In [16]:
# Shift by 32 (handled by overflow in shift_letters)
shift_32 = shift_letters(letters.copy(), 32)
# Print the shifted alphabet (optional)
print(shifted_alphabet)
['G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D', 'E', 'F']
4. Write the function shift_letters
¶
In [18]:
def shift_letters(letters, shift):
pass
Test it on your own:
In [29]:
def shift_letters(letters, shift):
"""Shifts an alphabet (list of letters) by a given number of positions.
Args:
letters: A list of letters representing the alphabet.
shift: The number of positions to shift the alphabet (positive for right,
negative for left).
Returns:
A new list containing the shifted alphabet.
"""
shifted_letters = [] # Create an empty list to store shifted letters
for letter in letters:
new_index = (letters.index(letter) + shift) % len(letters)
shifted_letters.append(letters[new_index]) # Append shifted letter
return shifted_letters
5. Write the function encrypt_simple
¶
In [33]:
def encrypt_simple(word, shift):
pass
def encrypt_simple(word, shift):
"""Encrypts a word using Caesar's cipher for uppercase letters.
Args:
word: The word to encrypt (uppercase letters only).
shift: The number of positions to shift the letters (positive for right,
negative for left).
Returns:
The encrypted word.
"""
encrypted_word = ""
for letter in word:
if not letter.isalpha() or not letter.isupper():
# Skip non-alphabetic characters or lowercase letters
encrypted_word += letter
continue
new_index = (ord(letter) - ord('A') + shift) % 26 + ord('A')
encrypted_word += chr(new_index)
return encrypted_word
# Example usage
print(encrypt_simple("DATAWARS", 5)) # Output: 'IFYFBFWX'
print(encrypt_simple("DATAWARS", -14)) # Output: 'PMFMIMDE'
IFYFBFWX PMFMIMDE
6. Write the function decrypt_simple
¶
In [35]:
def decrypt_simple(encrypted_word, origina_shift):
pass
def decrypt_simple(word, shift):
"""Decrypts a word encrypted using Caesar's cipher for uppercase letters.
Args:
word: The encrypted word (uppercase letters only).
shift: The number of positions used to encrypt the word (positive for right,
negative for left).
Returns:
The decrypted word.
"""
decrypted_word = ""
for letter in word:
if not letter.isalpha() or not letter.isupper():
# Skip non-alphabetic characters or lowercase letters
decrypted_word += letter
continue
new_index = (ord(letter) - ord('A') - shift) % 26 + ord('A')
decrypted_word += chr(new_index)
return decrypted_word
# Example usage (assuming encrypt_simple is defined as before)
print(encrypt_simple("DATAWARS", 5)) # Output: 'IFYFBFWX'
print(decrypt_simple("IFYFBFWX", 5)) # Output: "DATAWARS"
print(encrypt_simple("DATAWARS", -14)) # Output: 'PMFMIMDE'
print(decrypt_simple("PMFMIMDE", -14)) # Output: "DATAWARS"
IFYFBFWX DATAWARS PMFMIMDE DATAWARS
7. Define the functions encrypt_full
and decrypt_full
¶
In [37]:
def encrypt_full(text, shift):
pass
In [38]:
def decrypt_full(encrypted_text, original_shift):
pass
import string
def encrypt_full(text, shift):
"""Encrypts any text using Caesar's cipher for ASCII characters.
Args:
text: The text to encrypt.
shift: The number of positions to shift the letters (positive for right,
negative for left).
Returns:
The encrypted text.
"""
encrypted_text = ""
for char in text:
if char.isalpha():
# Encrypt only ASCII letters (uppercase or lowercase)
new_char = _encrypt_char(char, shift)
else:
# Leave non-alphabetic characters unchanged
new_char = char
encrypted_text += new_char
return encrypted_text
def decrypt_full(text, shift):
"""Decrypts a text encrypted with Caesar's cipher for ASCII characters.
Args:
text: The encrypted text.
shift: The number of positions used to encrypt the text (positive for right,
negative for left).
Returns:
The decrypted text.
"""
decrypted_text = ""
for char in text:
if char.isalpha():
# Decrypt only ASCII letters (uppercase or lowercase)
new_char = _decrypt_char(char, shift)
else:
# Leave non-alphabetic characters unchanged
new_char = char
decrypted_text += new_char
return decrypted_text
def _encrypt_char(char, shift):
"""Helper function to encrypt a single ASCII character."""
new_ascii_code = (ord(char) - ord('a') if char.islower() else ord(char) - ord('A')) + shift
new_ascii_code = (new_ascii_code % 26) + ord('a' if char.islower() else 'A')
return chr(new_ascii_code)
def _decrypt_char(char, shift):
"""Helper function to decrypt a single ASCII character."""
new_ascii_code = (ord(char) - ord('a') if char.islower() else ord(char) - ord('A')) - shift
new_ascii_code = (new_ascii_code % 26) + ord('a' if char.islower() else 'A')
return chr(new_ascii_code)
# Example usage
print(encrypt_full("DataWars is Great!", 9)) # Output: 'MjcjFjab rb Panjc!'
print(decrypt_full('MjcjFjab rb Panjc!', 9)) # Output: 'DataWars is Great!'
MjcjFjab rb Panjc! DataWars is Great!
Try it out!¶
Now you can put your cipher to a test. Execute the following line to encrypt any message that you want:
In [40]:
from IPython.core.display import HTML
Encrypt:¶
In [42]:
text = input("Enter the text you want to encrypt: ")
shift = int(input("Enter the `shift` value (integer): "))
display(HTML(f"<br>Your encrypted message: <b>{encrypt_full(text, shift)}</b>"))
Enter the text you want to encrypt: Enter the `shift` value (integer):
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[42], line 2 1 text = input("Enter the text you want to encrypt: ") ----> 2 shift = int(input("Enter the `shift` value (integer): ")) 4 display(HTML(f"<br>Your encrypted message: <b>{encrypt_full(text, shift)}</b>")) ValueError: invalid literal for int() with base 10: ''
Decrypt:¶
In [43]:
text = input("Enter the text you want to decrypt: ")
shift = int(input("Enter the `shift` value (integer): "))
display(HTML(f"<br>Your encrypted message: <b>{decrypt_full(text, shift)}</b>"))
Enter the text you want to decrypt: aaa Enter the `shift` value (integer): integer
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[43], line 2 1 text = input("Enter the text you want to decrypt: ") ----> 2 shift = int(input("Enter the `shift` value (integer): ")) 4 display(HTML(f"<br>Your encrypted message: <b>{decrypt_full(text, shift)}</b>")) ValueError: invalid literal for int() with base 10: 'integer'
Now it's time to apply all our previous functions and some algorithmic brain power to break the cipher! This is the ultimate challenge for any criptographer.
Implement the function break_cipher
that will break any code encrypted using Caesar's cipher. The general approach to break a cipher is to find known words. For example, if you know a messsage contains the word "DATAWARS", you can look for that pattern within the encrypted message.
Let's pick any random shift to encrypt a cipher, for example, 9
:
In [44]:
encrypt_full("THIS IS DATAWARS", 9)
Out[44]:
'CQRB RB MJCJFJAB'
The word DATAWARS
was encrypted as MJCJFJAB
. But if you check the "distances" between the words DATAWARS
and MJCJFJAB
you'll find the same pattern:
D => A => T => A => W => A => R => S
23 19 7 22 4 17 1
M => J => C => J => F => J => A => B
23 19 7 22 4 17 1
If the pattern is still not clear to you, try counting the "hops" between "D" and "A" and between "M" and "J" using the following alphabet. Important! The key is to always count "forward".
In [45]:
" ".join(letters)
Out[45]:
'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'
In [46]:
def break_cipher(encrypted_message, known_word):
pass
def break_cipher(encrypted_message, known_word):
"""Breaks a Caesar cipher by finding a known word in the encrypted message.
Args:
encrypted_message: The encrypted message (uppercase ASCII and whitespaces).
known_word: The known word to use for breaking the cipher.
Returns:
The decrypted message.
"""
for shift in range(26):
decrypted_message = decrypt_full(encrypted_message, shift)
if known_word in decrypted_message:
return decrypted_message
# If the known word is not found in any shifted version, return a failure message
return "Cipher could not be broken with the given known word."
Here are some test cases that you can use to test your own function:
In [47]:
encrypted_text = encrypt_full("DATAWARS IS THE BEST DATA SCIENCE PRACTICE PLATFORM IN THE ENTIRE WORLD", 12)
encrypted_text
Out[47]:
'PMFMIMDE UE FTQ NQEF PMFM EOUQZOQ BDMOFUOQ BXMFRADY UZ FTQ QZFUDQ IADXP'
In [48]:
break_cipher(encrypted_text, "DATAWARS")
Out[48]:
'DATAWARS IS THE BEST DATA SCIENCE PRACTICE PLATFORM IN THE ENTIRE WORLD'
In [49]:
break_cipher(encrypted_text, "SCIENCE")
Out[49]:
'DATAWARS IS THE BEST DATA SCIENCE PRACTICE PLATFORM IN THE ENTIRE WORLD'
In [50]:
break_cipher(encrypted_text, "PLATFORM")
Out[50]:
'DATAWARS IS THE BEST DATA SCIENCE PRACTICE PLATFORM IN THE ENTIRE WORLD'