Bluetooth Mesh Security

Bluetooth Mesh Security
Sequence Numbers · IV Index · Nonces — explained from scratch
24-bit
Sequence Number
32-bit
IV Index
13 bytes
Nonce Size
4 types
Nonce Variants

Why does Mesh need special security?

In a Bluetooth Mesh network, every message hops from node to node — like a chain of people passing a note. An attacker sitting in that chain could re-send an old note to trick the network (replay attack). Bluetooth Mesh blocks this with three interlocking mechanisms: a Sequence Number that ticks upward on every message, an IV Index that resets the counter before it overflows, and a Nonce that combines both values so that every encryption operation is completely unique.

This post walks through each mechanism step by step, shows the exact byte layout used on air, and demonstrates how BlueZ / the Linux kernel exposes these values.

Keywords covered

Bluetooth Mesh Replay Attack Sequence Number IV Index Nonce Network PDU SEQ field Network Nonce Application Nonce Device Nonce Proxy Nonce IV Update BlueZ meshctl CCM Encryption

Chapter 1 — Sequence Number (SEQ)
24-bit counter Replay protection Per-element counter Strictly increasing

Every time an element inside a mesh node sends a message, it stamps that message with a Sequence Number (SEQ) — a simple 24-bit integer stored in the SEQ field of the Network PDU. The rule is strict: each new outgoing message must use a number that is higher than the last one.

Why strictly increasing?

When a receiving node gets a message, it checks the SEQ against the last SEQ it saw from that source address. If the new SEQ is equal to or lower than the stored one, the message is dropped immediately — it is either a duplicate or a replayed packet.

Scenario Incoming SEQ Stored SEQ Decision
Normal fresh message 1005 1004 ✓ Accept
Replayed old message 1003 1004 ✗ Drop
Duplicate in-flight 1004 1004 ✗ Drop

How large is the counter space?

A 24-bit number can reach a maximum of 16,777,216. If an element sends one message every 5 seconds (a fairly busy device), it takes about 2.6 years to exhaust the counter. Before that happens, the node must run the IV Update procedure to reset the counter safely.

SEQ counter lifespan at different message rates
1 msg / 5 sec → ~2.6 years
65% of expected use-case life
1 msg / 1 sec → ~194 days
still manageable
10 msg / sec → ~19 days ⚠ trigger IV Update early!
update soon

BlueZ: reading the sequence number

BlueZ stores the current sequence number per element in /var/lib/bluetooth/<adapter>/mesh/<node-token>/config. You can also watch it in meshctl debug output:

# Start meshctl with debug logging
sudo meshctl --dbus-debug

# Inside meshctl, send a message and watch the log
[meshctl]# send 0100 0a

# BlueZ debug output will show:
# mesh_net.c: Network SEQ sent: 0x00002A
# mesh_net.c: Nonce built with SEQ=0x00002A IV=0x00000001

The source code in BlueZ that manages the sequence number lives in mesh/net.c:

/* mesh/net.c (simplified) */

/* get next sequence number for an element */
static uint32_t get_next_seq(struct mesh_net *net, uint16_t src)
{
    uint32_t seq = net->seq_num;
    net->seq_num++;   /* strictly incrementing */

    /* flush to storage so it survives a reboot */
    storage_save_sequence(net, src, net->seq_num);

    return seq;
}

Chapter 2 — IV Index
32-bit shared value IV Update procedure Secure Network Beacon 5 trillion year lifespan

The IV Index is like a “chapter number” for the whole mesh network. While the SEQ is a “page number” within a chapter, the IV Index says which chapter we are in. All nodes in the same mesh network share the exact same IV Index value at any given time.

Starting value and increment rule

The IV Index starts at 0x00000000. It is incremented by 1 every time any node’s sequence number gets close to its maximum (0xFFFFFF). This is called the IV Update procedure. Because IV Index is 32 bits wide, the entire mesh network can run for approximately 5 trillion years before the IV Index wraps — effectively forever.

