Embedded HandbookApps
All sections › X. Design, networks and IoT
STM32F103, STM32F407 · level advanced · 11 min read
Open in the interactive handbook

94. LoRaWAN: network, keys, classes

The LoRa radio (section 93) delivers bytes from a transmitter to a receiver. LoRaWAN is a network on top of this radio. It solves four problems: - who receives the frames; - how the server recognizes a device; - why an outsider can neither read nor forge the data; - when a device can get a reply.

Verified by experiment: a frame was built in portable C and sent to a real ChirpStack network server running in containers.

Terms used in the documentation

Term Meaning
gateway, packet forwarder the gateway, the gateway's packet-forwarding program
network server, application server, join server the servers that run the network, deliver the data, handle joins
DevEUI, JoinEUI, DevAddr the device's factory ID, the join server's ID, the network address
OTAA, ABP joining over the air, activation with built-in keys
AppKey, NwkSKey, AppSKey the root key, the network and application session keys
MIC the frame signature
FCnt, FPort the frame counter, the port
MHDR, FHDR, FCtrl, FOpts frame headers, the control byte, MAC commands in the header
class A, B, C device classes by when they listen
RX1, RX2 the first and second receive windows
data rate (DR), ADR the data rate, its automatic selection
confirmed / unconfirmed uplink an uplink frame with and without acknowledgement

94.1 How the network is built

LoRaWAN is a "star of stars":

Link What it does
device transmits over the air and is not connected to anything; its frame can be heard by several gateways
gateway receives everything on all its channels and all SF at once (section 93.2) and forwards it to the server over IP without parsing or decrypting it, including other people's frames
network server removes duplicates from several gateways, checks the signature and the counter, picks a data rate for the device, chooses a gateway for the reply
application server decrypts the data and hands it to the application over MQTT or HTTP (sections 87 and 88)

A gateway knows nothing about devices, so gateways can be placed anywhere and added as needed. All the network logic is on the server.


94.2 The frame

Field Bytes What it holds
MHDR 1 frame type: 0x40 is an unconfirmed data uplink
DevAddr 4 the device's network address
FCtrl 1 flags: ADR, acknowledgement, FOpts length
FCnt 2 the 16 low bits of the frame counter
FOpts 0–15 network MAC commands, if any
FPort 1 0 means MAC commands in the payload, 1–223 application data
FRMPayload as needed encrypted data
MIC 4 signature

Without FOpts there are 13 overhead bytes (section 89). The experiment frame with 7 bytes of data t=21.50 is 20 bytes:

40 | EF CD AB 01 | 00 | 01 00 | 01 | 36 B6 EF 94 14 F5 C6 | 7A D3 2F 6B
MHDR  DevAddr     FCtrl  FCnt  FPort   зашифрованные данные      MIC

The address 01ABCDEF and the counter are written least significant byte first, not most significant first like Modbus data and the numbers in TCP/IP headers. Getting the byte order wrong is the first reason a server silently does not see a device.


94.3 Keys and activation

A LoRaWAN 1.0 session has two keys, and they are separated for a reason:

A device gets its keys in one of two ways:

Production devices use OTAA; ABP is left for the bench and experiments. In LoRaWAN 1.1 the network key is split into three, and in a 1.0 session, as on the bench, all three are equal to NwkSKey. Keys stored in firmware as a plain array can be read from the image (sections 43 and 77.9).


94.4 Encryption and signature

The data is encrypted in counter mode. For every 16 bytes of data, AES encrypts a block A_i containing the address, the frame counter and the block number, and the data is XORed with the result. The signature is the first 4 bytes of AES-CMAC over block B0 and the whole frame:

/* Блоки A_i и B0: метка 0x01 или 0x49, четыре нуля, направление (0 — вверх),
 * адрес и счётчик — младшим байтом вперёд, ноль, номер блока или длина */
static void lw_block(uint8_t b[16], uint8_t tag, uint32_t dev_addr, uint32_t fcnt, uint8_t last)
{
    memset(b, 0, 16);
    b[0] = tag;
    for (int i = 0; i < 4; i++) {
        b[6 + i] = (uint8_t)(dev_addr >> (8 * i));
        b[10 + i] = (uint8_t)(fcnt >> (8 * i));
    }
    b[15] = last;
}

/* Неподтверждаемый кадр вверх: MHDR 0x40, DevAddr, FCtrl, FCnt (2 младших
 * байта), FPort, зашифрованные данные (до 64 байт) и MIC. Вернёт длину или 0. */
size_t lorawan_uplink(uint8_t *out, uint32_t dev_addr, uint32_t fcnt, uint8_t fport,
                      const uint8_t *data, size_t n, const uint8_t nwk_skey[16], const uint8_t app_skey[16])
{
    uint8_t a[16], s[16], b[16 + 9 + 64], mac[16];
    size_t k = 0;
    if (n > 64)
        return 0;
    out[k++] = 0x40;                            /* неподтверждаемые данные вверх */
    for (int i = 0; i < 4; i++)
        out[k++] = (uint8_t)(dev_addr >> (8 * i));
    out[k++] = 0x00;                            /* FCtrl: без ADR и без FOpts */
    out[k++] = (uint8_t)fcnt;
    out[k++] = (uint8_t)(fcnt >> 8);
    out[k++] = fport;
    for (size_t i = 0; i < n; i++) {            /* шифр — xor с AES(AppSKey, A_i) */
        if (i % 16 == 0) {
            lw_block(a, 0x01, dev_addr, fcnt, (uint8_t)(i / 16 + 1));
            aes128_encrypt(app_skey, a, s);
        }
        out[k++] = data[i] ^ s[i % 16];
    }
    lw_block(b, 0x49, dev_addr, fcnt, (uint8_t)k);  /* подпись — CMAC(NwkSKey, B0 | кадр) */
    memcpy(b + 16, out, k);
    aes128_cmac(nwk_skey, b, 16 + k, mac);
    memcpy(out + k, mac, 4);
    return k + 4;
}

