Skip to content

docs: per-device encryption key guide for production - #95

Closed
quechau wants to merge 6 commits into
masterfrom
docs/per-device-key-guide
Closed

docs: per-device encryption key guide for production#95
quechau wants to merge 6 commits into
masterfrom
docs/per-device-key-guide

Conversation

@quechau

@quechau quechau commented Aug 26, 2026

Copy link
Copy Markdown

What this adds

Per-Key-Loraraw.md - a self-contained procedure for giving each LoRa device its own AES key instead of the single key currently shared by the whole fleet.

Written for production and commissioning engineers, covering all three device families in one place: Optical Power Meter, Droplet V2 and FGA Gen2.

Documentation only. No code changes, and nothing in the module needs to change for the procedure to work - the per-device key mechanism has been in place since 26597487 (Feb 2025). What was missing was a written procedure.

Why

Every device that has never been re-keyed ships with the same compile-time AES key. Extracting it from one device, or reading it from the firmware source, exposes traffic for every device in the field. The capability to fix that already exists; this document makes it usable on the production line.

What is in it

  • How AES protects a frame - byte-level layout, what is encrypted and what is not, and why the address must stay in the clear. That ordering is precisely what allows per-device keys without touching the radio protocol.

  • Why every LoRaRAW address carries C0 in its second byte - it is a protocol version marker, not identity, so only three bytes distinguish devices. Includes the exception engineers will actually meet: legacy MicroEdge units do not follow the convention, and because they encrypt the address itself they cannot be given a per-device key at all.

  • A worked example following one real frame end to end. Part A shows the device assembling it - four readings bit-packed into 23 bytes, the three-byte inner header, padding to a whole AES block, encryption and CMAC. Part B shows Rubix CE taking the same frame apart - splitting the fields, reading address and signal strength with no key at all, decrypting, and decoding back to the four readings it started from. Cross-checked against the module's own republished frame on MQTT.

  • Bench setup per device family. The STM32 boards need SWD and UART simultaneously, so they are wired through an ICQMCU module breaking out an ST-LINK V3 and a UART adapter. The ESP32 needs only a USB cable.

  • Entering AT mode on STM32, which is where most people get stuck. The firmware samples the supply rail once at startup and only accepts AT commands above 2900 mV; on battery it reads about 1930 mV, so commands are silently ignored. Two ways in are documented, including the production path using a 3.3 V supply and stock firmware.

  • The key-format trap - a plain AES key without dashes for the STM32 boards and the Rubix CE field, a dashed AES key for the ESP32 console. Each rejects the other's form. The FGA error message is quoted verbatim so it can be searched for, with conversions in both directions.

  • The two failure modes behind most silent devices - AT+SAVE is mandatory on STM32 because AT+AES= only writes RAM, and the AT parser drops characters if a key is sent as a burst rather than at roughly 20-25 ms per character.

  • Verification, troubleshooting and a per-device checklist, including a power-cycle read-back so a write that did not persist is caught at the bench instead of hours later in the field.

  • Known limitations stated plainly - keys sit unprotected in device flash, re-keying breaks the link until both ends are updated, a factory reset silently reverts a device to the shared key, legacy MicroEdge devices cannot be keyed at all, and nothing currently prevents the same key being reused across devices.

Verification

Every command, both format conversions and the full end-to-end walkthrough were run against a live board before being written down. The log lines and decryption output in the document are copied from that run rather than reconstructed.

All thirteen diagrams render cleanly under mermaid-cli.

A self-contained procedure for giving each LoRa device its own AES key
instead of the single key currently shared by the whole fleet. Written for
production and commissioning engineers, and covering all three device
families in one place: Optical Power Meter, Droplet V2 and FGA Gen2.

Everything described works with the module as shipped - no firmware or
module change is needed for the feature itself.

Contents:

- Why it matters: every un-keyed device carries the same compile-time key,
  so extracting it from one device exposes the whole fleet.

- How AES protects a frame: byte-level layout drawn as packet diagrams, what
  is encrypted and what is not, and why the address must stay in the clear -
  that is precisely what lets each device have a different key without
  touching the radio protocol.

- Why every LoRaRAW address carries C0 in its second byte: it is a protocol
  version marker, not identity, so only three bytes distinguish devices.
  Includes the exception engineers will actually meet - legacy MicroEdge
  units do not follow the convention, and because they encrypt the address
  itself they cannot be given a per-device key at all.

- A worked example following one genuine frame end to end. Part A shows the
  device assembling it: four readings bit-packed into 23 bytes, the three-byte
  inner header, padding to a whole AES block, encryption and CMAC. Part B
  shows Rubix CE taking the same frame apart: splitting the fields, reading
  address and signal strength with no key at all, decrypting, and decoding
  back to the four readings it started from. Cross-checked against the
  module's own republished frame on MQTT, and closed by decrypting with the
  wrong key to show what failure looks like - which doubles as the technique
  for working out which key a silent device is actually using.

- A procedure overview diagram before the per-device sections, showing where
  the STM32 and ESP32 paths diverge and where the device is briefly
  unreachable.

- Bench setup per device family. The STM32 boards need SWD and UART at the
  same time - flash or verify over SWD, talk AT over UART - so they are wired
  through an ICQMCU module that breaks out both an ST-LINK V3 and a UART
  adapter. The ESP32 needs only a USB cable.

- Entering AT mode on STM32, which is where most people get stuck. The
  firmware samples the supply rail once at startup and only accepts AT
  commands above 2900 mV; on battery it reads about 1930 mV, so commands are
  silently ignored. Two ways in are given: power the board from a 3.3 V
  supply (the production path, stock firmware), or flash the build from the
  optical-power-meter branch feat/debug-print-aes-key (commit aeb725a), which
  adds a 3-second UART entry window that works on battery. The second is
  marked as bench tooling, not for shipped units.

- The key-format trap, stated in the terms an operator sees: a plain AES key
  (without dashes) for the STM32 boards and the Rubix CE field, a dashed AES
  key for the ESP32 console. Each rejects the other's form. The FGA error
  message this produces is quoted verbatim so it can be searched for, with
  one-line conversions in both directions.

- The two failure modes behind most silent devices: AT+SAVE is mandatory on
  STM32 because AT+AES= only writes RAM, and the AT parser drops characters
  if a key is sent as a burst rather than at roughly 20-25ms per character.

- Verification, troubleshooting that starts by reading the key back from the
  device rather than trusting the write, and a per-device checklist including
  a power-cycle read-back so a write that did not persist is caught at the
  bench instead of hours later in the field.

- Known limitations stated plainly: keys sit unprotected in device flash,
  re-keying breaks the link until both ends are updated, a factory reset
  silently reverts a device to the shared key, legacy MicroEdge devices
  cannot be keyed at all, and nothing prevents the same key being reused
  across devices.

Every command, both format conversions and the full end-to-end walkthrough
were run against a live board before being written down; the log lines and
decryption output in the document are copied from that run. All thirteen
diagrams render cleanly under mermaid-cli.
@quechau
quechau force-pushed the docs/per-device-key-guide branch from fee81c6 to 561a39c Compare August 26, 2026 08:28
Move Per-Key-Loraraw.md into docs/md/ and add docs/generate.py, which turns
a Mermaid-using Markdown file into a Google-Docs-ready .docx.

pandoc cannot render Mermaid on its own, so the script pre-renders each
```mermaid block to a PNG with mermaid-cli (white background, 2x scale for
legibility inside a document), rewrites the block as an image reference, then
runs pandoc to produce the .docx with a table of contents.

Layout:
  docs/md/<name>.md         source
  docs/images/<name>-N.png  rendered diagrams (generated)
  docs/<name>.docx          output (generated)

The .docx and the rendered PNGs are build artifacts, so they are ignored
locally via .git/info/exclude rather than committed - the Markdown source is
the thing under version control.

Verified: Per-Key-Loraraw.docx builds with all 13 diagrams embedded as images
and the full text intact.
Output location moves from docs/ to docs/docs/, keeping generated documents
in their own subfolder separate from the source and the script.

Layout is now:
  docs/generate.py          the script
  docs/md/<name>.md         source
  docs/images/<name>-N.png  rendered diagrams (generated)
  docs/docs/<name>.docx     output (generated)

Image references stay relative to docs/, and pandoc resolves them via
--resource-path regardless of where the .docx is written, so the images are
still embedded correctly.
Per-Key-Loraraw.md explains how per-device keys work - the background and the
protocol. This adds Per-Key-Loraraw-Guideline.md, the practical companion: a
tester reads it top to bottom, does each step, and ticks each box, with no
theory in the way.

It uses real photos and dashboard screenshots (committed under docs/images/):

- Bench wiring for each STM32 board (Optical Power Meter, Droplet V2), showing
  the ST-Link + UART 2-in-1 module and the 3.3 V wire that is required to
  enter AT mode - the single most common reason AT commands appear dead.
- FGA Gen2 wiring - a single USB-C, no AT mode, but the key must be dashed.
- The Rubix CE flow end to end: where to find the Loraraw device manager, how
  to read the Device Key column (a value means per-device, empty means
  shared), and how to enter the key when adding or editing a device, including
  which Model to pick per device family.

Also documents running Rubix CE itself: the binary comes from the
rubix-ce-builds releases page.

The generator picks up the second document automatically. It now resolves
both image styles - rendered diagrams (images/<name>-N.png) and the committed
screenshots the source references as ../images/... - by listing both
directories on pandoc's resource-path. Rendered diagrams stay build
artifacts (ignored); the screenshots are source and are committed.

Verified: both documents' 15 diagrams render clean, all 9 referenced
screenshots resolve, and Per-Key-Loraraw-Guideline.docx builds with 11 images
embedded (2 diagrams + 9 screenshots).
Every step that produces output now shows an actual capture from a bench run,
so a tester knows exactly what a correct result looks like rather than a
paraphrase.

- Section 4 (Verify) now carries a full real PASS log for a single Optical
  Power Meter frame - the enter/decode/decrypt-ok/decoded-values lines and the
  MQTT payload with the four UVP readings - and a real FAIL log for the same
  device with a mismatched key. Also notes that the "failed to find point ...
  UVP-1" lines are point creation, not an encryption error.

- AT+SAVE (2A.4) shows the real flash-write lines and CRC around the save.

- The SWD flash read-back (2A.6) shows a real two-record dump and points out
  which page is the live one.

- The ESP32 dashes trap (2B.3) shows the verbatim "Data length ... 11 bytes ...
  NOT within the allowed range" error the console prints for plain hex.

All captures are from real hardware. The 15 diagrams across both documents
still render clean; the guideline .docx rebuilds with 11 images embedded.
Include the built Word documents in the repo so members can download and open
them - or import to Google Docs - without needing pandoc and mermaid-cli
installed locally.

  docs/docs/Per-Key-Loraraw.docx            background / how it works
  docs/docs/Per-Key-Loraraw-Guideline.docx  hands-on test procedure (QA/QC)

Both are regenerated from docs/md/ by docs/generate.py and carry the latest
content, including the real captured logs added to the guideline. The rendered
Mermaid PNGs under docs/images/ remain build artifacts and stay ignored - the
diagrams are already embedded inside the .docx files.
@quechau quechau closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant