G11: Secure TOTP - shalan/CSCE4301-WiKi GitHub Wiki

Project Title: Secure TOTP

Name GitHub
Ali Elkhouly khoulykd
Peter Aziz peter-aziz
Moaz Hafez justagoat21

1. The Proposal

Abstract / Elevator Pitch:

Modern two-factor authentication (2FA) is one of the most effective defenses against unauthorized account access, yet most people interact with it only through their smartphones. This project brings that mechanism down to the hardware level by building a standalone TOTP (Time-Based One-Time Password) token from scratch — a physical embedded device that generates the same 6-digit codes used by Google Authenticator and similar apps, with no internet connection required.

The core problem is that traditional passwords are static and easily compromised. TOTP solves this by generating a new password every 60 seconds using a shared secret key and the current Unix timestamp, meaning the code is useless after it expires. Our system implements this on a microcontroller paired with a real-time clock (RTC) module, running the HMAC-SHA1 cryptographic algorithm in embedded C. A companion Python script running on a PC independently computes the same codes — if both match, the system works.

The engineering challenge lies in the synchronization and drift management between the hardware clock and real-world time, implementing a non-trivial cryptographic function in a resource-constrained environment, and demonstrating a complete hardware-software co-design with a meaningful security application.

Proposed Solution

A standalone embedded TOTP token built on the STM32L432KCU microcontroller, paired with a DS3231 RTC module for timekeeping. The device computes HMAC-SHA1 over the current time and a shared secret key, applies RFC 4226 dynamic truncation to produce a 6-digit OTP, and displays it on a PmodCLP LCD. A companion Python script running on a PC independently computes the same codes from the same secret — if both match, the system is validated.

Time synchronization is handled over UART: pressing a button on the STM32 sends a SYNC_REQ to the PC, which responds with the current time. The MCU parses the response and programs the DS3231 over I2C, eliminating clock drift.

Project Objectives & Scope:

Minimum Viable Product:

  • Microcontroller reads current time from an RTC module (DS3231 via I2C)
  • HMAC-SHA1 implemented or ported to embedded C
  • TOTP code computed inspired by RFC 6238 and displayed on OLED/LCD every 60 seconds
  • PC-side Python script generates identical codes from the same shared secret
  • Codes match consistently across both devices

Functional Requirements

# Requirement Priority
FR-1 MCU reads current time from DS3231 RTC via I2C Must Have
FR-2 HMAC-SHA1 computed on-device using STM32 X-CUBE-CRYPTOLIB Must Have
FR-3 OTP displayed on 16×2 LCD, refreshed every second Must Have
FR-4 PC Python script generates identical codes from the same shared secret Must Have
FR-5 Codes match consistently between device and PC Must Have
FR-6 Button-triggered UART time sync between device and PC Must Have
FR-7 OTP refreshes when the minute changes (time window boundary) Must Have
FR-8 Sync timeout detected and reported over UART Should Have
FR-9 Visual countdown or time display alongside OTP Should Have

2. System Architecture

2.1 High-Level Block Diagram:

High-Level Block Diagram

Subsystem Breakdown:

The system is organized into five interacting modules: secure key provisioning, time synchronization, OTP generation, user display, and PC-side validation. Before operation, the same shared secret is loaded onto both the MCU and the Python script, and it is never transmitted during runtime. At startup (or during manual resync), the PC sends a Unix timestamp to the MCU over UART, and the MCU programs the DS3231 RTC over I2C. During normal operation, the MCU reads RTC time every 60 seconds, applies HMAC-SHA1 with the shared secret, and generates the 6-digit TOTP value inspired by RFC 6238. The code is shown on the OLED over SPI/I2C, while the PC script independently computes its own TOTP from system time and the same secret; matching outputs confirm correct synchronization and implementation.

2.2 Software Architecture

The firmware runs on FreeRTOS (CMSIS-OS v2) with four concurrent tasks:

Task Stack Responsibility
RTC_Task 512 B Reads DS2321 every second, updates shared time buffers
OTP_Task 512 B Computes HMAC-SHA1 every second, updates current_otp
Uart_LCD_Task 512 B Prints time+OTP over UART and LCD; handles button-triggered SYNC

Shared data is protected by two FreeRTOS mutexes:

  • Time_Mutex — guards hourbuffer, minbuffer, secbuffer
  • OTP_Mutex — guards current_otp A volatile flag sync_done signals RTC_Task to pause buffer writes during an active UART sync to prevent race conditions.

3. Hardware Design

3.1 Components & Bill of Materials

Component Part Qty Est. Cost
Microcontroller STM32L432KCU (Nucleo-32) 1 ~$15
RTC Module DS3231 breakout (I2C) 1 ~$2
Display 16×2 Digilent PmodCLP LCD 1 ~$3
Switch generic Switch 1 ~$0.50
Button Tactile pushbutton 1 ~$0.10
Breadboard + wires 1 ~$5
Battery + Battery pack 3.7V 1 ~$5
Total ~$31

3.2 Wiring / Pinout

Signal STM32 Pin Connected To
I2C1_SCL PA9 DS3231 SCL
I2C1_SDA PA10 DS3231 SDA
LCD RS PB0 HD44780 pin 4
LCD E PB1 HD44780 pin 6
LCD DB4 PA3 HD44780 pin 11
LCD DB5 PA4 HD44780 pin 12
LCD DB6 PA5 HD44780 pin 13
LCD DB7 PA6 HD44780 pin 14
UART TX PA2 USB-UART (ST-Link)
UART RX PA15 USB-UART (ST-Link)
Button PA8 Tactile switch → GND

4. Implementation

4.1 Major Implementation Details

HMAC-SHA1 and OTP Generation

The OTP is computed using ST's X-CUBE-CRYPTOLIB (cmox_mac_compute with CMOX_HMAC_SHA1_ALGO). The message input is a 2-byte seed composed of the current hour and minute in BCD. After HMAC-SHA1 produces a 20-byte MAC, RFC 4226 dynamic truncation is applied:

uint8_t offset = mac_out[19] & 0x0F;
uint32_t binary = ((mac_out[offset]     & 0x7F) << 24) |
                  ((mac_out[offset + 1] & 0xFF) << 16) |
                  ((mac_out[offset + 2] & 0xFF) <<  8) |
                  ( mac_out[offset + 3] & 0xFF);
current_otp = binary % 1000000;  /* 6-digit OTP */

The OTP changes whenever the minute changes, giving a ~60-second validity window.

RTC Communication (DS3231 over I2C)

The DS3231 stores time in BCD format. Each field (seconds, minutes, hours) is read by first writing the register address then reading one byte back:

HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &reg, 1, 10);
HAL_I2C_Master_Receive (&hi2c1, 0xD1, &val, 1, 10);

Hours are masked with 0x3F to strip the 12/24-hour mode bit.

UART Time Synchronization

When the button on PA8 is pressed (active low), Uart_Task sends SYNC_REQ\r\n over UART and blocks for up to 8 seconds waiting for a SYNC:HHMMSS\r\n response (13 bytes). The PC Python script detects SYNC_REQ and immediately replies with the current system time. The MCU parses the ASCII digits into BCD and writes them to the DS3231:

uint8_t h = ((sync_buf[5]-'0') << 4) | (sync_buf[6]-'0');
uint8_t m = ((sync_buf[7]-'0') << 4) | (sync_buf[8]-'0');
uint8_t s = ((sync_buf[9]-'0') << 4) | (sync_buf[10]-'0');

4.2 Key Design Choices

FreeRTOS over bare-metal superloop — Four independent concerns (RTC reading, OTP computation, UART I/O, LCD rendering) map cleanly to separate tasks. Mutexes prevent data races on shared time and OTP values without disabling interrupts.

