Bluetooth Mesh Security – Nonces, Keys & Encryption — How Mesh Keeps Your Network Safe

 

Bluetooth Mesh Security
Nonces, Keys & Encryption — How Mesh Keeps Your Network Safe
3
Nonce Types
3
Key Types
AES-CCM
Encryption

Why Does Mesh Need Security?

In a Bluetooth Mesh network, messages hop from node to node across a large area — think smart lighting in a building, or industrial sensors on a factory floor. Any node in range can hear those messages. Without proper security, an attacker could replay old commands, inject fake ones, or read private data.

The Mesh Profile specification solves this using a layered security model: nonces prevent replay attacks, and keys provide encryption and authentication at multiple layers of the stack.

Key Concepts in This Post

Network Nonce Application Nonce Device Nonce Proxy Nonce AppKey NetKey DevKey NID AID IV Index SEQ k1 / k2 / k3 / k4 ECDHSecret ProvisioningSalt BlueZ

📌 Section 1 — What Is a Nonce?

A nonce (Number used ONCE) is a unique value that is combined with a key when encrypting a message. Even if the same message is sent twice with the same key, a different nonce produces a completely different ciphertext. This stops replay attacks — where an attacker records a valid message and sends it again later.

In Bluetooth Mesh, a nonce is always 13 bytes (octets) long. Each nonce type serves a different layer or purpose in the stack.

How a Nonce Prevents Replay Attacks
📨
Message
Same payload
+
🔑
Key
AppKey / NetKey
+
🎲
Nonce
SEQ + IV Index
🔒
Unique Ciphertext
Changes every packet

📌 Section 2 — The Proxy Nonce (Nonce Type 0x03)

The proxy nonce is used to secure proxy configuration messages — these are control messages exchanged between a GATT Proxy node and a connected smartphone or provisioner. It uses an encryption key derived from the NetKey.

Proxy Nonce Structure (13 octets total)
0x03
Nonce Type
1 octet
0x00
Pad
1 octet
SEQ
Sequence No.
3 octets
SRC
Source Addr
2 octets
0x0000
Pad
2 octets
IV Index
IV Index
4 octets

SEQ (Sequence Number) increments with every message sent from that node. IV Index (Initialization Vector Index) is a global counter that the whole mesh network shares and increments over time. Together, SEQ + SRC + IV Index make every nonce globally unique across the entire mesh.

BlueZ — Proxy Nonce Construction (mesh/crypto.c style)C
/* Proxy nonce: used for proxy config message encryption */
#include <string.h>

void mesh_create_proxy_nonce(uint8_t *nonce,
                             uint32_t seq,
                             uint16_t src,
                             uint32_t iv_index)
{
    /* Octet 0: Nonce Type = 0x03 (Proxy) */
    nonce[0] = 0x03;

    /* Octet 1: Pad = 0x00 */
    nonce[1] = 0x00;

    /* Octets 2–4: SEQ (big-endian, 3 bytes) */
    nonce[2] = (seq >> 16) & 0xFF;
    nonce[3] = (seq >>  8) & 0xFF;
    nonce[4] = (seq      ) & 0xFF;

    /* Octets 5–6: SRC (big-endian, 2 bytes) */
    nonce[5] = (src >> 8) & 0xFF;
    nonce[6] = (src     ) & 0xFF;

    /* Octets 7–8: Pad = 0x0000 */
    nonce[7] = 0x00;
    nonce[8] = 0x00;

    /* Octets 9–12: IV Index (big-endian, 4 bytes) */
    nonce[9]  = (iv_index >> 24) & 0xFF;
    nonce[10] = (iv_index >> 16) & 0xFF;
    nonce[11] = (iv_index >>  8) & 0xFF;
    nonce[12] = (iv_index      ) & 0xFF;
}

📌 Section 3 — Three Types of Keys in Bluetooth Mesh

