Time-Based One-Time Password (TOTP) Smart Safe

2,998

55

10

Introduction: Time-Based One-Time Password (TOTP) Smart Safe

In this project I will go over the steps for building a Time-based One-time passwords (TOTP) Smart Safe. If you are not familiar with TOTP here is a good article that describes What is a Time-based One-time Password (TOTP)? | Twilio.

In a nutshell, its a method for calculating a 6 digit passcode given a pre-determined key based on the current date/time. This means that as long as the Safe can keep track of time, I will be able to use my Authenticator app to get a new passcode every 30 seconds, with which I will be able to open the safe.

Update: Take this a step further and add a fingerprint sensor: Fingerprint Safe : 5 Steps - Instructables.

Supplies

  • D1 Mini (ESP8266)
  • Relay module
  • Piezo buzzer
  • Power supply (5v)
  • Prototype PCB board

Step 1: Background

I found this old safe that died due to a leaking battery. It just needed some cleanup and re-soldering of the connectors to make it work again. There is a button inside that lets you reset the pin, so overall it was operational.

When I was about to place it somewhere for actual use, I started to think what would be a good pin code to use, one that I will remember for years to come and that isn't one of the "regular" pins we use at the house, so that my kids wouldn't be able to figure it out. That made me think about using an Authenticator-based time-based one-time passwords (TOTP).


Step 2: How the Safe Works

The way this safe (and I assume many other) work is using a large lock metal bar, that can slide in and out. Once out (locked), the spring on the edge of the solenoid jumps out and prevents the lock from going back in, hence the safe cannot be unlocked.

To unlock it again, you can either use the key (in the middle, not very visible in these photos), or if the solenoid will pull itself back. This of course happens when there is current running through it. The control board manages that part. It will fire the solenoid when the correct pin is entered.

The pin is entered using the keypad on the other side of the door. The green-ish ribbon is the connection between the keypad and the control board.

Step 3: The Plan

The plan is to replace the control board with a D1 Mini that will control a relay. The relay will close a circuit between the power supply and the solenoid for a few seconds, thus opening the safe, when the correct pin is entered. We'll need to also connect the original keypad to the D1 Mini for input, and also to add a buzzer for feedback.

The D1 Mini will need to be connected to the WiFi so that it'll be able to sync the clock. This is required for the TOTP to work. We'll need to write some simple code that accepts input from the keypad and after 6 digits will compare it to the current TOTP. If it matches, it will close the relay for a few seconds to allow the safe to open.

In the pictures you can see the final setup.

  1. I used a relay module, but a bare relay can be used as well, with the needed adjustments to the board.
  2. I also used some connectors that I had, for power, for the solenoid and for the keyboard (harvested that one from the original board).
  3. I used a 5v 2A power supply, but that's probably an overkill for this solenoid. You can probably get away with something smaller that can be placed outside the box. Just make sure not to expect the D1 Mini to drive the solenoid directly.

Step 4: Decoding the Keypad

The most difficult part was to figure out how the keypad works.

In general, 3x4 keypads connect using 7 wires. 1 for each row and 1 for each column. The rows are pulled high and the columns are low, and when a key is pressed it closes the circuit and that's how the controller can figure out which key was pressed. Here is a good article that explains this: How to Set Up a Keypad on an Arduino - Circuit Basics.

So I had to figure out the order of the wires on the ribbon, and associate each with the right row / column. This required some trial and error. I used a voltmeter in diode mode to check each pair while pressing the various keys until I saw the voltmeter indicating the circuit is closed.

While I was expecting the wires to be ordered (i.e. wire 1-4 for rows and wire 5-7 for columns), it turned out that the correct order for my keypad is:

Wire 1 - Column 1
Wire 2 - Row 1
Wire 3 - Row 2
Wire 4 - Column 2
Wire 5 - Row 3
Wire 6 - Column 3
Wire 7 - Row 4


Each wire from the keypad connects to a different pin in the D1 Mini. I used the following mapping, but you can obviously connect it differently. You will need to refer to this later in the code.

const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = { 
  {'1', '2', '3'},  
  {'4', '5', '6'},  
  {'7', '8', '9'},  
  {'A', '0', 'B'} 
}; 

byte rowPins[ROWS] = {TX, RX, D2, D4}; 
byte colPins[COLS] = {D5, D1, D3}; 


