← Tejasvi Konjeti
RSA Attendance System · ESP32 + browser cryptography Summer program project · tested on hardware

A student ID that never crosses the Wi-Fi in the clear.

An attendance page served over plain http on a shared classroom network sends student IDs as readable text to anyone running a packet capture. In this system the browser encrypts the ID with RSA before it leaves the laptop, and an ESP32 microcontroller holds the only key that can read it. No plaintext student ID was found in the captured test traffic.

The top and bottom of a Wireshark window: the display filter frame contains followed by the test student ID, and the status bar reading Packets: 557, Displayed: 0.
Figure 1. The proof. Searching a 557-packet capture of two real submissions for the student ID returns nothing: 0 packets, and 0 of 44 reassembled TCP conversations. Recorded on the board on September 13, 2026. The top and bottom of the same Wireshark window are shown; the empty packet list between them is cut.

The problem

If a classroom attendance page runs over plain http on the school Wi-Fi, every student ID typed into it crosses the air as readable text, and anyone on the same network with a free packet-capture tool can collect the whole class list.

The goal was a system where the ID is unreadable in transit, and where that claim can be checked by anyone with a capture rather than taken on trust.

System architecture

Two devices and one shared network. The middle lane of the diagram is the Wi-Fi: everything written there is what an attacker can record, and none of it is the student ID.

BROWSER · LAPTOP THE WI-FI · ALL AN ATTACKER CAN CAPTURE ESP32 · THE SERVER 1 · Page opens asks for the public key Key pair made at boot 2048-bit RSA, e = 65537 private key d stays here GET /key n, e: the public key only 2 · Student types an ID random padding added c = m^e mod n in JavaScript, with BigInt POST /attend: 512 hex digits of ciphertext 3 · Decrypt and store m = c^d mod n padding stripped time from its own clock 4 · Live table refreshes every 3 seconds Records in RAM full IDs on USB serial only GET /records: IDs masked as ******45 Never on the wire: the student ID, or the private key.
Figure 2. How a check-in travels. The ESP32 hands out only the public half of its key. The browser pads the ID with random bytes and encrypts it before anything is sent. Only the ESP32 can decrypt it, and the records that travel back to the table are masked to the last two digits.

Why it is built this way

  • The encryption has to happen in the browser. Encrypting on the server would be pointless: the plain ID would already have crossed the network.
  • The private key never leaves the board. The key export asks only for n and e and passes nothing for p, q and d, so the secret half is never copied out, turned into text, or sent.
  • The RSA math is written out by hand. The browser’s Web Crypto encryption functions only work on secure (https) pages, and the ESP32 serves plain http, so the page computes c = m^e mod n itself with JavaScript BigInt. Its random padding still comes from crypto.getRandomValues, which works on http.
  • Random padding is required, not decoration. Student IDs are short and predictable, so without it an attacker could encrypt every possible ID and compare. PKCS#1 v1.5 padding makes every ciphertext different.
  • The return trip leaks too. The live table comes back over the same Wi-Fi, so IDs are masked to their last two digits.
  • The timestamp comes from the server. The ESP32 stamps each record from its own internet-synced clock, so a client cannot fake when it checked in.

Tested on the real board

Every claim was checked against a Wireshark capture of real submissions, the same recording an attacker on the network would make. The weakness the tests found is reported alongside the passes.

CheckResult
Search every packet for the student ID0 packets, and 0 of 43 reassembled TCP conversations
Submit the same ID twiceTwo completely different ciphertexts
Replay a captured request with curlAccepted: a second record was created without the attacker ever learning the ID
Change one hex digit of a captured ciphertextDecryption fails, so the row is saved with a blank ID

Measured on the board on September 14, 2026, in a 541-packet capture of the final code.

Wireshark's packet detail for an attendance submission: the only form field is ciphertext, followed by a long hexadecimal value, with the raw bytes shown alongside.
Figure 3. What a submission actually carries: the only form field in the POST /attend request is ciphertext, 512 hex digits. A second submission of the same ID carried a completely different ciphertext.

What it does not stop

RSA as used here gives confidentiality, not freshness. The ESP32 accepts any ciphertext that decrypts, however many times it has seen it, which is why the replay test succeeded.

The proposed fix is a one-time nonce. Before each submission the page asks the ESP32 for a random number it will accept once, and encrypts it together with the ID. A replayed request carries a nonce that is already spent, and the attacker cannot wrap a fresh one around the old ciphertext without knowing the ID.

The other gaps are written down rather than hidden: nobody is authenticated, so anyone who knows a classmate’s ID can submit it; over plain http the browser cannot prove it is talking to the real ESP32, the most serious gap; records live in RAM and vanish on a restart; and whoever holds the USB cable sees full IDs on the serial console.

The record

Built
summer 2026 program · tested September 2026
Hardware
ESP32 Dev Module
Code
271 lines · one Arduino sketch · no external libraries
Encryption
2048-bit RSA · e = 65537 · PKCS#1 v1.5 padding
Requirements
9 functional · all shown on hardware
Evidence
4 Wireshark captures · Sep 13–14 2026
My role
solo · designed, built and tested it
Repository
private

Stack

Device
ESP32 · Arduino C++ · mbedTLS for key generation and decryption
Browser
A page served by the ESP32 itself · RSA in JavaScript with BigInt · padding from crypto.getRandomValues
Testing
Wireshark captures and display filters · curl for the replay test · OpenSSL