Bluetooth Mesh uses a two-layer security model. Messages are encrypted twice — once at the upper transport layer (using an AppKey or DevKey) and once at the network layer (using a NetKey). An attacker who breaks one layer still cannot read the full message.

Key Type Comparison
Key Full Name Used At Known By Unique Per
AppKey Application Key Upper Transport Layer All nodes in same app group Application (e.g. lighting)
NetKey Network Key Network Layer All nodes in same subnet Subnet
DevKey Device Key Access Layer (config messages) Node + Configuration Client only Each individual node

AppKey Binding: One AppKey → One NetKey

NetKey 0
0x0000
NetKey 1
0x0013

AppKey 0
0x0000 → bound to NetKey 0
AppKey 1
0x0022 → bound to NetKey 1
AppKey 2
0x0027 → bound to NetKey 1

Model 0
e.g. Light Lightness
Model 1
e.g. On/Off
Model 2
e.g. Sensor

Rule: Each AppKey is bound to exactly one NetKey. But one NetKey can have multiple AppKeys bound to it. A DevKey is implicitly bound to all NetKeys.

📌 Section 4 — Device Key (DevKey) and Its Derivation

The DevKey is what makes each node individually addressable for configuration. When a provisioner (like a phone app) wants to configure a specific node — say, set its publish address — it uses that node’s unique DevKey to encrypt the config message. No other node can read or tamper with it.

DevKey is derived during the provisioning process, using two inputs:

DevKey Derivation Flow
ECDHSecret
Shared secret from
ECDH key exchange
+
ProvisioningSalt
Derived from provisioning
confirmation values
k1( ) function
with label “prdk”
DevKey
Unique to this node
Formula: DevKey = k1(ECDHSecret, ProvisioningSalt, "prdk")
The string "prdk" is a domain separator — it makes this k1 call unique compared to other k1 uses.

BlueZ — Where DevKey Is Stored (mesh/node.c style)C
/* In BlueZ, each provisioned node stores its DevKey.
   mesh-config.json has a "deviceKey" field per node. */

struct mesh_node {
    uint16_t  unicast;        /* Node's unicast address */
    uint8_t   dev_key[16];    /* 128-bit DevKey */
    uint8_t   num_ele;        /* Number of elements */
    /* ... */
};

/* Config messages (e.g., Config AppKey Add) are
   encrypted with the node's DevKey, not AppKey.
   This is why provisioner must track each DevKey. */

/* Deriving DevKey using k1 (AES-CMAC based): */
void derive_dev_key(const uint8_t *ecdh_secret,
                   const uint8_t *prov_salt,
                   uint8_t *dev_key)
{
    const uint8_t label[] = "prdk";

    /* k1(N, SALT, P):
       T  = AES-CMAC_SALT(N)
       k1 = AES-CMAC_T(P) */
    mesh_crypto_k1(ecdh_secret, 32, prov_salt, label, 4, dev_key);
}

📌 Section 5 — Application Key (AppKey) and the AID

An AppKey is randomly generated (using a cryptographically secure RNG) and manually distributed to all nodes that need to share an application. For example, all light switches and light bulbs in one room share the same AppKey.

But how does a receiving node quickly figure out which AppKey was used to encrypt an incoming message — without trying all of them? That’s where the AID (Application Key Identifier) comes in.

AID Derivation
AppKey
128-bit random key
k4( ) function
AES-CMAC based
AID
6-bit identifier
Formula: AID = k4(AppKey)
The AID travels in plaintext inside the Access PDU header. A receiver checks the AID to pick the right AppKey for decryption — it’s like a hint, not a secret.
BlueZ — AID Lookup During Decryption (mesh/crypto.c style)C
/* Receiver side: find matching AppKey by AID */
bool find_app_key_by_aid(struct mesh_node *node,
                        uint8_t aid,
                        uint8_t **app_key_out)
{
    for (int i = 0; i < node->num_app_keys; i++) {
        uint8_t candidate_aid;

        /* k4: AES-CMAC of AppKey, take lower 6 bits */
        mesh_crypto_k4(node->app_keys[i], &candidate_aid);

        if ((candidate_aid & 0x3F) == (aid & 0x3F)) {
            *app_key_out = node->app_keys[i];
            return true;
        }
    }
    return false;  /* No matching key found */
}

📌 Section 6 — Network Key (NetKey) Hierarchy

The NetKey is the root of all network-level security material. From a single NetKey, the mesh stack derives four pieces of material using cryptographic key derivation functions (k1, k2, k3):

NetKey → Derived Keys

NetKey
128-bit random key

IdentityKey
k1(NetKey, …)
Used for node identity beacons (proxy)
BeaconKey
k1(NetKey, …)
Authenticates secure network beacons
NID + EncKey + PrivKey
k2(NetKey, 0x00)
Secures every Network PDU
Network ID
k3(NetKey)
64-bit public identifier of subnet

The NID (7-bit Network ID) travels in plaintext inside every Network PDU header. A receiving node uses it to quickly pick which NetKey material to try. Since there are 2121 possible keys per NID, it’s only a hint — the node still must verify the message MIC after decryption.

Network PDU Security — What Each Derived Key Does
EncryptionKey
AES-CCM encrypts the Transport PDU payload inside the Network PDU. Confidentiality.
PrivacyKey
Obfuscates the Network PDU header (SRC, SEQ, etc.) to prevent traffic analysis. Privacy.
NID (7-bit)
Identifies which security material to use. Sent in plaintext. Receiver uses this as a lookup hint.
BlueZ — NetKey Material Derivation (mesh/crypto.c style)C
/* Derive NID, EncryptionKey, PrivacyKey from NetKey.
   k2(NetKey, 0x00) for master security credentials. */

struct net_key_material {
    uint8_t  nid;               /* 7 bits */
    uint8_t  enc_key[16];       /* 128-bit encryption key */
    uint8_t  priv_key[16];      /* 128-bit privacy key */
};

void derive_net_key_material(const uint8_t *net_key,
                             struct net_key_material *out)
{
    uint8_t  p[1] = { 0x00 };     /* Master credentials */
    uint8_t  result[33];          /* NID(1) + EncKey(16) + PrivKey(16) */

    /* k2 uses CMAC-based HKDF internally */
    mesh_crypto_k2(net_key, p, sizeof(p), result);

    out->nid = result[0] & 0x7F;          /* Lower 7 bits */
    memcpy(out->enc_key,  result + 1,  16);
    memcpy(out->priv_key, result + 17, 16);
}

📌 Section 7 — The Full Picture: Two-Layer Encryption

When a mesh node sends an application message (e.g., “Turn light ON”), it goes through two rounds of encryption before hitting the air:

Message Encryption Journey

1
Access Layer: Plaintext payload encrypted with AppKey + Application Nonce → produces Upper Transport PDU with MIC

2
Transport Layer: May segment large messages into smaller blocks (SAR), adds transport MIC

3
Network Layer: Entire Transport PDU encrypted with EncryptionKey + Network Nonce, header obfuscated with PrivacyKey → sent over the air
Key Insight: A relay node that forwards your message can decrypt the Network PDU (it has the NetKey) to read the routing header — but it cannot read the actual application payload, because that is still protected by the AppKey, which relay nodes don’t need to have.

📋 Quick Reference — Key Derivation Functions
Function Input Output Used For
k1 N, SALT, P 128-bit key DevKey, IdentityKey, BeaconKey
k2 NetKey, P NID + EncKey + PrivKey Network PDU security material
k3 NetKey 64-bit Network ID Public subnet identifier
k4 AppKey 6-bit AID Application key identifier

Continue the Bluetooth Mesh Series

Next: Provisioning Protocol — how a new node gets its DevKey, NetKey, and joins the mesh securely.

Back to EmbeddedPathashala Next Post →

Leave a Reply

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