Generating a door lock code

MP 130: How do you make a reasonably secure access code for an electronic door lock?

We recently moved into a new house, and one of the first tasks after moving in is to change the locks. Rather than just rekey the existing locks, we decided to install electronic locks. It's really nice to not have to carry house keys around when going out for a walk! It also means we don't have to worry about anyone losing a key, and when we have visitors we can share a temporary code with them. (Modern electronic locks have many interesting features beyond just setting a single entry code.)

One of the tasks when installing an electronic lock involves choosing a key code. Humans are terrible at choosing random sequences, so I wrote a short program to generate a reasonably secure access code.

What kind of electronic lock?

There are all kinds of electronic locks available these days. If you search for "electronic door locks", the first results usually show "smart" locks. These are really helpful for landlords, especially people who run short-term rentals. They let you change the code remotely, manage multiple codes for different people who need to enter the home, and see exactly when those codes were used.

As a homeowner, I don't want a wifi-connected door lock. We don't need to change codes often, and we shouldn't have to change the code remotely. Without that need, the wifi connectivity seems like little more than a vulnerability. We chose a simple electronic lock that doesn't have any kind of connectivity. To gain access, someone would have to guess the code or physically break the lock.

search results from Lowes.com, showing 130 results for "smart compatible: no"
Everyone pushes "smart" devices, but there are many choices of simple electronic locks that don't offer any connectivity.

I chose a lock that has a key as well, because it feels like a nice backup to have if there are any issues with the electronics. Since there's more than one door with the same lock, I don't think this is particularly important. The likelihood of both locks, on different sides of the house, failing at the same time seems pretty small.

I was surprised to learn that most electronic locks, even non-smart locks, allow you to set multiple codes. This is really helpful. For example you can keep one code for your family, set a separate code for visitors, and delete that code after the visit is over. You can also set one-time codes that get automatically deleted after they're used, if you need to grant someone access only once.

Setting a code

If the existing door doesn't have any physical issues such as being misaligned, installing a new lock is pretty straightforward. It took about half an hour to replace the first lock, and about ten minutes to replace the second lock. So it was pretty quickly time to choose an access code.

Choosing a code length

The lock we chose allows you to set a code from four to eight digits in length. Most of the codes I've used to open electronic door locks are four digits long. That seems to make sense for codes that are shared with people for short-term use. However, it's always struck me as a bit weak. I know there are 10,000 choices with four digits, but there are other reasons I don't feel as comfortable using a four-digit code over an extended period for our home. A repeated digit means there's only three digits to guess. More significantly, if any of the digits on the keypad become worn, people can see which digits are being used.

I like 6-digit PIN codes. They seem much more secure than 4-digit codes, and easier to remember than 8-digit codes. I can remember a 6-digit code more easily than an 8-digit code, which means I'm okay with a truly random 6-digit code. It's also easier to enter, so we won't be standing at the door reentering the code as often as we would with a longer sequence.

Generating a sequence

This is where I got stuck. People are tempted to come up with a code that's easy to remember, such as someone's birthday or birth year. But that's a bad idea, because those kinds of codes can be guessed by anyone interested in entering your home. I'm not overly paranoid about a targeted break-in, but it's easy enough to get a little more security by avoiding common vulnerabilities.

There are a number of articles describing how to set a reasonably secure access code. Here's one example, showing how tempting it is for people to fall into insecure patterns. It's best to avoid personal information such as birthdays, addresses, zip codes, and simple sequences like 123456, 654231, or 112233.

Knowing all this, I realized I could just generate a random code with Python. I thought it would be a one-liner, but it quickly got more interesting than that.

A Python one-liner

Here's the "one-liner" that should generate a random 6-digit access code:

from random import randint

keycode = randint(0,999999)
print(keycode)
key_code.py

It seems you should just be able to ask for a random number between 0 and 999,999. But here's one issue:

$ python key_code.py
38025

This is a random number, but it's only five digits. To make this a key code, you'd need to use 038025.

I could use this code, but I wasn't satisfied with a program that generates a bunch of useless codes such as 000023. So I started to think about how to approach this a little differently.

Sequences, not numbers

I started to think about the key code as a sequence of characters instead of a single number. That's how I always think about PINs; I didn't think of them as numbers until starting to write a program to generate an access code.

Here's an approach that builds a sequence of characters, rather than a single number:

from random import choices

keycode = choices("0123456789", k=6)
keycode = "".join(keycode)

print(keycode)

This approach uses the choices() function to pull six random characters from a string containing all ten digits:

$ python key_code.py
053858

This correctly formats key codes that start with zero.

CLI parameters

In a small script like this, I'm a big fan of using sys.argv or argparse to add a simple CLI. Let's add a CLI arg for the length of the key code:

from random import choices
import sys

try:
    length = int(sys.argv[1])
except IndexError:
    length = 6

keycode = choices("0123456789", k=length)
keycode = "".join(keycode)

print(keycode)

Now we can easily generate 4-digit key codes and 8-digit key codes as well:

$ python key_code.py 4
8157

With no CLI args, we'll get a default code with a length of 6 characters.

Repeated digits

I already feel much better about the access code we'll end up using. Knowing that it's a randomly generated sequence, and not just something we thought up that might be easy to remember, is reassuring. But I found one more criteria I'd like to include when reviewing the codes that were being generated.

I don't like the code 053858 that showed up earlier. It has six digits, but two of those are repeated. I actually like the idea of one digit being repeated, so it's a little easier to remember. Let's add code that makes sure this happens:

from random import choices
import sys

try:
    ...

while True:
    keycode = choices("0123456789", k=length)
    if len(set(keycode)) == length - 1:
        break

keycode = "".join(keycode)
print(keycode)

We convert the list keycode to a set, which gets rid of any duplicate numbers. If the length of that set is one less than the desired code length, then we have one repeated digit.

This version always generates a code with exactly one repeated digit:

963259
299350
549647
613145
801270
073987
201137
540491

I'd be pretty happy with any of these as a door code.

Refactoring

I don't always refactor code that's done its job, but it's nice to put the most critical code into a function:

from random import choices
import sys

try:
    ...

def get_keycode():
    """Generate a reasonably secure keycode."""
    while True:
        keycode = choices("0123456789", k=length)
        if len(set(keycode)) == length - 1:
            return "".join(keycode)

keycode = get_keycode()
print(keycode)

This generates exactly the same output as the previous listing. But if I want to come back to this code and improve the algorithm for generating codes, I can see exactly where to do that.

Conclusions

It's not overly difficult to come up with a reasonably secure access code for an electronic door lock. But if you have some programming skills, you can write a bit of code that lets you be much more confident the access code you're using meets certain criteria. And if you need to come up with codes on a regular basis, having an automated way to do so is really helpful.

If I were running this program frequently, I'd add some more logic to only repeat digits in codes that are above a certain length. For four-digit codes, I wouldn't want repeated codes. I might also add a CLI arg for how many codes to generate, so I could make a list once and then just work through those codes as needed.

Resources

You can find the code from this post in the mostly_python GitHub repository.