Field Guide · algorithm

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

B0 block block 1 last ⊕ K1/K2 CBC chain under AES_K MIC = first 4 bytes L = AES_K(0¹²⁸) · K1 = shift(L) ⊕ 0x87 (if MSB set) · K2 = shift(K1) ⊕ 0x87 last block complete → ⊕ K1 · incomplete → append 0x80 pad, ⊕ K2
The MIC is an AES-CMAC: derive subkeys K1/K2 from the key via a left-shift and conditional XOR with 0x87, CBC-chain the B0 block and message under AES, XOR the final block with the right subkey, encrypt, and take the first four output bytes.

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

  1. RFC 4493 — The AES-CMAC Algorithm — IETF, the subkey generation (Rb = 0x87), padding rules and CMAC construction. 

  2. LoRa — Wikipedia, on LoRaWAN’s security, session keys and frame format. 

See also