BCD time format throughout — The DS3231 natively stores BCD. Keeping values in BCD until the final snprintf call eliminates conversion overhead and round-trip errors.

HMAC-SHA1 seeded with hour+minute only — Using only H:M (not seconds) as the HMAC input gives a stable OTP for a full minute, matching standard TOTP window behavior and reducing unnecessary recomputation.

4.3 Technical Challenges and Solutions

Challenge Root Cause Solution
LCD blank after splash screen HAL_Delay() broken inside FreeRTOS tasks (SysTick hijacked) Replaced all LCD timing with NOP busy-wait loops
LCD never initialized LCD_Init() called after osKernelStart(), which never returns Moved LCD_Init() and splash screen before osKernelStart()
SYNC TIMEOUT on STM32 HAL_UART_Receive window expired before Python responded Added ser.flush() on PC side; reduced Python processing latency
Wrong serial package pip install serial installs an unrelated package Must use pip install pyserial; both expose import serial
COM port not found on Linux Windows-style COM10 port name used on Linux host Changed to /dev/ttyACM0 (ST-Link CDC ACM device)
High power consumption during standby RTC must remain continuously powered to preserve accurate time, but keeping all peripherals active drains the battery Added a hardware power switch to disconnect all non-essential components while keeping the RTC powered independently

5. Testing and Validation

5.1 Unit Testing

RTC Driver — Verified DS3231 I2C communication by writing a known time (21:47:00) at startup and reading it back over UART. Confirmed BCD encoding/decoding is consistent.

HMAC-SHA1 / OTP — Cross-validated OTP output against the PC Python script using the same secret key and same H:M input. Both devices produced identical 9-digit values.

LCD Driver — Isolated LCD hardware with a static "HELLO LCD / DEBUG OK" test in StartLCDTask before connecting shared data, confirming the display and wiring were correct independently of the RTOS.

UART Sync — Manually triggered sync via button press and confirmed SYNC OK response over serial monitor. Verified DS3231 time updated to match PC time immediately after sync.

5.2 Integration Testing

With all four tasks running simultaneously:

  • UART output confirmed correct time and OTP printing every second
  • LCD confirmed displaying matching time and OTP values
  • OTP value on LCD matched OTP value on PC Python script
  • Button press triggered sync and OTP updated to reflect new time window within 1 second

5.3 Known Limitations

  • OTP window is ~60 seconds (minute-granular), not the standard 30-second TOTP window, because the seed uses H:M only
  • DS3231 has no automatic drift compensation — long-term accuracy depends on periodic UART sync
  • No persistent secret key storage — key is hardcoded in firmware and Python script
  • LCD NOP timing is calibrated for 32 MHz system clock; recalibration needed if clock frequency changes

6. Division of Labor

Team Member Primary Contributions
Ali & Moaz STM32 firmware architecture, FreeRTOS task design, RTC I2C driver
Peter & Ali HMAC-SHA1 integration (X-CUBE-CRYPTOLIB), OTP truncation logic
Moaz LCD driver (HD44780, 4-bit mode), display task, timing debug
Peter PC Python sync script, UART protocol, integration testing

7. Media

Demo Video

https://github.com/user-attachments/assets/375b2cb7-8835-4a21-a7f3-76e297f111de

Photos

Breadboard wiring — STM32 + DS3231 + LCD

UART Output Sample

STM32: Time: 01:12:34 | OTP: 778974
STM32: Time: 01:12:35 | OTP: 778974
STM32: SYNC_REQ
Sent:  SYNC:011236
STM32: SYNC OK
STM32: Time: 01:12:36 | OTP: 778974
STM32: Time: 01:13:00 | OTP: 711066   ← OTP updates on minute boundary

8. Appendices & References

8.1 Source Code Repository:

https://github.com/Peter-aziz/TOTP

8.2 Presentation Slides

Presentation Slides

8.3 References: