Sniffing is the act of silently monitoring communication. Some sniffing is not considered a cyber attack. For example, law enforcement agencies and/or network administrators might monitor network communication. However, cyber attackers use sniffing to gain access to data like passwords and personal information, which they might use to gain access to personal accounts, networks, and other resources.
Not all people or devices are careful about encrypting data, which makes it very easy for cyber attackers to “sniff” private data. For example, when step counters were new, not all brands encrypted their data. At one marathon race, attackers waited at the finish line and collected hundreds of usernames and passwords.
This activity has examples for examining how data can be vulnerable to sniffing attacks and demonstrates how encryption is the first line of defense against those attacks.
Make sure you have:
Complete these tutorials first:
You will be able to add simple encryption to just about any of your micro:bit radio applications and you will be ready to move on to these tutorials (coming soon!)
You will also be one step closer to ready to compete in cybersecurity contests such as Hackers vs. Defenders.
Imagine sharing something personal, like maybe your mood or reaction to something, with a friend. The catch is, everybody else in the class gets to read your text! That’s certainly possible if you share your data unencrypted.
This activity uses one micro:bit to broadcast microbit.Image.HAPPY/SAD/ANGRY images. They might be only intended for one micro:bit receiver, but without encryption, any other micro:bit receiver in range can access the data too. Other people might monitor channels with their micro:bit modules and after gathering information through sniffing, then use it for purposes you would not want or intend.
After this activity, you will add encryption, and examine the difference in what the “sniffing” micro:bit is able to detect.
If you only have two micro:bit modules you can alternate the one between intended receiver and sniffer roles. You will load the script for the intended receiver and perform any tests. Then, load the script for the sniffer, and perform tests for that role.
See Texting with Terminals [5] if you need a reminder on how to set up the serial connection to the micro:bit modules.
# radio_send_images.py from microbit import * import radio radio.on() radio.config(channel=7) sleep(1000) while True: packet = "HAPPY" print("Send:", packet) radio.send(packet) sleep(2500) packet = "SAD" print("Send:", packet) radio.send(packet) sleep(2500) packet = "ANGRY" print("Send:", packet) radio.send(packet) sleep(2500)
# radio_receive_images.py from microbit import * import radio radio.on() radio.config(channel=7) sleep(1000) while True: packet = radio.receive() if packet: print("Receive:", packet) display.show(getattr(Image, packet))
If the sniffer micro:bit also displays the same images, it means the person with that micro:bit can monitor all your data exchanges!
The transmitter script broadcasts a string containing a term every 2.5 seconds. Those terms HAPPY, SAD, ANGRY can be used by receivers in statements that work the same way as display.show(Image.HAPPY), display.show(Image.SAD), and so on.
The statements below set up and turn on the micro:bit module’s built-in radio and also provide a helpful 1-second delay before sending data to the terminal. To find out more, try out the Cybersecurity: Radio Basics [6] activities.
# radio_send_images.py from microbit import * import radio radio.on() radio.config(channel=7) sleep(1000)
A convenient approach to sending predefined image data through the radio is to simply send strings that contain words like "HAPPY", "SAD", "ANGRY" that can be used as arguments. The receiver script can build a statement like display.show(Image.HAPPY). In addition to sending the data packet, the transmitter script prints the word on the terminal so you can monitor which mood/reaction image it’s sending through radio.send(packet).
while True: packet = "HAPPY" print("Send:", packet) radio.send(packet) sleep(2500) ...
After the same familiar initialization, the receiver script's main loop checks for incoming messages with packet = radio.receive(). If nothing is received, packet = None, and the statements under if packet: get skipped. When packet stores a string like "HAPPY", "SAD", or "ANGRY", the statements indented below if packet: print the string to the terminal, and then display the corresponding image on the micro:bit module’s LEDs matrix.
while True: packet = radio.receive() if packet: print("Receive:", packet) display.show(getattr(Image, packet))
The trick to using a string to access the image is to use getattr (object, name). This “get attribute” function accepts an object and the string name of one of its properties. It responds by returning that property. When packet contains "HAPPY", then getattr(Image, "HAPPY") returns Image.HAPPY, and the micro:bit lights up its LEDs in response to display.show(Image.HAPPY).
If you’re skeptical that packet = "HAPPY" followed by getattr(Image, packet) is equivalent to Image.HAPPY, try this:
print("Image.HAPPY =", Image.HAPPY) sleep(500) packet = "HAPPY" print( "getattr(Image, packet) =", getattr(Image, packet) ) sleep(500)
Image(…) has brightness data for each LED in the display. In this case it only uses 0 (LED off), and 9 (LED brightest). Values between 0 and 9 for each LED can be used to control the brightness.
This code takes up unnecessary program space in the micro:bit module’s memory.
packet = "HAPPY" print("Send:", packet) radio.send(packet) sleep(2500) packet = "SAD" print("Send:", packet) radio.send(packet) sleep(2500) packet = "ANGRY" print("Send:", packet) radio.send(packet) sleep(2500)
Your program can instead create a list like string_list = ["HAPPY", "SAD", "ANGRY"]. Then, inside the while True loop, the for packet in string_list can index through each string in string_list.
In this activity, you will examine the effects of encrypting your messages on a sniffing cyberattack. You will leave the sniffer micro:bit’s script the same receiver script, but add Caesar cipher encryption to the transmitter and intended receiver scripts.
Cybersecurity: Encryption Intro [2] has activities introducing the Caesar cipher if you want to learn more about it.
# radio_send_images_caesar.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) string_list = ["HAPPY", "SAD", "ANGRY"] while True: for packet in string_list: print("packet:", packet) display.show(getattr(Image, packet)) packet = caesar(3, packet) print("Send encrypted:", packet) radio.send(packet) sleep(2500)
# radio_receive_images_caesar.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) print("packet:", packet) display.show(getattr(Image, packet))
The sniffing receiver script running radio_receive_images threw an exception when it tried to look up getattr(Image, "KDSSB"). If the string was not encrypted, it would be "HAPPY", which would work just fine.
Can you see the data in the terminal now?
Instead of commenting display.show in radio_receive_images_sniffer, you can add exception handling code. That way, your micro:bit’s display will immediately display unencrypted images, and tell you if the data is not a valid image without halting because of an exception.
Can you match the terminal output shown by the sniffer below? Whenever the string does not represent a valid image, it prints: packet is not an image string.
Both the transmitter and receiver scripts have a caesar() function added above radio.on(). This function was developed step-by-step in Encryption Intro [2] and calling the function was introduced in Caesar Cipher in a Function [9]. Once your script has the caesar() function at its disposal, all it has to do is call it to encrypt single upper-case word strings. For example, if packet contains the string to encrypt, packet = caesar( 3, packet ) uses a key of 3 to encrypt the word in packet. To decrypt, use the negative key value with packet = caesar( -3, packet ).
Remember from the previous activity that packet contains either "HAPPY", "SAD", or "ANGRY" strings. In radio_send_images_caesar, it displays the packet before encrypting with print("packet:", packet). Then, it sends the packet through the Caesar cipher with packet = caesar(3, packet). Next, print("Send encrypted:", packet) prints the ciphertext version of the packet to the terminal. For example, after the caesar(3, packet) call, HAPPY becomes KDSSB.
print("packet:", packet) packet = caesar(3, packet) print("Send encrypted:", packet) radio.send(packet) sleep(2500)
When the intended receiver’s radio.receive call returns an encrypted packet, it should look the same as the transmitter’s encrypted packet. To decrypt, simply call the caesar function with the negative of the key used to encrypt. Since the key was 3 to encrypt, it has to be -3 to decrypt. That’s why the intended receiver script’s packet = caesar(-3, packet) has the negative value of the transmitter’s key.
packet = radio.receive() if packet: print("Receive encrypted:", packet) packet = caesar(-3, packet) print("packet:", packet) display.show(getattr(Image, packet))
Another activity that was vulnerable to sniffing attacks was the Bidirectional Texts [10] application from Cybersecurity: Radio Basics. In this activity, you will add an ASCII shift cipher to encrypt and decrypt the text communication.
Next, let’s encrypt micro:bit transceiver A’s data and see what it looks like in micro:bit transceiver B’s terminal.
# terminal_chat_through_microbits_encrypted_A.py # learn.parallax.com/cyberbot # Copyright Parallax Inc 2020 under MIT license from microbit import * import radio ''' Function converts plaintext to ciphertext using key ''' def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result ''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) print("micro:bit transceiver A") print() text = input("Enter key: ") key = int(text) print() print("Type messages, press enter to send.") print("Received messages will also be displayed.") print() while True: if uart.any(): tx = input("Send: ") tx = ascii_shift(key, tx) radio.send(tx) message = radio.receive() if message: message = ascii_shift(-key, message) print("Receive: ", message)
What do you see in micro:bit transceiver B’s terminal? This is what an attacker trying to use sniffing would see when you send the encrypted message.
Now that the scripts are sending different case letters, spaces and punctuation, it’s time to choose a different cipher. Reason being the caesar() function cannot accommodate those extra characters without modifications, but the ascii_shift() function can. It was introduced in the Cybersecurity: Encryption Intro tutorials’ ASCII Shift Cipher activity [11].
After the import statements, the ascii_shift() function is added. With def ascii_shift(key, text), it accepts key and text parameters and returns the encrypted result. Most importantly, the text can contain any printable ASCII character.
from microbit import * import radio ''' Function converts plaintext to ciphertext using key ''' def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result
After the ascii_shift() function, the script starts with the usual radio.on, radio.config, and sleep calls along with a couple print statements to help you identify which micro:bit is running which version of the script.
''' Script starts from here... ''' radio.on() radio.config(channel=7) sleep(1000) print("micro:bit transceiver A") print()
Before the main loop, the script prompts you for an encryption key. So long as both transceivers are using the same key, the messages sent will be faithfully reproduced by the receiver and vice-versa.
text = input("Enter key: ") key = int(text)
These are messages similar to the original unencrypted radio texting app. Without them, an empty terminal might be confusing, so the prompts are intended to encourage users to start typing messages.
print() print("Type messages, press enter to send.") print("Received messages will also be displayed.") print()
Inside the main loop, the script checks if you have typed any characters in the terminal. If you have, an input statement captures what you typed. Before transmitting it with radio.send, it does the extra step of tx = ascii_shift(key, tx) to encrypt the string you typed.
while True: if uart.any(): tx = input("Send: ") tx = ascii_shift(key, tx) radio.send(tx)
The main loop also checks for messages from the radio. When one is received, it is stored in the message variable. At this point, message is ciphertext, which has to be decrypted. That’s what message = ascii_shift(-key, message) does, returning the plaintext message. …and after print("Receive: ", message), you’ll be able to see and read it in the terminal.
message = radio.receive() if message: message = ascii_shift(-key, message) print("Receive: ", message)
In a way, dictionary data looks kind of cryptic to begin with. But, without actual encryption, it would make it very easy for attackers to know where your cyber:bot is going in a contest.
This activity will show you how to encrypt data for the countdown app from the Cybersecurity: Radio Data [12] tutorial’s Send and Receive Packets [13] activity. Make sure you understand it!
After that, your task will be to follow the same steps to encrypt the terminal controlled cyber:bot app from the Cybersecurity: Navigation Control from a Keyboard [14] tutorial’s Terminal Control — Go Wireless! [14] Activity.
When you have a working radio data app, it only takes three steps to update the script so that it transmits encrypted data. Likewise, it only takes three steps to update a receiver script to decrypt the data.
Transmitter:
Receiver:
Before adding encryption, it’s best to make sure the original app is working.
Now that you’ve got the original app working, follow the steps to update the scripts to send and receive encrypted data. Again, it’s important to follow the steps because you’re on your own when it comes to encrypting the wireless keyboard cyber:bot control.
from microbit import * import radio # Step 1: Add the cipher function below the transmitter # script’s import statements. (6 statements) def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result # Step 2: Set an encryption key with a statement like key = 5. key = 5 radio.on() radio.config(channel=7,length=50) sleep(1000) print("Countdown App") print("micro:bit sender") while True: text = input("Enter countdown start: ") value = int(text) message = input("Enter message after countdown: ") dictionary = { } dictionary['start'] = value dictionary['after'] = message packet = str(dictionary) print("Send: ", packet) # Step 3: Call the cipher function to encrypt the data # before sending the packet. packet = ascii_shift(key, packet) radio.send(packet) print()
# countdown_receiver_encrypted.py from microbit import * import radio # Step 1: Add the cipher function below the transmitter # script’s import statements. (6 statements) def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result # Step 2: Set a key to decrypt with a statement like key = -5. key = -5 radio.on() radio.config(channel=7,length=50) sleep(1000) print("Countdown App") print("micro:bit receiver\n") while True: packet = radio.receive() if packet is not None: # Step 3: Call the cipher function to decrypt the data # after receiving/before using the packet. packet = ascii_shift(key, packet) print("Receive: ", packet) print() print("Parse: ") dictionary = eval(packet) value = dictionary['start'] message = dictionary['after'] print("value = ", value) print("message = ", message, "\n") while value >= 0: print(value) sleep(1000) value = value - 1 print(message) print()
By changing one print statement and adding another, you can monitor and compare the plaintext and ciphertext versions of the packets.
# Transmitter Excerpt packet = str(dictionary) # print("Send: ", packet) print("packet: ", packet) # <- change # Step 3: Call the cipher function to encrypt the data # before sending the packet. packet = ascii_shift(key, packet) radio.send(packet) print("Encrypted message: ", packet) # <- add print()
# Receiver Excerpt packet = radio.receive() if packet is not None: print("Receive encrypted: ", packet) # <- add # Step 3: Call the cipher function to decrypt the data # after receiving/before using the packet. packet = ascii_shift(key, packet) print("packet: ", packet) # <- change print()
Up to this point, you just finished adding encryption to the countdown app from the Cybersecurity: Radio Data [13] tutorial’s Send and Receive Packets [13] activity. Now, let’s apply the same technique to the wireless cyber:bot keyboard control app from the Cybersecurity: Navigation Control from a Keyboard [16] tutorial’s Terminal Control — Go Wireless! [16] activity. If you can do this, you will have a level of protection against attackers in future cyber:bot competitions.
Links
[1] https://python.microbit.org/v/2
[2] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-encryption-intro
[3] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data
[4] https://python.microbit.org
[5] https://learn.parallax.com/tutorials/language/python/cybersecurity-radio-basics/texting-terminals
[6] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-basics
[7] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-sniffing-attacks-and-defenses/share-something-personal
[8] https://learn.parallax.com/tutorials/robot/cyberbot/exception-handling-primer
[9] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-encryption-intro/encrypt-and-decrypt-terminal/caesar-cipher
[10] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-basics/texting-terminals/bidirectional-texts
[11] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-encryption-intro/ascii-and-other-simple-ciphers
[12] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data
[13] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data/send-and-receive-packets
[14] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard/terminal-control-%E2%80%94-go-wireless
[15] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data/send-and-receive-packets
[16] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard/terminal-control-%E2%80%94-go-wireless-1
[17] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard/terminal-control-%E2%80%94-go-wireless
[18] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard/terminal-control-%E2%80%94-go-wireless-6
[19] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard/terminal-control-%E2%80%94-go-wireless-1