This project implements a secure, real-time Li-Fi communication channel between a Raspberry Pi Pico and a host computer. It is designed to be a robust and production-ready embedded system, featuring strong cryptography, persistent key storage, and a rich command interface for easy management.
This repository contains the embedded software for a secure Li-Fi transmitter (the Pico) and the necessary host-side components to manage it. The system is designed to showcase a secure communication workflow example, from initial key provisioning to real-time encrypted messaging.
- Sender (Raspberry Pi Pico): A powerful Li-Fi transmitter that encrypts messages using a persistent session key with AES-128-GCM.
- Receiver/Controller (Host): A host system (like a Raspberry Pi 4 or a PC) is responsible for the initial provisioning of the session key to the pico and can be used to receive and decrypt the Li-Fi messages.
In our system, security comes not from the secrecy of the optical signal, but from the inability of attackers to preserve its freshness over time.
In an outdoor or open environment, attackers can easily capture the optical signal. However, the system ensures security through time-bounded authorization. The receiver must respond to a challenge with valid credentials within a tight time window.
As illustrated, a "Trusted Sender" (e.g., a drone) emits a session key or challenge. A legitimate node (receiver) captures this preamble and decrypts the key. An attacker ("Evil Attacker") might capture the signal, but cannot decrypt and respond fast enough or replay old capture due to Nonce freshness checks.
This work explores access control based on continuous physical presence rather than estimated location. A device receives a short-lived cryptographic token over a light channel; the token remains valid only while the light signal is continuously observed. If the signal is blocked or interrupted, the token expires and access is revoked, either immediately or after a bounded decay window defined by policy.
This design ties authorization to time-bounded physical coupling instead of one-time checks such as GPS or Wi-Fi. We formally model the mechanism and verify key security properties—including:
-
Token secrecy
Tokens are delivered only via the optical channel and are not observable without physical light access. -
Freshness
Tokens are time-bound and require continuous, recent optical reception to remain valid. -
Replay resistance
Expired or previously observed tokens are rejected and cannot restore access. -
Relay-bounded authorization
Relay attacks are limited by the token decay window and cannot extend authorization indefinitely. -
Revocation on interruption
Blocking or loss of the light signal triggers timely expiration and access revocation.
using symbolic verification tools. Under the stated assumptions, authorization is maintained only while fresh optical presence is continuously observed.
We validate practicality with a prototype built from low-cost hardware (microcontroller, LEDs, and a photodiode), evaluating time-based enforcement, resistance to relay attacks, and imperceptible optical signaling. The result is a clear, verifiable method for binding authorization to ongoing physical presence.
The system includes (1) an optical beacon that emits short-lived presence refresh material via light, (2) a client device that receives the optical signal and maintains a time-decaying “presence capability,” and (3) a verifier/policy service that grants or revokes authorization based on the freshness of that capability.
We consider an attacker who can:
- Network control (Wi-Fi path): eavesdrop, replay, delay, drop, and inject packets between the client and verifier.
- Optical observation: observe the optical signal near the designated space using commodity sensors (e.g., camera/photodiode).
- Relay: capture the optical signal near the designated space and forward it to another device elsewhere, potentially with latency and jitter.
- Injection (optional): introduce a rogue light source to transmit crafted optical messages.
- Disruption (DoS): block or jam the light channel (“shadowing”) or disrupt Wi-Fi connectivity.
- The verifier and its long-term keys are trusted.
- Standard cryptographic assumptions hold (e.g., AEAD integrity and confidentiality).
- The system has a bounded notion of freshness (e.g., epochs/counters or bounded clock drift) to support token decay.
- Full client compromise (malware extracting secrets) is out of scope for the core protocol guarantees.
Our primary goal is presence-based authorization: a device should remain authorized only while it can demonstrate fresh physical presence within a bounded time window Δ.
Concretely, we aim to provide:
- G1 — Freshness / Expiration: authorization depends on presence material that expires automatically after Δ.
- G2 — Replay Resistance: previously observed presence material cannot be reused after expiration.
- G3 — Relay-Bounded Authorization: relay attacks cannot extend authorization indefinitely; success is limited by Δ and degrades with relay delay/jitter.
- G4 — Revocation on Interruption: loss of optical reception leads to deauthorization within Δ (immediately or via the decay window).
- We do not aim to provide precise indoor localization or positioning.
- We do not claim to prevent denial-of-service (blocking/jamming can always disrupt), but the system should fail closed and recover cleanly.
- We do not claim security if the client device is fully compromised.
- Raspberry Pi Pico (RP2040)
- Li-Fi LED transmitter module
- USB cable (for programming and debug serial)
- Raspberry Pi 4 Model B (4 GB)
- Li-Fi receiver module
For full details on the board headers:
- Raspberry Pi Pico (RP2040) → Pico Pinout (official PDF)
- Raspberry Pi 4 (40-pin header) → Pi 4 GPIO Pinout (pinout.xyz)
| Function | Pico Pin (RP2040) | Pi 4 Header Pin | Pi 4 GPIO | Notes |
|---|---|---|---|---|
| UART1 TX | GPIO4 (Pin 6) | Pin 10 | GPIO15 RX | Pico sends → Pi 4 receives |
| UART1 RX | GPIO5 (Pin 7) | Pin 8 | GPIO14 TX | Pico receives ← Pi 4 sends |
| Ground | GND (Pin 38) | Pin 6 | GND | Common ground required |
⚠️ TX ↔ RX must cross: Pico TX → Pi RX, Pico RX ← Pi TX.
#define UART_ID uart1 // Use hardware UART1 (separate from USB debug/stdio)
#define UART_TX_PIN 4 // GPIO4 → TX line for Pico → Pi4 (sending data out)
#define UART_RX_PIN 5 // GPIO5 → RX line for Pico ← Pi4 (receiving session keys)
#define BAUD_RATE 1000000 // 1 Mbps for high-throughput, low-latency key exchange
uart_init(UART_ID, BAUD_RATE);
// Map the chosen GPIO pins to the UART hardware.
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
// Allowing the pico to retrieve the session key over UART.-
Enable UART in
/boot/config.txt:enable_uart=1 -
Reboot, then check:
ls -l /dev/serial0
It should link to
/dev/ttyAMA0or/dev/ttyS0. -
Open the port at 1 Mbps:
# Configure 1M Baud rate and interface to type messages to send on pico stty -F /dev/serial0 1000000 # setting to 1M Baud matches pico's firmware -> #define BAUD_RATE 1000000 screen /dev/serial0 1000000 # setting pico CMD interface so you can type commands like CMD: new key, CMD: print key sender, etc. directly into the Pico.
-
Authenticated Encryption:
Utilizes AES-128-GCM for encryption and message authentication, protecting against both eavesdropping and tampering. -
Robust Key Persistence:
Implements a redundant A/B slot system in the Pico's flash memory to ensure the session key survives reboots and power loss. The system automatically falls back to a valid key if one slot is corrupted. -
Secure Key Provisioning:
On first boot/empty slot, listens on UART1 with preamble 0xAB 0xCD to receive the session key (e.g., from the Pi 4/auth client). Supportsnew keyandnew key -ffor controlled overwrite. -
Watchdog Timer:
The Pico (sender) is monitored by a hardware watchdog that automatically reboots the device if it becomes unresponsive, ensuring high availability. -
Secure Memory Handling:
Sensitive data like keys, nonces, and ciphertext are securely zeroed from memory after use withsecure_zero()to limit in-RAM exposure. -
Interactive Command Interface:
A rich set of commands allows for real-time management of the device, including key management, slot status checks, and diagnostics. -
Modular & Reusable Code:
The project is built with a modular architecture, separating hardware-specific logic (pico_handler), command processing (cmd_handler), and the main application logic for maximum reusability and maintainability. -
Cryptographically Secure PRNG:
mbedTLS CTR_DRBG seeded from the RP2040 ring-oscillator viapico_hardware_entropy_poll()→ high-quality randomness for salts and other needs.
- CMake ≥ 3.13
- ARM GCC Toolchain
- in deps/ for you (git submodule update --init --recursive --progress)
-
- (Pico Sender) Pico SDK
-
- (Pi4 Receiver) iotauth project for advanced key provisioning.
The code is organized into a modular structure:
src/: Core logic, including the command handler (cmd_handler.c) and Pico-specific/Pi-4 specific functions (pico_handler.c)/(pi_handler.c).-
note:pi_handler.cis under construction - waiting for TPM module
include/: Header files defining the public interface for each module.sender/src/: The main application firmware (lifi_flash.c) for the Pico transmitter.lib/: External libraries, includingmbedtls,pico-sdk, andpicotool.deps/: required repositories for session key includingsst-c-apiandiotauth.-
iotauthto be run if setting up server for live connection and live access to session key (on pi4)
CMakeLists.txt: The main build file that orchestrates the compilation of all modules and targets.
lifi-auth
├── CMakeLists.txt # Top-level CMake entry
├── README.md # This doc
├── run_build.sh # Build helper (pico/…)
├── set_build.sh # Env/preset helper
├── make_build.sh # Convenience wrapper
├── lifi_receiver.config # Default runtime config
│
├── 📁 config/
│ └── mbedtls_config.h # mbedTLS build config (Pico)
│
├── 📁 include/
│ ├── cmd_handler.h
│ ├── config_handler.h
│ ├── pico_handler.h
│ ├── protocol.h
│ └── sst_crypto_embedded.h
│
├── 📁 src/
│ ├── cmd_handler.c
│ ├── config_handler.c
│ ├── pico_handler.c
│ └── sst_crypto_embedded.c
│
├── 📁 sender/
│ ├── CMakeLists.txt
│ └── 📁 src/ # sender app sources
│
├── 📁 receiver/
│ ├── CMakeLists.txt
│ ├── 📁 include/ # receiver-local headers
│ ├── 📁 config/ # receiver config (creds, etc.)
│ ├── 📁 src/ # receiver app sources
│ └── update-credentials.sh # helper for credential files
│
├── 📁 deps/ # External project dependencies (git submodules)
│ ├── iotauth/ # (submodule) iotauth server/client bits (if used)
│ └── sst-c-api/ # (submodule) core SST C API (c_api.c, etc.)
│
├── 📁 lib/ # Third-party libraries (git submodules)
│ ├── mbedtls/ # (submodule) mbedTLS crypto (pinned)
│ ├── pico-sdk/ # (submodule) Raspberry Pi Pico SDK
│ └── picotool/ # (submodule) Pico CLI tool (building/flash)
│
├── 📁 img/
│ ├── build_artifacts_layout.PNG
│ └── physical_lifi.png
│
├── 📁 artifacts/ # (Generated) Build outputs kept for convenience
│ └── 📁 pico/
│ ├── latest.uf2 # last built firmware image
│ ├── latest.uf2.sha256 # checksum
│ └── *.json / *.uf2 # versioned build metadata & images
│
└── 📁 build/ # (Generated) CMake build trees
├── pico/ # Pico build dir (cmake/ninja/make files, libs, elf/uf2)
├── pi4/ # (optional) other target builds
└── _picotool/ # picotool helper build
deps/iotauth→https://github.com/iotauth/iotauthdeps/sst-c-api→https://github.com/iotauth/sst-c-api.gitlib/mbedtls→https://github.com/Mbed-TLS/mbedtls.git(recommended tag:mbedtls-3.5.1)lib/pico-sdk→https://github.com/raspberrypi/pico-sdk.gitlib/picotool→https://github.com/raspberrypi/picotool.git
Ubuntu / WSL
sudo apt update
sudo apt install -y build-essential cmake git pkg-config \
ninja-build \
# Pico toolchain (for `pico` builds)
gcc-arm-none-eabi libnewlib-arm-none-eabi \
# Needed to build picotool once
libusb-1.0-0-dev
# (pi4 builds will use your system gcc; if OpenSSL is missing:)
sudo apt install -y libssl-devAlso install for scripts:
sudo apt install -y libusb-1.0-0-dev pkg-config
Optional (recommended) speed-ups:
Install ninja-build to make builds faster on repeats
ssh
git clone git@github.com:asu-kim/lifi-auth.git
cd lifi-authBring in submodules recursively and show progress:
git submodule update --init --recursive --progressTo set the pico-sdk path run:
export PICO_SDK_PATH=$(pwd)/lib/pico-sdkuse helper script run_build.sh which automatically cleans and builds inside build/ folder.
- Uses
set_build.shandmake_build.sh - Creates artifacts/ for latest builds and keeps a history of most recent builds and auto-prunes old builds
- Note: first build takes longer, next builds will use .tooling to build faster
./run_build.sh pico # artifacts appear under artifacts/pico/ (latest.uf2 and versioned files)- Very first
./run_build.sh picowill take a while but after that it'll run faster (by reusing .tooling/) - ^ it will build and “install” a local picotool under
embedded/.tooling/picotool/and wire CMake to use it automatically. - Subsequent runs:
-
- reuse .tooling/ (no warning spam, no re-build).
-
- cleans the build so no need to
rm -rf build.
- cleans the build so no need to
-
- cleans the
artifacts/pico(or pi4) folder based on how manyKEEP_BUILDSare set
- cleans the
./run_build.sh pi4 # artifacts appear under artifacts/pi4/ (latest and versioned files)cd deps/iotauth/examples
./cleanAll.sh
./generateAll.shYou will be prompted to enter your password. Build the server:
cd ../auth/auth-server/
mvn clean installbuild the server:
java -jar target/auth-server-jar-with-dependencies.jar -p ../properties/exampleAuth101.propertiesEnter your password again. Now you should leave that terminal running, open another terminal
- Note: for latest build instructions check: https://github.com/iotauth/iotauth
in the second terminal navigate to root directory of the repo:
lifi-auth/We can now connect to the server with pi4 (assuming you built it above with./run_build.sh pi4):
- once inside
lifi-auth/runupdate_credentials.shfrom there
./receiver/update-credentials.shWith this lifi-auth/receiver/config/credentials should now be populated with the necessary Auth101EntityCert.pem & Net1.ClientKey.pem files
ls receiver/config/credentials/Note: never push these files to Github
- Now connect to the server using the latest build (will always be here and updated from running
./run_build pi4previously). - iotauth takes a config to connect: so lifi_receiver.config will be the second argument:
./artifacts/pi4/latest lifi_receiver.configThis will now connect to auth you will get "Retrieving session key from SST..." The pi4 should print the session key (for debug).
- Pico:
artifacts/pico/latest.uf2(+latest.uf2.sha256,latest.json) - Pi4:
artifacts/pi4/latest(executable) (+latest.sha256,latest.json)
We keep a short history of prior builds next to latest*, and prune to the last 3 build sets by default. Override per run:
KEEP_BUILDS=5 ./run_build.sh pi4( cd artifacts/pi4 && sha256sum -c latest.sha256 )
( cd artifacts/pico && sha256sum -c latest.uf2.sha256 )build/*is throwaway; safe to delete any time..tooling/picotoolis the reusable local install that suppresses SDK warnings.artifacts/<target (pi4 OR pico)>/latest*always points to the newest build; older builds are pruned.
-
Could NOT find OpenSSL (missing: OPENSSL_CRYPTO_LIBRARY)(pi4):sudo apt install -y libssl-dev -
No installed picotool with version …(pico): Our script builds a local picotool once; ensurelibusb-1.0-0-devis installed, then re-run./run_build pico. -
pico_sdk_init.cmake not foundor missing SDK:git submodule update --init --recursive --progress(we vendored the SDK underembedded/lib/pico-sdk). -
Assembler errors like
.syntax/.cpuduring Pico builds: You’re using the host gcc instead of ARM GCC. Installgcc-arm-none-eabi libnewlib-arm-none-eabi, or setPICO_TOOLCHAIN_PATHto your ARM toolchain root.
After ./run_build.sh pico, your firmware is here: artifacts/pico/latest.uf2.
./run_build.sh picoOption A (TESTED/WORKING) — Windows & WSL (usbipd Powershell)
- Unplug the Pico. Hold BOOTSEL while plugging it in (it mounts as a USB drive).
- Copy or drag the UF2:
- I did this using windows->WSL running Windows PowerShell as admin via usbipd in this order:
-
- Flashed latest.uf2 to pico
-
- Attached the busid to wsl
usbipd listIn the same row as USB Mass Storage Device, RP2 Boot -> Note the hardware under the BUSID: use that id for this command. In my case the BUSID is 2-2.
- Replace
2-2with your BUSID --found when running usbipd list:
usbipd attach --busid 2-2 --wslIn WSL now you should be able to see ls /dev/ttyACM0
ls /dev/ttyACM*If thats the case you can run:
picocom /dev/ttyACM0
Now you can provision pico with keys (up to 2) and use CMD: help for all commands.
These keys will be saved to flash storage even if it powers off it will reboot with valid key
- Note: logic to valid keys are not implemented yet (timers with rel_validity and abs_validity in sst-c-api -- SOON --)
Option B — Flash to Pico
UF2="artifacts/pico/latest.uf2"
DEST="$(ls -d /media/$USER/RPI-RP2 /run/media/$USER/$USER/RPI-RP2 2>/dev/null | head -n1)"
cp "$UF2" "$DEST"/Adjust
DESTif your system mounts the drive somewhere else (e.g.,/media/user/RPI-RP2). On Windows, just draglatest.uf2onto theRPI-RP2drive in Explorer.
This guide outlines the steps to provision the Pico with a session key from the host and establish secure communication.
- All hardware is connected, especially the Pico ↔ Pi 4 UART connection (see wiring details).
- The firmware and executables have been built successfully using
./run_build.sh. - The
lifi_receiver.configfile is present in theembedded/directory and all paths are correct.
Next, power on the Pico. If it's the first time running or its key slots are empty, it will automatically enter provisioning mode.
- Flash the firmware (
artifacts/pico/latest.uf2) to the Pico. If it's already flashed, simply reset it or unplug and plug it back in. - Connect to the Pico's USB serial port with a terminal emulator (e.g.,
screen,minicom, PuTTY) to monitor its status. - On boot, the Pico will fail to find a key and print:
No valid session key found. Waiting for one... - The Receiver, which was already waiting, will send the key. The Pico will receive it, save it to flash, and confirm with:
Received session key: <hex> Key saved to flash slot A.
The Pico is now provisioned and ready to communicate securely.
Once provisioned, the system is ready for use.
- In the Pico's USB serial terminal, type any message (e.g.,
hello world) and press Enter. - The Pico will encrypt the message and send it over the Li-Fi UART.
- The Receiver's terminal will display the decrypted message:
Decrypted: hello world
You can manage the session key from the Pico's serial console.
- Request a New Key from SST: To have the receiver fetch and send a new session key, simply type
new keyornew key -f(force) as a message in the Pico's terminal. The receiver will handle the request and provision the Pico with the new key.
Use the on-device command interface over the Pico’s USB serial:
-
Check key status:
CMD: slot status -
(Other commands; see the "Command Interface" section below for more.)
- Nothing prints on USB: Ensure your terminal is on the Pico’s USB CDC port and baud is 115200. Unplug/replug while holding BOOTSEL to reflash if needed.
- Key not accepted: Make sure you sent exactly 2 bytes of preamble (
AB CD) + 32 bytes of key (not ASCII text unless your firmware converts it). - Receiver can’t decrypt: Double-check both sides use the same 32-byte key and the receiver was restarted/reloaded after provisioning.
Interact with the Pico over the USB serial connection. All commands are prefixed with CMD:.
| Command | Description |
|---|---|
help |
Displays a list of all available commands. |
print key |
Prints the currently active session key. |
slot status |
Shows the validity of key slots A and B and which one is active. |
use slot A / use slot B |
Switches the active session key to the one in the specified slot. |
clear slot A / clear slot B |
Erases the key from the specified slot. |
new key |
Waits to receive a new key, but only if the current slot is empty. |
new key -f |
Forcibly overwrites the key in the current slot. |
print slot key A / B / * |
Prints the key stored in a specific slot (or all slots). |
entropy test |
Prints a sample of random data from the hardware RNG for verification. |
reboot |
Reboots the Pico. |
- The system is designed for high reliability, automatically recovering from reboots and provisioning itself on first run.
- Future work could include:
- A GUI-based host application for managing multiple devices.
- Support for secure different file types over Li-Fi.
- Integration with a hardware Trusted Platform Module (TPM) on the host for even more secure key storage.
