Procedures / LoRa / LoRaWAN / RFSAM-LORA-CR-01
RFSAM-LORA-CR-01REVIEWED · high

Assess LoRaWAN join and session-key management

Determine whether a LoRaWAN deployment's join procedure and key management are sound — that the root AppKey is a per-device random value (not default, shared or guessable), that DevNonce is fresh per join, and that session keys are not static and reused — by analysing captured join and data frames against the network's stated LoRaWAN version and activation mode.

LoRaWAN CR · Crypto HIGH
LoRaWAN's entire confidentiality and integrity model hangs off the root key: the AppKey (1.0.x) or NwkKey+AppKey (1.1) from which every session key is derived. There is no feasible brute force of a strong random AES-128 key — the exposure is in how keys and nonces are managed. This control checks the join and session-key chain for default/shared keys, DevNonce reuse, and static ABP session keys, the recurring real-world failures that collapse the crypto.

Mechanism

LoRaWAN’s confidentiality and integrity derive entirely from a root key, so this layer is about key and nonce management, not cipher strength. In 1.0.x the device and back end share an AppKey; in 1.1 the root is split into NwkKey and AppKey. From the root, the two session keys are derived: the AppSKey encrypts the FRMPayload (AES-128), and the NwkSKey keys the 4-byte MIC (AES-128-CMAC) [loraalliance2020l2][hessel2022survey]. There is no feasible brute force of a strong random AES-128 root key; the real-world exposure is that the key is often not strong, not unique, or not secret, and that the join nonce and session keys are mismanaged [hessel2022survey].

Default / shared / hardcoded AppKey. IOActive’s audit of real LoRaWAN deployments found root keys that were hardcoded in firmware, shared fleet-wide, set to vendor defaults, or otherwise easy to source from public repositories or weak enough to guess [ioactive2020lorawan]. Because every session key descends from that root, recovering it once decrypts all traffic for the device’s lifetime and lets an attacker forge frames with valid MICs — a complete crypto break [ioactive2020lorawan][hessel2022survey]. This is exactly what a candidate-key test demonstrates: given a guessed/leaked AppKey and a captured join, derive the session keys and confirm by recomputing the captured MIC [loracrack][laf]. It is not a brute force of a strong key [loracrack].

DevNonce reuse / join replay (1.0.x). The OTAA JoinRequest carries JoinEUI/AppEUI, DevEUI and a DevNonce in the clear, protected only by a MIC — the key is never transmitted, but the DevNonce sequence is fully observable passively [loraalliance2020l2]. In LoRaWAN 1.0.0–1.0.3 the DevNonce is a random value, and the network must remember and reject previously-seen values; a repeat lets a captured JoinRequest be replayed [na2017replay][loraalliance2020joinsync]. Reuse is not hypothetical: the random nonce is subject to the birthday paradox over a multi-year lifetime, and constrained devices reuse nonces after reboots, counter resets, or poor RNG seeding [hessel2022survey]. LoRaWAN 1.0.4 and 1.1 redefine DevNonce as a monotonic counter that SHALL never be reused for a given JoinEUI, closing this window — at the cost of the join-synchronisation problems the LoRa Alliance documents for mixed fleets [loraalliance2020joinsync][loraalliance2020l2].

Static ABP session keys / frame-counter reset. ABP (Activation By Personalisation) burns static NwkSKey/AppSKey/DevAddr into the device with no per-join rotation. If the frame counter resets on power-cycle, the AES-CTR-style payload encryption repeats keystream (a two-time pad recoverable by XOR) and stale frames become replayable — both reachable without ever recovering the key [hessel2022survey][tarlogic2020lorawan]. OTAA exists precisely to avoid this by deriving fresh session keys at each join.

The IOActive findings (default/hardcoded/weak AppKeys) are reported across audited deployments and firmware, not a single named CVE; treat them as a representative pattern and check the specific device/stack before asserting a given deployment is affected. Loracrack and LAF are PoC/audit tooling (Loracrack last pushed 2022-06, LAF 2023-05); re-confirm they build against your capture format and LoRaWAN version before relying on them.

Procedure