Note that I used TX and RX as regular GPIO because I originally wanted to also connect LEDs (and the D1 Mini doesn't have enough pins). This obviously prevents you from using the Serial for logging and debugging.

To make this happen, you will need to add the following lines to Setup()

void setup() {
  pinMode(TX, FUNCTION_3); 
  pinMode(RX, FUNCTION_3); 
}


Once done, the following code controls the Keypad, accepts input from the user, compares to the secret (to be implemented later) and opens the safe if equals, otherwise (and also on 5 seconds of idle) resets the input buffer.

#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 3; 

char hexaKeys[ROWS][COLS] = {
 {'1', '2', '3'},
 {'4', '5', '6'},
 {'7', '8', '9'},
 {'A', '0', 'B'}
};

byte rowPins[ROWS] = {TX, RX, D2, D4}; 
byte colPins[COLS] = {D5, D1, D3}; 

unsigned long lastClickMillis = 0;
char code[6];
int codeIndex = 0;

Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS); 

void setup() {
 pinMode(TX, FUNCTION_3); 
 pinMode(RX, FUNCTION_3); 
}

void loop() {
 char customKey = customKeypad.getKey();
 if (customKey) {
  code[codeIndex] = customKey;
  codeIndex = codeIndex + 1;

  if (codeIndex == 6) {
   if (strcmp(code, $secret$) == 0) {
    // openSafe();
   } else {
    // incorrectPin();
   }
   codeIndex = 0;
   memset(code, 0, 8);
  }
 }

 if (codeIndex != 0 && (millis() - lastClickMillis > 5000)) {
  // incorrectPin();
  codeIndex = 0;  
  memset(code, 0, 8);
 }
}

Step 5: Connecting the Hardware

I connected everything on a prototype board, and drilled some holes to match the ones on the original board. This allowed me to use the same mounting screws that the original board had. I messed up the measurement a little, and it ended up a little croocked.

In the center, I placed headers for the D1 Mini. This gives plenty of space to connect the rest of the wires to the various pins.

On the top right I used a regular screw connector (POW) for power. This connects directly to the +/- from the power supply.

Below that I used the same connector that is used on the original board for connecting the solenoid (SOL). This wire gets the negative connection from the above power connector (POW), and the positive goes from the upper connector into the Relay's NO connection and back to the solenoid connector's positive connector.

The Relay module connects to the 5V and GND, and it's signal pin is connected to the D0 pin in the D1 Mini.

The buzzer is connected to GND and to the D7 pin in the D1 Mini.

Finally, the 7 pins of the Keypad are connected to D5, TX, RX and D1-D4.

Step 6: The Pin Code

In Step #3 we have the code to get input from the Keypad and construct that into a 6 digit pin. Now we need to compare it to the current pin code as calculated based on the current time and initial secret key. For this I used two libraries. ezTime (ropg/ezTime: ezTime (github.com)) is the simplest one I know for getting a micro controller's clock synced. As I mentioned before, this is required for the calculation of the one-time password. The second is a simple implementation of TOTP (lucadentella/TOTP-Arduino (github.com)).

The following code was cleaned up for brevity.

#include <TOTP.h>
#include <ezTime.h>

uint8_t hmacKey[] = {0x4D, 0x79, 0x20, 0x73, 0x61, 0x66, 0x65};
TOTP totp = TOTP(hmacKey, 7);

void setup() {
 waitForSync();
}

void loop() {
 char customKey = customKeypad.getKey();
 if (customKey) {
  code[codeIndex] = customKey;
  codeIndex = codeIndex + 1;

  if (codeIndex == 6) {
   char* tot = totp.getCode(UTC.now());
   if (strcmp(code, tot) == 0) {
    // openSafe();
   } else {
    // incorrectPin();
   }
   codeIndex = 0;
   memset(code, 0, 8);
  }
 }
}


The code above defines a key (seed) for the TOTP. The key is the string "My safe" in hex. You can use any online Text to Hex converter, such as: Text to Hex Converter - Online Toolz (online-toolz.com).

uint8_t hmacKey[] = {0x4D, 0x79, 0x20, 0x73, 0x61, 0x66, 0x65};

With this key in place, our TOTP implementation will be able to calculate a new 6 digit key every 30 seconds.

Next we need to create an entry in our favorite Authenticator app. I use Microsoft's Authenticator, but this works just as well in Google's. Both apps will require the BASE32 representation of the key ("My safe" in my case). You can use any online Base32 Encoder, for example: Base32 Encode Online (emn178.github.io).

The base32 encoded string (e.g. "My safe" -> "JV4SA43BMZSQ") is the Key to be used for the Authenticator entry. You can enter this manually in your Authenticator app, or you can use a QR. A simple online tool for creating QR codes that Authenticator apps understand is Generate QR Codes for Google Authenticator (hersam.com).

Alternatively, just for testing, you can use TOTP Generator (danhersam.com). You can manually enter the key (again, in base32 encoded string), and it will prompt you for the 6 digit one-time password, which the safe will accept in the 30 second time window.

The complete code is available in the attached file below, and also available at GitHub adi-miller/totp-safe.

Step 7: Be Careful