aes128_encrypt and aes128_cmac in the test are compact implementations for the experiment only. They were checked against published test vectors and against a Python library. In a real device, use a library or the hardware block (section 43). On a Cortex-M3, AES takes 184 bytes of stack, and building the frame takes 208.

⚠️ The counter is part of the cipher. The frame counter goes into block A_i, so the same data with different counters is encrypted differently. Repeat a counter with the same keys, for example after a reboot of an ABP device, and two frames end up encrypted with the same keystream. By XORing them, an outsider gets the XOR of the two plaintexts without any key. With the same keys, a counter must never repeat.


94.5 Experiment: a real network server

The bench. Running in containers: - the ChirpStack network server and application server with PostgreSQL and Redis; - the Mosquitto broker; - the gateway bridge.

The device is registered through the server's programming interface (gRPC): ABP, LoRaWAN 1.0.3, EU868.

The "gateway" is a C program. It wraps the frame the way the packet forwarder of a real gateway sends it: a UDP packet with JSON that gives the frequency, SF, power, SNR and the frame itself in base64. The bridge acknowledged all four forwards:

What was sent What the server did
counter 1, t=21.50 accepted, decrypted and published to MQTT: counter 1, DR5, that is SF7 at 125 kHz
the same frame again, intercepted and replayed dropped: "frame counter did not increase", an event in the device log
counter 2, one bit of the signature changed dropped: "no session with address 01abcdef gave a valid signature"
counter 2, a correct frame accepted t=21.75: the corrupted frame did not advance the counter

The bytes of both frames were rebuilt independently in Python and matched.

The third row shows something you cannot see in the frame description. The device address is not unique: the top bits of the address are the network ID, fewer are left for the devices themselves, and the server may give the same address to several devices. The server finds all sessions with that address and checks the signature of each one. The signature also identifies the device. A frame with a wrong signature is foreign to the server, and the application never even hears about it.


94.6 Classes A, B, C

Class When the device listens When a command arrives Power
A only two windows after its own transmission at the device's next transmission battery, years
B also in windows scheduled by gateway beacons in the scheduled window battery
C all the time except during its own transmissions almost immediately mains

All devices must support class A. Receive windows in the bench's region: RX1 1 s after the end of transmission, on the frame's frequency; RX2 after 2 s on 869.525 MHz at DR0 (SF12). These are the region defaults: a network can set its own, often SF9 for RX2. With OTAA the device gets them in the join reply; with ABP they are written into the device by hand. A command for a class A device waits in the server queue until the device itself makes contact.

The window is opened by a timer started at "transmission finished", which is DIO0 from section 93, not by a delay loop (section 13). If the window is opened late, the server's replies never arrive, and the device looks "deaf".

The window accuracy in the LoRaWAN specification is ±20 µs, so the window timer is clocked from a crystal: LSE or HSE. The STM32 LSI is off by tens of percent (section 12), HSI by about 1 % (section 6), and 1 % of a second is 10 ms, more than the whole SF7 preamble: 8 symbols of 1.024 ms each are 8.2 ms. Stacks open the window a little early and keep it open longer to cover clock drift, and pay for it with receive current.

Class A: a 61.7 ms transmission, windows RX1 at 1 s and RX2 at 2 s after its end

A reply is expensive. The gateway is half-duplex: while it transmits a reply to one device, it hears nobody. Confirmed frames need a reply to every frame. In a network of thousands of devices they are enabled only for rare important events, not for telemetry.


94.7 ADR: data rate from SNR

The network server remembers the SNR of the device's last frames and computes the margin: the best SNR minus the threshold for the current data rate (section 93.2) minus the installation margin, usually 10 dB. Every 3 dB of margin is one data rate step up. When there is nowhere faster to go, the server lowers the power.

DR 0 1 2 3 4 5
SF at 125 kHz 12 11 10 9 8 7
SNR threshold, dB −20 −17.5 −15 −12.5 −10 −7.5

Example: a device at SF12 with a best SNR of +3 dB has a margin of 3 − (−20) − 10 = 13 dB. That is 4 steps, and the server moves the device to DR4, SF8. The packet becomes several times shorter, the battery lasts longer, and the air is freed for others. ADR is good for stationary devices. For moving ones the SNR jumps around, so ADR is turned off for them and the data rate is set by the device itself.

ADR: a 13 dB margin is four 3 dB steps, the device moves from SF12 to SF8


94.8 A device on STM32: what to store and what to send


94.9 Pitfalls

The server silently does not see the device. The address or a key was entered in the wrong byte order. The server log says that no session gave a valid signature.

After a reboot the ABP device went silent. The counter started from zero, and the server expects more than the previous one: "frame counter did not increase".

The counter was reset together with the server database, but the keys were kept. The keystream repeats, and the data can be recovered without the key.

Commands do not reach the device. The RX1 window opens later than 1 s: the delay loop drifted because of interrupts, or the window timer runs from the LSI.

After a reboot the OTAA device cannot join. DevNonce restarted from the beginning, and the server remembers the used numbers.

The network is choking. All devices send confirmed frames, and the gateway transmits more than it listens.

The battery runs flat in months instead of years. The device sends JSON at SF12.

The keys leaked from the firmware. They were in the image as a plain array.


94.10 Summary

Next: section 94а "NFC tag ST25DV": how the phone and the firmware share one memory: addresses on the bus, the password, the mailbox and the interrupt on write.

Open in the interactive handbook