Keypad Insecurity

The following code is defined as an arduino sketch in the book - 30 Arduino Projects for the Evil Genius. This sketch is designed to provide you with a keypad based magnetic door lock. As an exercise in programming it is good fun, something new to learn, and so forth. Unfortunately it is described for fun rather than actual security.

Even as a description of fun it is necessary to point out the coding flaw in this. Those of you who were in the know would have noticed a case in the United States a couple of years ago where keys to properties currently under Real Estate control were being locked into a keypad-based system. One of the realtors figured out that they didn't need to type the keys into the keypad correctly - just that they needed to type them in at some point (order not withstanding). The Arduino based keypad does not suffer from this flaw, but it does suffer from something very like it.

Here is the code (mostly) as it appears in the first edition of 30 Arduino Projects for the Evil Genius. I have not yet received my copy of the second edition (Amazon report up to 3 months for shipping so I am holding off until that number comes down) but Si tells me the issue exists in the new edition as well.

void loop()
{
	char key = keypad.getKey();
	if ( key == '#' )
	{
		locked = true;
		position = 0;
		updateOutputs();
	}
	if ( key == secretCode[position] )
	{
		position ++;
	}
	if ( position == 4 )
	{
		locked = false;
		updateOutputs();
	}
	delay( 100 );
}

I have cut the code down slightly for brevity. This looks like a great thing to do - putting an electronic lock on my door makes it that much harder for my minions to get in. There are thousands of combinations for a 4 digit pin.

This code is a C program that you can test this locking mechanism with.

#include <stdio.h>
#include <stdlib.h>

int main()
{
	char key = '\0';
	int position = 0;
	char code[4] = "9876";

	while( 1 )
	{
		key = getchar();
		if ( key == '#' ) {
			printf("Reset\n");
			position = 0;
		}

		if ( key == code[position] )
		{
			printf("Key [%c] position [%i] found\n", key, position);
			position++;
		}
		if ( position == 4 )
		{
			printf("Unlocked\n");
			exit(0);
		}
	}
}

Compile it on your *nix machine with: gcc keypad.c -o keypad

When executing the program you will get extra information displayed on the screen to make it easier to follow what is going on. Attempting to brute force the code would take a long time, so it is necessary to attempt to exploit the application instead. Press return after each digit has been entered.

There is a logic error in the code. At the absolute maximum for a 4 digit passcode you will need 41 key presses - one # to get started, and then the sequence.

#0123456789012345678901234567890123456789

Always check your code - a compiler won't save you from logic errors. Before looking at the work around below I encourage you to read through the code and play with the C code I have provided to try and guess exactly where the logic error arises. It is important that a programmer understands how things work before entering production as a fault in logic will be very confusing.

A Workaround

Now that this code has had some time to stew and many of you have followed through with the hardware hack, we should probably present a bit of a fix before I pass it off to the original author.

During setup we will need another integer to track failed attempts and one for totals. Here is the replacement loop code.

	if ( key == secretCode[position] )
	{
		position ++;
	} else {
		failed ++;
	}
	total = position + failed;

	if ( position == 4 )
	{
		locked = false;
		updateOutputs();
	} else {
 	       if ( total > 3 )        
 	       {
 	               position = 0;
 	               failed = 0;
 	               locked = true;
 	               updateOutputs();
 	       }
	}

This loop will not prevent an unlimited brute force attempt, it will simply cancel and reset the pin code after four button presses.

Note that the total is calculated from the failed and the position - this will prevent us from typing the first three digits correctly and then brute forcing the fourth.

Here is the C code, updated to include the testing and lock (also a check because it's C to be sure that the correct key sequences have been entered (somewhat - it skips out on returns and the hash key as they shouldn't be counted as invalid).

#include <stdio.h>
#include <stdlib.h>

int main()
{
	char key = '\0';
	int position = 0;
	char code[4] = "9876";
	int total = 0;
	int failed = 0;

	while( 1 )
	{
		key = getchar();
		if ( key == '#' ) {
			printf("Reset\n");
			position = 0;
			total = 0;
			failed = 0;
		}

		if ( key != '\n' && key != '\r' && key != '#' )
		{
			if ( key == code[position] )
			{
				printf("Key [%c] position [%i] found\n", key, position);
				position++;
			} else {
				failed++;
			}

			total = position + failed;

			if ( position == 4 )
			{
				printf("Unlocked\n");
				exit(0);
			} else {
				if ( total > 3 )
				{
					position = 0;
					total = 0;
					failed = 0;
					printf("Reset\n");
				}
			}
		}
	}
}

Once again compile the software with gcc keypad.c -o keypadfixed and execute ./keypadfixed to test. Because we are printing the key found character you can perform a visual brute force on the code, however there is no way to guess the pin without brute forcing every combination.

0.0922 seconds