Needless to say, this Safe isn't very safe. There are many potential failure points here, besides the actual safe. If the D1 Mini dies, you will not be able to operate this at all. In addition, if it looses internet connectivity it will not be able to sync it's clock, and then the passcode will be incorrect. Bottom line - be careful.

Thanks for reading. Happy to hear what you think and if you are trying to build this or something similar. Please leave comments below, or follow me on Twitter @adi_miller.

Be the First to Share

    Recommendations

    • New Year, New Skill Student Design Challenge

      New Year, New Skill Student Design Challenge
    • Anything Goes Contest 2021

      Anything Goes Contest 2021
    • Fix It Speed Challenge

      Fix It Speed Challenge

    10 Comments

    0
    Fik of the Borg
    Fik of the Borg

    6 days ago

    Didn't know that TOTP had open implementations. I'm tempted to build this but for my front door ... but today is my last vacation day, so back to work tomorrow.
    Internet conectivity and electricity are sometimes erratic where I live, so I would add an RTC, GPS-based periodic sync, and a battery backup. Maybe an IR-beam matrix from the doorframe instead of a physical keyboard, with small inconspicuous dots marking where the "keys" are.

    0
    AussieMakerGeek
    AussieMakerGeek

    8 days ago

    This is awesome! I'm thinking of adding a version of this to my safe for sure! I remember looking a while ago for an OTP implementation on Arduino but it didn't exist then.

    I currently use RFID (my own implementation) so I could add this too, as true 'MFA'.

    Tip - I would highly recommend adding a DS3231 RTC chip to the project. Then you can just sync the time every now and then to the RTC so it will work even without wifi. Gives you a little extra backup.

    Also, perhaps an 8 digit, single use 'override' code might be handy?

    0
    adimiller
    adimiller

    Reply 7 days ago

    Interesting idea about using the Real-Time Clock module! This, combined with chrwei comment below about using deep sleep could void the need for Wifi connectivity altogether (except for once during setup).

    With ezTime once synced (and as long as it is powered), the time sync will be good enough for OTP for days even if it looses Wifi connectivity. So as long as it is able to connect to Wifi even once a week, it will still be usable for the purpose of OTP.

    0
    AussieMakerGeek
    AussieMakerGeek

    Reply 6 days ago

    Yeah that's a good point too - You could sleep the microcontroller and only wake on key input.

    I was so inspired by this, I've mangled some of your ideas and code into an existing project that I have which is a door access module based on an RFID reader to now give multi factor access with OTP.

    I am using:
    ESP32 (may be adaptable to ESP8266)
    DS3231 RTC
    Wiegand RFID reader (stores 20+ cards + MFA secret)
    TTP229 16 button capacitive touch keypad.

    I have also implemented:
    'Master' access code via the keypad - Silently ignores bad codes etc
    -> Will work without wifi and/or MFA so it is a fallback code.

    Uses the same time library but only to sync time on the RTC - OTP codes are generated from RTC time.
    Defaults to just RTC if no internet but keeps trying every hour.

    Generates 3 codes (last, current and next) to compare the entered code against
    -> Allows for up to 90 secs of time skew, this is how most TOTP systems work.

    Still some polishing to do and perhaps make a custom PCB for the setup but it's completely functional as-is. I'll probably publish it as my own instructable a bit later.

    Thanks so much for the inspiration!

    20220111_200210_resized.jpg
    0
    adimiller
    adimiller

    Reply 6 days ago

    Thanks for sharing! :)

    I like the 3 codes approach - I will use that.

    0
    XlaitsX
    XlaitsX

    7 days ago

    Time to adapt this to our front door lock.

    0
    agtig
    agtig

    7 days ago

    @adimiller: Just out of curiosity, is the safe susceptible to magnetic interference? Meaning, if you bring a strong magnet to the front of the safe, will it be able to either trigger the relay or the solenoid?
    Alternatively you could use solid state relays instead, and if the solenoid is also manipulable with a magnet, using a servo would solve the issue?

    0
    chrwei
    chrwei

    8 days ago

    nice idea! the key still works, right? so not any more risky than the original.

    upgrade ideas, use deep sleep and only connect to wifi and update the time when a pin is entered, it'll have a little lag before unlocking, but if you thread that on the first keypress it shouldn't be bad. the hard part of that will be making it wake on any key press. Then you can run on a lipo battery, should handle a couple years idle, but each unlock would shorten that. you'll be able to keep bd use the safe somewhere without easy access to power, like in a closet. keep the power port for charging and quick use in case battery dies.

    0
    adimiller
    adimiller

    Reply 7 days ago

    You're right about the key. I like the deep-sleep idea. Another comment above suggested using RTC - combined with deep-sleep would be the best of both worlds.