Concept Book analogy Bit width Scope Who controls it?
SEQ Page number 24 bit Per element Each element independently
IV Index Chapter number 32 bit Whole network Any node via IV Update
SEQ + IV Index Chapter + Page 56 bit combined Unique per message Together form the Nonce input

How is the IV Index shared?

Every node broadcasts Secure Network Beacons periodically. These beacons carry the current IV Index value (and a 1-bit flag showing whether an IV Update is in progress). Any node that has been powered off for a while can listen for these beacons and catch up immediately.

Node A
SEQ near max
Starts IV Update

Secure Beacon
IV=0x01

Node B
Receives beacon
Updates IV Index

Relays beacon
IV=0x01

Node C
Receives relay
Updates IV Index
IV Index propagates across the whole network via Secure Network Beacon relay

Subnet rule for IV Index propagation

A node connected to the primary subnet that receives an IV update on the primary subnet must propagate it to all other subnets it belongs to. However, if that same node receives an IV update on a secondary subnet, it must ignore it — only primary-subnet updates are authoritative.

BlueZ: reading the IV Index

# BlueZ stores IV Index in the node config JSON
cat /var/lib/bluetooth/<BD_ADDR>/mesh/<token>/config | python3 -m json.tool | grep -i iv

# Expected output:
#   "IVindex": 1,
#   "IVupdate": 0
/* mesh/net.c — IV Index access (simplified) */

uint32_t mesh_net_get_iv_index(struct mesh_net *net)
{
    return net->iv_index;
}

/* called when a Secure Network Beacon arrives with a higher IV */
static void process_iv_update(struct mesh_net *net, uint32_t new_iv)
{
    if (new_iv > net->iv_index) {
        net->iv_index = new_iv;
        net->seq_num  = 0;  /* SEQ resets after IV increment */
        storage_save_iv(net);
    }
}

Chapter 3 — The Nonce: What it is and why it exists
13-byte unique value CCM encryption input Never reuse 4 nonce types

The word nonce literally means “number used once”. In Bluetooth Mesh, the nonce is a 13-byte value fed into the AES-CCM encryption engine alongside the actual message payload and a key. If the nonce is ever the same for two different messages encrypted with the same key, the encryption is broken — an attacker can recover the plaintext. The entire SEQ + IV Index design exists purely to guarantee the nonce is always unique.

AES-CCM Inputs → Encrypted PDU
Encryption Key
Network Key or App Key
(128-bit)
Nonce
SEQ + IV Index
+ address fields
(13 bytes, always unique)
Plaintext
Actual message data
↓ AES-CCM ↓
Encrypted PDU + MIC (Message Integrity Check)
Safe to transmit over air

The four nonce types at a glance

Type byte Name Used with Includes TTL? Includes DST? Purpose
0x00 Network nonce Network Key ✓ Yes ✗ No Encrypt/auth at network layer
0x01 Application nonce App Key ✗ No ✓ Yes Encrypt/auth upper transport (app data)
0x02 Device nonce Device Key ✗ No ✓ Yes Provisioning / config messages
0x03 Proxy nonce Network Key ✗ No ✗ No GATT Proxy connections

Notice the key design decisions baked into the table above: the TTL (Time To Live) is inside the Network nonce but not inside the Application or Device nonce. This is intentional — when a relay decrements the TTL, the network-layer nonce changes (so the relay re-authenticates the TTL), but the application-layer nonce stays the same (the app doesn’t care how many hops the message took). Similarly, the DST (destination address) is encrypted at the network layer — it goes into the application/device nonce but not the network nonce.

Chapter 4 — Network Nonce: byte-by-byte
CTL + TTL byte SEQ 3 bytes SRC 2 bytes IV Index 4 bytes

The Network nonce is exactly 13 bytes wide. Let’s walk through each field:

Octet 0 Octet 1 Octet 2 Octet 3 Octet 4 Octet 5 Octet 6 Octet 7 Octet 8 Octet 9 Octet 10 Octet 11 Octet 12
0x00
Type
CTL|TTL
1B
SEQ
byte 2
SEQ
byte 1
SEQ
byte 0
SRC
hi
SRC
lo
0x00
Pad
0x00
Pad
IVI
byte 3
IVI
byte 2
IVI
byte 1
IVI
byte 0
1 byte 1 byte 3 bytes (SEQ) 2 bytes (SRC) 2 bytes (Pad) 4 bytes (IV Index)

What is the CTL|TTL byte?

Octet 1 packs two fields into one byte: the most-significant bit is CTL (0 = access message, 1 = control message) and the remaining 7 bits hold the TTL (Time To Live, 0–127). This combined byte is included in the nonce so that relays cannot silently change the TTL without breaking authentication.

Bit 7 (MSB) Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 (LSB)
CTL TTL [6:0]
0=access
1=control
0–127 hops remaining

BlueZ: building the Network nonce

/* mesh/crypto.c — network nonce construction (simplified) */

void mesh_crypto_network_nonce(uint8_t  ctl,
                               uint8_t  ttl,
                               uint32_t seq,
                               uint16_t src,
                               uint32_t iv_index,
                               uint8_t  nonce[13])
{
    nonce[0]  = 0x00;                     /* Nonce Type: Network */
    nonce[1]  = (ctl << 7) | (ttl & 0x7F);  /* CTL | TTL */
    nonce[2]  = (seq >> 16) & 0xFF;      /* SEQ MSB  */
    nonce[3]  = (seq >>  8) & 0xFF;
    nonce[4]  =  seq        & 0xFF;      /* SEQ LSB  */
    nonce[5]  = (src >>  8) & 0xFF;      /* SRC hi   */
    nonce[6]  =  src        & 0xFF;      /* SRC lo   */
    nonce[7]  = 0x00;                     /* Pad      */
    nonce[8]  = 0x00;                     /* Pad      */
    nonce[9]  = (iv_index >> 24) & 0xFF; /* IV MSB   */
    nonce[10] = (iv_index >> 16) & 0xFF;
    nonce[11] = (iv_index >>  8) & 0xFF;
    nonce[12] =  iv_index        & 0xFF; /* IV LSB   */
}

/* Example call: element 0x0101, seq=0x2A, iv=1, TTL=5, access msg */
uint8_t nonce[13];
mesh_crypto_network_nonce(0, 5, 0x2A, 0x0101, 1, nonce);
/* nonce = { 00, 05, 00, 00, 2A, 01, 01, 00, 00, 00, 00, 00, 01 } */

Chapter 5 — Application Nonce & Device Nonce
ASZMIC bit DST field SeqAuth Upper transport layer

The Application nonce and Device nonce share an almost identical structure. The key difference from the Network nonce is that they include the destination address (DST) and drop the TTL. They are used one layer higher — at the upper transport layer — with the Application Key or Device Key respectively.

Oct 0 Oct 1 Oct 2 Oct 3 Oct 4 Oct 5 Oct 6 Oct 7 Oct 8 Oct 9 Oct 10 Oct 11 Oct 12
0x01
App
ASZMIC
|Pad
SEQ
b2
SEQ
b1
SEQ
b0
SRC
hi
SRC
lo
DST
hi
DST
lo
IVI
b3
IVI
b2
IVI
b1
IVI
b0
Device nonce is identical but uses 0x02 as Octet 0 (Nonce Type)

What is ASZMIC?

ASZMIC stands for Access Segmented Message Integrity Check. It occupies bit 7 of octet 1. For a segmented access message (one that had to be split into multiple PDUs), ASZMIC mirrors the SZMIC flag which controls whether the MIC is 4 bytes or 8 bytes long. For all unsegmented messages it is always 0.

Bit 7 (MSB) Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 (LSB)
ASZMIC Pad = 0b0000000
1=8-byte MIC
0=4-byte MIC
always zeros

