The Cybersecurity: Sniffing Attacks and Defenses [1] tutorial’s Share Something Personal - Encrypted [2] activity demonstrated how even a relatively weak form of encryption can at least deter casual sniffing. However, an experienced attacker would likely examine that sniffing data and then make an educated guess that the data was encrypted with the Caesar cipher. After that, coding a brute force attack is a relatively simple matter.
In this activity, you will use brute force to crack the Caesar cipher that made sharing HAPPY, SAD, and ANGRY images (sort of) private in the Share Something Personal - Encrypted [2] activity.
# packet = caesar(-3, packet) # print("packet:", packet) # display.show(getattr(Image, packet))
for key in range(-1, -26, -1): result = caesar(key, packet) print("key:", key, "result:", result) sleep(200) print()
# radio_send_images_caesar_key_unknown.py from microbit import * import radio import random # <- add ''' Function converts plaintext to ciphertext using key ''' def caesar(key, word): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = "" for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index] return result ''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) string_list = ["HAPPY", "SAD", "ANGRY"] key = random.randint(1, 25) # <- add while True: for packet in string_list: print("packet:", packet) display.show(getattr(Image, packet)) # packet = caesar(3, packet) # <- change (before) packet = caesar(key, packet) # <- change (after) print("Send encrypted:", packet) radio.send(packet) # sleep(2500) # <- change (before) sleep(6000) # <- change (after)
# radio_receive_images_caesar_brute_force.py from microbit import * import radio ''' Function converts plaintext to ciphertext using key ''' def caesar(key, word): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = "" for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index] return result ''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) while True: packet = radio.receive() if packet: print("Receive encrypted:", packet) # packet = caesar(-3, packet) # <- comment # print("packet:", packet) # <- comment # display.show(getattr(Image, packet)) # <- comment for key in range(-1, -26, -1): # <- add result = caesar(key, packet) # <- add print("key:", key, "result:", result) # <- add sleep(200) # <- add print() # <- add
This section only explains modifications that make it use a random key and send strings at a slower rate. For info on the program before modifications, check Share Something Personal - Encrypted [2].
Adding import random makes the random methods available so that the script can use random.randint().
Adding key = random.randint(1, 25) sets the key equal to a randomly generated integer in the 1 through 25 range. This statement was placed above the while True: loop so that it gets executed before the main loop starts sending repeated encrypted messages. That way, the script sets the key value once, and then uses it with every repetition of the while True: loop.
Each time you restart the micro:bit, there is a 1 in 25 chance you will get the same key. Conversely, there is a 24 out of 25 chance that the key will be different.
In the main loop, changing packet = caesar(3, packet) to packet = caesar(key, packet) makes it so that the main loop repeatedly uses the one key value that was randomly determined before the main loop started repeating.
The sleep(2500) was changed to sleep(6000). Intentionally slowing down the broadcast rate gives the online editor enough time to take a 0.2 s break between each line printed. This will reduce the WebUSB tendency to make printing errors.
The brute force receiver removed the statements that assumed key was 3 and displayed it.
# packet = caesar(-3, packet) # <- comment # print("packet:", packet) # <- comment # display.show(getattr(Image, packet)) # <- comment
In place of those three statements, it uses a loop that decrypts and prints the decrypted packet’s result to the terminal with each of the 25 possible keys. The for key in range(-1, -26, -1) loop repeats the indented-below statements with key set to -1, then -2, -3, and so on up through -25. Each time through the loop, result = caesar(key, packet) decrypts the packet for that repetition’s key value. Then, print("key:", key, "result:", result) displays the key and decrypted string for each iteration of the loop.
for key in range(-1, -26, -1): # <- add result = caesar(key, packet) # <- add print("key:", key, "result:", result) # <- add sleep(200) # <- add print() # <- add
Lastly, sleep(200) delays before repeating the loop to give the online editor’s WebUSB connection extra time to transfer information to the terminal (since it seems to need it at the time of this writing). After all 25 loop repetitions, print() displays an empty line before the while True: loop repeats and another 25 keys and decrypted strings are displayed.
Just as a PIN is stronger with an increased number of combinations, so is the cipher. The ascii_shift cipher has more combinations.
To crack the cipher, your script will have to examine more combinations.
You might notice an upper-case HAPPY as well as a lower-case happy. The key that generates the upper-case HAPPY is the correct answer, though either might actually work for a sniffing attacker’s purposes. This is an additional weakness of the ASCII Shift cipher. Make sure not to choose a key that will simply encrypt your upper case characters as lower-case or vice-versa.
# radio_send_images_caesar_key_unknown_try_this.py from microbit import * import radio import random # <- add ''' Function converts plaintext to ciphertext using key ''' def ascii_shift(key, text): # <- change (try this) result = "" # <- chante (try this) for letter in text: # <- change (try this) ascii = ( ord(letter) + key - 32 ) % 94 + 32 # <- change (try this) result = result + chr(ascii) # <- change (try this) return result # <- change (try this) ''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) string_list = ["HAPPY", "SAD", "ANGRY"] # key = random.randint(1, 25) # <- add key = random.randint(1, 93) # <- change (try this) while True: for packet in string_list: print("packet:", packet) display.show(getattr(Image, packet)) # packet = caesar(3, packet) # <- change (before) # packet = caesar(key, packet) # <- change (after) packet = ascii_shift(key, packet) # <- change (try this) print("Send encrypted:", packet) radio.send(packet) # sleep(2500) # <- change (before) sleep(6000) # <- change (after)
# radio_receive_images_caesar_brute_force_try_this.py from microbit import * import radio ''' Function converts plaintext to ciphertext using key ''' def ascii_shift(key, text): # <- change (try this) result = "" # <- chante (try this) for letter in text: # <- change (try this) ascii = ( ord(letter) + key - 32 ) % 94 + 32 # <- change (try this) result = result + chr(ascii) # <- change (try this) return result # <- change (try this) ''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) while True: packet = radio.receive() if packet: print("Receive encrypted:", packet) # packet = caesar(-3, packet) # <- comment # print("packet:", packet) # <- comment # display.show(getattr(Image, packet)) # <- comment # for key in range(-1, -26, -1): # <- add for key in range(-1, -94, -1): # <- change (try this) # result = caesar(key, packet) # <- add result = ascii_shift(key, packet) # <- change (try this) print("key:", key, "result:", result) # <- add sleep(200) # <- add print() # <- add
Links
[1] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-sniffing-attacks-and-defenses
[2] https://learn.parallax.com/tutorials/language/python/cybersecurity-sniffing-attacks-and-defenses/share-something-personal-1
[3] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-encryption-intro/ascii-and-other-simple-ciphers