Authorised testing only. Capturing and analysing another network’s traffic, and testing any candidate key against it, requires explicit written permission and must be lawful in your jurisdiction. Do not transmit at this layer; this control is offline analysis over a capture you are authorised to hold. Test AppKey candidates only against your own / in-scope devices.

  1. Capture the join and following data frames for the target device, per RFSAM-RES-07 and RFSAM-LORA-LL-01 — de-chirp the CSS waveform and export a LoRaTap PCAP that contains at least one OTAA JoinRequest/JoinAccept pair and subsequent uplinks for the same DevAddr. Confirm in Wireshark:

    lorawan.mtype == 0     # Join Request: JoinEUI/AppEUI, DevEUI, DevNonce (cleartext)
    lorawan.mtype == 1     # Join Accept (encrypted)

    Expected: the join identifiers readable in the clear; the FRMPayload of data frames shown as AES-128 ciphertext.

  2. Establish the LoRaWAN version and activation mode (from the IG step / device profile). The checks below differ for 1.0.x vs 1.0.4/1.1, and for OTAA vs ABP. Record which applies.

  3. Audit DevNonce freshness (OTAA, 1.0.x). Group JoinRequests by DevEUI and extract the DevNonce sequence. Export the join frames and tabulate:

    # tshark over the LoRaTap PCAP: one row per JoinRequest
    tshark -r joins.pcap -Y "lorawan.mtype == 0" \
      -T fields -e lorawan.dev_eui -e lorawan.dev_nonce | sort | uniq -c | sort -rn

    Expected: a count per (DevEUI, DevNonce). Any count > 1 is a reused DevNonce → 1.0.x join-replay exposure [loraalliance2020joinsync]. A small or clustered nonce space (few distinct values, or values biased low) indicates weak RNG even without an exact repeat [hessel2022survey].

  4. Test for a weak / shared / default AppKey (OTAA). Assemble candidate keys: vendor defaults, all-zero / sample keys from datasheets or SDKs, keys extracted from firmware, and any fleet-wide value you suspect is reused. Feed the captured join + a data frame and each candidate to the derivation-and-MIC check:

    # Loracrack: derive session keys from a candidate AppKey and validate via the MIC
    python loracrack.py --pcap capture.pcap --appkey <CANDIDATE_APPKEY>

    Expected: for the correct key, the tool derives NwkSKey/AppSKey and the recomputed MIC matches the captured frame’s MIC — confirming the key and that the session is now decryptable [loracrack]. For a wrong key the MIC mismatches. Equivalently, use LAF to recompute and validate the MIC against a key candidate [laf]. A match is a finding: the root key is not secret.

    A MIC match means you have recovered the session keys for that capture. Decrypt only traffic you are authorised to analyse; do not act on recovered keys beyond the agreed scope.

  5. Audit ABP session-key hygiene (ABP). For ABP devices, confirm whether the frame counter (FCnt) ever resets — capture across a device reboot/power-cycle in your test rig and check for a counter that returns to a low value while the DevAddr and (static) keys are unchanged:

    lorawan.dev_addr == <TARGET_DEVADDR>     # then read the FCnt column over time

    Expected: a healthy device’s FCnt increases monotonically. A reset (a high value followed by a low one) under static keys is the keystream-reuse / replay finding [hessel2022survey][tarlogic2020lorawan].

  6. Record the result per device: version + activation mode; DevNonce behaviour (fresh / reused / weak-RNG); AppKey result (strong-random vs a candidate that produced a MIC match); ABP FCnt reset (y/n). Each positive is a key-management failure that collapses some or all of the crypto chain.

Field case

Reported measurement (carried over from the paired link-layer control). The same authorised US915 capture used to profile this network at the link layer (RFSAM-LORA-LL-01) — 51,304 LoRaWAN frames, of which 45,815 (89.3%) were JoinRequests — is the input for the CR-layer key analysis: because the capture was so heavily dominated by JoinRequests, each device’s DevNonce sequence was directly observable in the clear, which is the prerequisite for spotting reuse [loraalliance2020joinsync]. The application payloads stayed AES-128 protected throughout — confidentiality held against the capture; the question this control asks is whether it holds against the key management. This frame/JoinRequest count is the author’s reported field figure, reproduced verbatim from the paired RFSAM-LORA-LL-01 control; it has not been independently re-measured here.

