Building a One-time Password Token

One-time passwords are an alternative to the more common "permanent" passwords. The problem with a "normal" password is that it can be captured during communication, by fake login screens or by key-loggers.  Once a normal password is captured, it can be used until it is changed.

To avoid this "repetition attack", one-time passwords work just once. For each login, a new password is used. This creates a new problem: how to set up these passwords? One solution is to have a "password book", but that is very clumsy.

In this post, we will see how to build a device that generates one-time passwords, one at a time. The other side can use the same algorithm to check the passwords. We will use a Raspberry Pi Pico W board with a MicroPython application.

WARNING: This is a demo only. The encrypting key will be in plain text in the code, open to any curious eyes. Do not use this with any real key!

A Little Theory

What we will use is an HMAC-based one-time password (HOTP). Both sides keep a counter and know a secret key. The counter is processed by a cryptographic hash (HMAC); the password is extracted from the resulting hash. The algorithm used is described in RFC 4226, and is the same employed by many sites (like Github) and applications (like Google Authenticator).

The counter is created from the current UTC time. To account for clock variations and user response times, the counter is incremented every 30 seconds. Furthermore, the side checking the password can accept the previous and next passwords. To account for long-time drift (in cases where the device clock is set only at manufacturing), the checking side can record the differences and take them into account in the next checks. The code uses the Unix Epoch (number of seconds from 1/1/1970 00:00:00 UTC).

HMACs are normally used to check a text integrity. They generate a value (the hash) from a text and a key. RFC 4226 mandates the use of HMAC-SHA1, resulting in a 160-bit (20 bytes) hash. The input text is the counter, encoded as 8 bytes with the more significant byte first (big-endian).

To extract the password from the 20-byte hash, we will:

  • Get an offset (0 to 15) from the 4 least significant bits in the last byte
  • Obtain a 32-bit number from the 4 bytes starting at the offset (more significant byte first, ignoring the signal bit)
  • Extract the 6 least significant digits from the decimal representation of this 32-bit number

Implementation

We will use a Raspberry Pi Pico W board connected to a small 128x32 OLED display:

Check the pinout for your display, mine was different!

To obtain the counter, we will:

  • Connect to a WiFi network
  • Get the current time using the NTP protocol
  • Divide the current time by 30
  • Convert the result into a bytearray
Note: On RP2040-based boards, MicroPython uses the Unix Epoch. On some other boards (like ESP32-based boards) the epoch is 1/1/2000.

The SHA1 algorithm is available MicroPython in the hashlib library. HMAC needs to be downloaded from https://github.com/micropython/micropython-lib/tree/master/python-stdlib/hmac and installed in the Lib directory on the board.

The driver for the OLED display is available at https://raw.githubusercontent.com/micropython/micropython-lib/master/micropython/drivers/display/ssd1306/ssd1306.py and needs to be installed in the Lib directory on the board.

The project is available at https://github.com/dquadros/otp_token. To use the software you will need to put your WiFi credentials in the secrets.py file. If you want, you can also change the HMAC key.

Testing

An easy way to test our project is to use the Google Authenticator app on an Android phone. You can register the secret key using a QRcode containing

otpauth://totp/{name}?secret= {secret}

where {name} is the name shown in the app and {secret} is the secret key coded in Base32.

In my repository, I included a Python script that can be run on a PC to generate the qrcode. This program requires the segno module.

Further Ideas

You can use this software with other boards supported by Micro Python, the main difference will be the display connections. Supporting other types of display should not be too hard.

You can also adapt the project for offline operation, setting manually the date and tine. An external battery-backed RTC (such as the DS1307) can be used to achieve greater precision and maintain the time when the Pico is powered off.

The biggest problem with this example is that the secret key is exposed. It would be very hard to avoid this using MicroPython. A professional solution should be written in C for the Pico 2 (or other board using the RP2350), storing the secret key in the OTP, encrypting the firmware, and disabling debugging.

Comments

Popular posts from this blog

Using the PIO to Interface a PS/2 Keyboard