BlueZ: building the Application nonce

/* mesh/crypto.c — application nonce construction (simplified) */

void mesh_crypto_application_nonce(uint8_t  aszmic,
                                   uint32_t seq,
                                   uint16_t src,
                                   uint16_t dst,
                                   uint32_t iv_index,
                                   uint8_t  nonce[13])
{
    nonce[0]  = 0x01;                         /* Nonce Type: Application */
    nonce[1]  = (aszmic & 0x01) << 7;         /* ASZMIC | Pad            */
    nonce[2]  = (seq >> 16) & 0xFF;
    nonce[3]  = (seq >>  8) & 0xFF;
    nonce[4]  =  seq        & 0xFF;
    nonce[5]  = (src >>  8) & 0xFF;
    nonce[6]  =  src        & 0xFF;
    nonce[7]  = (dst >>  8) & 0xFF;           /* DST hi — not in net nonce */
    nonce[8]  =  dst        & 0xFF;           /* DST lo                    */
    nonce[9]  = (iv_index >> 24) & 0xFF;
    nonce[10] = (iv_index >> 16) & 0xFF;
    nonce[11] = (iv_index >>  8) & 0xFF;
    nonce[12] =  iv_index        & 0xFF;
}

/* Device nonce is exactly the same but nonce[0] = 0x02 */

Chapter 6 — Putting it all together: end-to-end message flow
Two layers of encryption App Key → Net Key Relay TTL decrement Double-decrypt at destination

Bluetooth Mesh uses two independent layers of encryption on every access message. Understanding which nonce is used at each layer is the key to understanding the whole security model.

Sender (Source Element)
Step 1 — Upper Transport
Encrypt payload with App Key using Application Nonce (0x01)
AES-CCM(AppKey, AppNonce, Payload) → EncPayload + MIC4
Step 2 — Network Layer
Encrypt (EncPayload + DST) with Net Key using Network Nonce (0x00)
AES-CCM(NetKey, NetNonce, EncPayload+DST) → NetworkPDU
Step 3 — Air
NetworkPDU transmitted over BLE advertisements
Relay node — only touches Network layer
Decrypts with NetKey, decrements TTL (network nonce changes), re-encrypts, forwards. Cannot read the payload — no App Key.
Receiver (Destination Element)
Step A — Network decrypt
Decrypt with Net Key + Network Nonce → recovers EncPayload + DST
Step B — Upper transport decrypt
Decrypt with App Key + Application Nonce → recovers Plaintext
Step C — Deliver
Model receives plaintext application message

Quick summary: which fields change at each hop?

Event SEQ TTL Network Nonce App/Device Nonce
Original send Set once Set once Built Built
Relay hop Unchanged Decremented Rebuilt (TTL changed) Unchanged
IV Update fires Resets to 0 N/A IV part changes IV part changes

Chapter 7 — Interview & Quick-Reference Card
Common questions Key numbers Cheat sheet
Question Answer
What does SEQ protect against? Replay attacks — old messages are rejected if SEQ ≤ last seen
How many messages can one element send before needing IV Update? 16,777,216 (2²⁴)
How is IV Index shared across the network? Via Secure Network Beacons broadcast by every node
What is a Nonce and how big is it? A unique 13-byte value fed into AES-CCM; never reused
Why does the Network Nonce include TTL but not Application Nonce? So relay nodes can authenticate the TTL without seeing the payload
Why does the Application Nonce include DST but not Network Nonce? DST is encrypted at network layer; only the intended receiver can read it with App Key
What is ASZMIC? 1-bit flag in app/device nonce; selects 4-byte (0) or 8-byte (1) MIC for segmented messages
Which BlueZ file handles crypto operations? mesh/crypto.c

Explore more on EmbeddedPathashala

Dive deeper into Bluetooth Mesh, BLE stack internals, and Linux kernel programming.

Visit EmbeddedPathashala Mesh Series

Leave a Reply

Your email address will not be published. Required fields are marked *