Illustrative walkthrough — substitute the values you capture. To reproduce against your own authorised test network: stand up a couple of OTAA devices and one ABP device on a ChirpStack/RAK gateway, deliberately provision one OTAA device with a default/sample AppKey (so the key-candidate test in step 4 succeeds), and one 1.0.x device that re-joins on reboot (so step 3 shows a DevNonce repeat). Then run steps 3–5 and tabulate:

# per-device key-management findings from an authorised test capture
DevEUI / DevAddr        version  activation  DevNonce        AppKey test       ABP FCnt reset
[FILL: id]              1.0.3    OTAA        [FILL: repeated?] MIC match (2b11ff0d) n/a
[FILL: id]              1.1      OTAA        monotonic        [FILL: no match]  n/a
[FILL: id]              1.0.3    ABP         n/a              n/a               [FILL: y/n]

Reproducible public worked example (no live capture) for the step-4 cell. Rather than depend on a live measurement, the candidate-key/MIC test for the weak OTAA row above is grounded in a documented public test vector shipped with the lora-packet library [lorapacket]. For the data uplink frame 40F17DBE4900020001954378762B11FF0D, feeding the candidate NwkSKey 44024241ed4ce9a68c6a8bc055233fd3 recomputes the MIC as 2b11ff0d, which equals the frame’s trailing MIC bytes — verifyMIC returns true (MIC check=OK) — and feeding the AppSKey ec925802ae430ca77fd3dd73cb2cc588 decrypts the FRMPayload to the ASCII plaintext test. The same library’s vectors also confirm the join side: for the JoinRequest 0039363463336913AA05693574323831330489C65B1304, the candidate AppKey 98929b92c49edba9676d646d3b612456 recomputes the JoinRequest MIC as c65b1304, again matching the frame. These values are self-consistent and reproducible entirely offline (__tests__/mic_test.ts, __tests__/decrypt_test.ts, and the README CLI decoder walkthrough), so the “candidate key → MIC match → decryptable payload” chain of step 4 can be demonstrated end-to-end without any live RF capture [lorapacket].

The expected shape: the deliberately weak OTAA device yields a MIC match under the default key (its session keys are recoverable and traffic decryptable, exactly as the lora-packet vector above shows), the 1.0.x re-joining device shows a repeated DevNonce (join-replay exposure), and the ABP device’s counter resets across reboot (keystream-reuse/replay exposure) — while a correctly provisioned 1.1 OTAA device passes all three. The remaining [FILL: …] cells above are placeholders for the values you capture in your own authorised analysis — they are not measured findings.

Remediation

Developer (device / stack). Provision a unique, cryptographically random AppKey (1.0.x) or NwkKey+AppKey (1.1) per device; never ship a default, sample, all-zero, fleet-wide or firmware-hardcoded root key, since recovering it once breaks every session for the device’s life [ioactive2020lorawan][hessel2022survey]. Generate DevNonce correctly: on 1.0.4/1.1 use the mandated monotonic counter that is never reused for a JoinEUI and persists across reboots; on legacy 1.0.x with a random DevNonce, ensure adequate RNG entropy and persistence so nonces do not repeat after a reset [loraalliance2020l2][loraalliance2020joinsync]. Prefer OTAA over ABP so session keys are derived fresh at each join rather than burned in static.

Integrator (network / join server). Enforce strict server-side DevNonce tracking: remember and reject previously-seen DevNonce values (1.0.x) or enforce monotonicity (1.0.4/1.1), so a replayed JoinRequest is denied [na2017replay][loraalliance2020joinsync]. For ABP deployments, enforce frame-counter validation that survives device reboots — reject a counter that resets rather than accepting it — to neutralise the keystream-reuse and replay paths [hessel2022survey][tarlogic2020lorawan]. Where feasible, migrate to 1.1’s split keys and Join Server, and ensure no 1.0.x elements remain in a network claimed to be 1.1, since backward compatibility reopens the older weaknesses [tarlogic2020lorawan].

Operator (deployment). Treat the root key as the crown jewel: rotate keys where the lifecycle allows, audit that every deployed device carries a unique key (not a shared batch value), and protect the network/join server credentials so the keys cannot be read from an exposed back end [ioactive2020lorawan]. Periodically run this passive join/session-key audit against your own network as a monitoring control — a recurring DevNonce, a successful default-key derivation, or a resetting ABP counter is a provisioning failure to fix, not merely an attacker’s opportunity.

