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.
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.
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.
c = m^e mod n itself with JavaScript BigInt. Its random padding still comes from crypto.getRandomValues, which works on http.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.
| Check | Result |
|---|---|
| Search every packet for the student ID | 0 packets, and 0 of 43 reassembled TCP conversations |
| Submit the same ID twice | Two completely different ciphertexts |
| Replay a captured request with curl | Accepted: a second record was created without the attacker ever learning the ID |
| Change one hex digit of a captured ciphertext | Decryption 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.
POST /attend request is ciphertext, 512 hex digits. A second submission of the same ID carried a completely different ciphertext.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.
crypto.getRandomValues