Up to this point, the scripts have used simple PINs with three or four binary digits. If the scripts instead use decimal digits, the increase in possible combinations improves the security by making it more time consuming to crack the PIN with brute force.
In this activity, you will use scripts that repeat what you’ve done up to now, but with decimal digits in the 0 through 5 range. Since each digit has six possible values, the number of combinations is:
6 x 6 x 6 = 63 = 216 combinations
# decimal_pin_pad_transmitter from microbit import * import radio radio.on() radio.config(channel=7) pin = '' n = 0 while True: x = len(pin) if button_a.was_pressed(): if n < 5: n += 1 if n is not 0: y = n - 1 display.set_pixel(x, y, 9) else: for y in range(0, 5): display.set_pixel(x, y, 0) n = 0 if button_b.was_pressed(): pin += str(n) n = 0 if len(pin) == 3: radio.send(pin) display.scroll(pin) pin = '' display.clear()
# decimal_bank_vault_receiver # <- change from microbit import * import radio import random radio.on() radio.config(channel=7) pin = '324' # <- change while True: display.show(Image.SQUARE_SMALL) # <- change message = radio.receive() if message: pin_entered = str(message) if pin_entered == pin: radio.send("Access granted.") for n in range(4): display.show(Image.YES) sleep(1000) display.clear() sleep(200) else: radio.send("Access denied.") display.show(Image.NO) sleep(3000) display.clear()
Again, let’s check to make sure each micro:bit is running the correct script.
Now, as you press and release the A button, LEDs will light from the top downward. No lights in a column means 0. One light means 1, and so on, up through five lights for 5.
Compared to the first binary pin_pad_transmitter script, this decimal version has the same statements, up through x = len(pin).
# decimal_pin_pad_transmitter.py from microbit import * import radio radio.on() radio.config(channel=7) pin = '' n = 0 while True: x = len(pin)
When button A is pressed to select the next digit, the script remembers that digit by increasing the value of n. It also represents 0 with no lights, 1 with the row 0 LED on, 2 with the row 1 LED on, and so on, up through 5 with the row 4 LED on. The if n < 5 block takes care of adding 1 to n and successively turning on each LED in the column. If all the lights are on and you press A again, the else block takes care of setting n to 0 and turning off all the lights.
if button_a.was_pressed(): if n < 5: n += 1 if n is not 0: y = n - 1 display.set_pixel(x, y, 9) else: for y in range(0, 5): display.set_pixel(x, y, 0) n = 0
Take a close look at if n is not 0 above. It prevents any lights from turning on when n is 0. When n is 1, it has to turn on the light in row 0. Since the row with the light on has an index of one below the value of n, y = n – 1 stores the value of the correct row index in y to turn on the correct LED.
When button B is pressed, the script has to update the pin string and reset n to 0 so that you can enter the next column. If button B is pressed to enter a digit in the third column, it also has to send the pin string to the decimal vault micro:bit.
if button_b.was_pressed(): pin += str(n) n = 0 if len(pin) == 3: radio.send(pin) display.scroll(pin) pin = '' display.clear()
In pin += str(n), the str(n) call returns a string with a single digit character that represents the value n stores. That character could be '0', '1', '2', '3', '4', or '5'. The pin += part appends that character to the pin string. When the pin string is appended with the third digit, len(pin) will return 3. At that point, the if len(pin) == 3 block sends the pin string to the Vault Receiver micro:bit, scrolls the digits across the LED display, resets pin to an empty string, and clears the display.
Aside from the comment with the script’s name, only three other lines were changed. In the original binary version, the script initialized pin to '011', then displayed a dot in the center of the LED display.
pin = '011' while True: display.set_pixel(2,2,9)
In the decimal version, all that really needed to change was the pin string—from '011' to '324'. Since there might be some confusion about whether the original binary or updated decimal version of the vault receiver script is running, the single pixel in the center of the binary version was changed to a small square in the center of the decimal version.
pin = '324' while True: display.show(Image.SQUARE_SMALL)
The brute force attack for the decimal vault is almost ready. All you have to do is add '2', '3', '4', and '5' to the digits list. The nested loops automatically go through all the items in the digits list regardless of how many items it contains. So, instead of eight combinations, the updated list will cause the script to try up to 216 combinations.
# decimal_bank_vault_crack.py # <- change from microbit import * import radio radio.on() radio.config(channel=7) # digits = ['0','1'] # <- comment (before change) digits = ['0','1','2','3','4','5'] # <- change display.show(Image.ARROW_W) while True: if button_a.was_pressed(): display.clear() for a in digits: for b in digits: for c in digits: pin = ''.join([a, b, c]) print("pin =", pin) for x in range(3): for y in range(int(pin[x])): display.set_pixel(x, y, 9) response = None while response is None: radio.send(pin) sleep(100) response = radio.receive() print(response) if response == "Access granted.": while True: display.scroll(pin) sleep(4000) display.clear()
While you are waiting for the crack script to succeed, let’s calculate how long it will take.
Since each digit counts from 0 to 5, that’s 6 possible digits: 0, 1, 2, 3, 4, and 5. After the right digit has counted through its 6 possibilities, the middle digit increases by 1, and the right digit has to start over. All told, the two right digits have 6 x 6 = 36 combinations. The third digit also has 6 possibilities, and for each of those, the right two digits must go through all their combinations. So, that’s 6 x 36 = 216.
More generally, if p = number of possible values for each digit, d = the number of digits, and c = the number of combinations, you can calculate the possible values like this:
c = pd
Let’s try it with p = 6 and d = 3. That’s:
c = 63= 216.
Now, remember that there’s a 4 second delay between each try. So the number of seconds for all combinations would be:
216 x 4 seconds = 864 seconds.
846 seconds x ( 1 minute / 60 seconds ) = 14.4 minutes.
Also, to reach 324, the decimal bank vault crack will have to go through this many combinations:
Digit-left : 3 repetitions x 36 = 108
…because the middle and right digits have to go through their cycles for each time the left digit increases by 1.
When digit-left = 3, that’s the fourth repetition, and the right digits still have some cycles.
Digit-middle needs to go through 2 more cycles, x 6 for the right digit = 12
On the middle digit’s 3rd cycle, the right digit has to count 0, 1, 2, 3, 4, which is 5 repetitions.
Total: 108 + 12 + 5 combinations = 125 combinations.
In terms of minutes, that’s:
125 combinations x 4 seconds/combination x (1 minute / 60 seconds) = 8.33 minutes.
What happens if you increase the number of digits to 4?
Answer: _______ (216 x 6 = 1296)
What’s the longest a brute force attack would take in that case?
Answer: _______ (1296 combinations x 4 seconds/combination = 5184 seconds. 5184 seconds x 1 minute / 60 seconds = 86.4 minutes. That’s over 1 hour and 26 minutes.)
Even if you add a fourth digit to the PIN, it can still be cracked in under two hours. How do modern cell phones, tablets, and teller machines deal with this problem and still keep PIN numbers short enough to be memorable? One technique they use is to only allow you a certain number of failed tries before making you wait a longer time.
Here is a terminal password example you can run on one micro:bit to understand how this process works.
# if_three_pin_fails_wait_an_hour from microbit import * sleep(1000) pin = '324' fails = 0 while True: message = input("Enter PIN: ") if message == pin: fails = 0 print("Access granted.") else: fails += 1 print("Access denied.") if fails > 2: print("Oops, 3 fails in a row!") print("Try again in an hour.") sleep(3600000) fails = 0
Assuming you didn’t have the ability to press and release the micro:bit module’s reset button after 3 incorrect tries, it would take 216 combinations x 1 hour/combination = 216 hours. You could further increase the security by having it make you wait a day before trying again, maybe after the 6th fail.
There is still a glaring vulnerability in this system! Any micro:bit listening on the same channel will still receive a correctly entered PIN. In other words, the PIN is still totally vulnerable to sniffing attacks!
Links
[1] https://learn.parallax.com/node/2284