Also known as: LoRaWAN MIC, message integrity code, AES-CMAC MIC
The LoRaWAN MIC (Message Integrity Code) is the 4-byte tag that authenticates every LoRaWAN data frame: a receiver that shares the network session key can confirm the frame is genuine and unaltered, and reject anything that fails.1 It is an AES-CMAC (RFC 4493) computed over a synthetic B0 header block prepended to the frame, using AES as its underlying block cipher — the same primitive that, in a different mode, encrypts the payload.2
How it works
AES-CMAC first derives two subkeys. The cipher encrypts an all-zero block to get L, and each
subkey is a one-bit left shift of the previous value, conditionally XORed with the constant
Rb = 0x87 when the shifted-out high bit was set — the spec’s generator for a 128-bit block:
// shiftLeftXorRb: one-bit left shift, XOR Rb (0x87) when the high bit was set.
func shiftLeftXorRb(in [16]byte) [16]byte {
var out [16]byte
var carry byte
for i := 15; i >= 0; i-- {
out[i] = in[i]<<1 | carry
carry = in[i] >> 7
}
if in[0]&0x80 != 0 {
out[15] ^= 0x87
}
return out
}
K1 comes from shifting L, K2 from shifting K1. The message is then split into 16-byte
blocks and CBC-chained under the key. The final block gets special handling: if the message is a
whole number of blocks it is XORed with K1; otherwise it is padded with a 0x80 byte (then
zeros) and XORed with K2. Encrypting that last block yields a 16-byte value whose first four
bytes are the MIC. GopherTrunk’s VerifyMIC recomputes this under the network session key and
compares against the received tag in constant time.
The B0 block
CMAC is not run over the frame alone — LoRaWAN prepends a 16-byte B0 block that binds the MIC to the frame’s context, so a valid tag cannot be replayed on a different address, direction or frame counter:
| Byte(s) | Contents |
|---|---|
| 0 | 0x49 (block-type marker) |
| 5 | direction — 0 uplink, 1 downlink |
| 6–9 | device address (little-endian) |
| 10–13 | 32-bit frame counter (little-endian) |
| 15 | length of the MAC payload |
The MIC is computed over B0 ‖ frame, so tampering with the address, the counter, or the
direction changes the input and invalidates the tag.
Payload encryption
Authentication and confidentiality are separate mechanisms. The frame payload is encrypted with
an AES-CTR-style scheme (LoRaWAN 1.0 §4.3.3): AES encrypts a per-block A counter block
(marker 0x01, direction, device address, frame counter, and a block index in its last byte)
to produce a keystream, which is XORed with the payload. Because XOR is symmetric, the same
routine encrypts and decrypts. The key differs by port — the application session key for
FPort > 0, the network session key for FPort == 0 MAC-command payloads — while the MIC is
always keyed under the network session key. Key material itself is held in the keystore and
never logged.
Sources
-
RFC 4493 — The AES-CMAC Algorithm — IETF, the subkey generation (Rb = 0x87), padding rules and CMAC construction. ↩
-
LoRa — Wikipedia, on LoRaWAN’s security, session keys and frame format. ↩