KNOWN ATTACKS

Default / shared / hardcoded AppKey recovery

LoRaWAN's confidentiality and integrity derive entirely from the root key; IOActive documented hardcoded, default and weak AppKeys in real deployments and firmware, and the LAF toolkit tests AppKey candidates against captured frames via the MIC.

Impact:Recovering or guessing the root AppKey lets an attacker derive both session keys (NwkSKey, AppSKey) for every session, decrypt all application traffic, and forge frames with valid MICs — a full break of the device's crypto.
Preconditions:The device uses a default, shared, vendor-wide or otherwise guessable AppKey, or one extractable from firmware / public source; plus a captured OTAA join (and following data frames). The strong-random-key case is not brute-forceable.
ioactive2020lorawan, laf
DevNonce reuse / random-nonce join replay (1.0.x)

In 1.0.x DevNonce is a random value the network must remember-and-reject; reuse (collision, reboot, weak RNG) allows JoinRequest replay. 1.0.4/1.1 close this by making DevNonce a monotonic counter that is never reused.

Impact:A repeated DevNonce on a 1.0.x device enables replay of captured JoinRequests, forcing rejoin/desynchronisation or denial of service, and indicates a join-security model that does not reject stale nonces.
Preconditions:A LoRaWAN 1.0.0–1.0.3 device whose DevNonce is a random value (birthday collision over device lifetime) or reused after reboot/poor RNG; the join is cleartext, so the DevNonce sequence is observable passively.
hessel2022survey, loraalliance2020joinsync, na2017replay
Static ABP session keys / frame-counter reset

ABP trades key rotation for simplicity: static session keys plus a resettable frame counter give keystream reuse and replay — the session-key-management failure OTAA is designed to avoid.

Impact:ABP devices carry static NwkSKey/AppSKey that never rotate; a frame-counter reset on reboot repeats the keystream (two-time pad on the AES-CTR-style payload) and re-opens replay, both reachable without ever recovering the key.
Preconditions:An ABP-activated device (static session keys + DevAddr burned in) whose frame counter resets on power-cycle, captured passively.
hessel2022survey, tarlogic2020lorawan

REFERENCES

  1. [hessel2022survey]
    LoRaWAN Security: An Evolvable Survey on Vulnerabilities, Attacks and their Systematic Mitigation — F. Hessel, L. Almon, M. Hollick, ACM Transactions on Sensor Networks, Vol. 18, Issue 4, 2022(paper)
  2. [ioactive2020lorawan]
    LoRaWAN Networks Susceptible to Hacking: Common Cyber Security Problems, How to Detect and Prevent Them — C. Cerrudo, E. Martinez Fayo, M. Sequeira (IOActive), IOActive, 2020(blog)
  3. [na2017replay]
    Scenario and countermeasure for replay attack using join request messages in LoRaWAN — S. H. Na, D. Hwang, W. Shin, K.-H. Kim, IEEE ICOIN 2017, pp. 718–720, 2017(paper)
  4. [tarlogic2020lorawan]
    LoRaWAN 1.0, vulnerabilities and backward compatibility in version 1.1 — G. J. Carracedo Carballal, Tarlogic Security, 2020(blog)
  5. [loraalliance2020joinsync]
    LoRaWAN 1.0.x Join-Synch Issues — Remedies (v1.0.0) — LoRa Alliance Technical Committee, LoRa Alliance, 2020(standard)
  6. [loraalliance2020l2]
    LoRaWAN L2 1.0.4 Specification (TS001-1.0.4) — LoRa Alliance, LoRa Alliance, 2020(standard)
  7. [loracrack]
    Loracrack — LoRaWAN session cracker (PoC for weak/shared Application Keys) — S. Mellema (Applied Risk), GitHub, 2022(tool)
  8. [laf]
    LAF — LoRaWAN Auditing Framework — IOActive, GitHub(tool)
  9. [lorapacket]
    lora-packet — LoRaWAN packet decoder/encoder (test vectors + README walkthrough) — A. Kirby, GitHub(tool)

RELATED RESOURCES

RFSAM-RES-07Capture and decode LoRa / LoRaWAN
← PREVIOUS
Profile LoRaWAN frames and harvest cleartext join identifiers