Many BLE field logs create a false impression: the device is already connected, so security should also already be done. That is not true. Permission errors, pairing failures, or pairing again after reconnecting usually mean that “connection exists” and “trust relationship established” have been mixed into one thing.
This article does not expand on Legacy Pairing, BR/EDR security mechanisms, or vendor SDK wrapper differences. It focuses only on the most common pairing, bonding, encryption, and privacy path.
If you isolate the security chain, the main path looks like this:
Establish LE connection -> exchange pairing capabilities and public keys -> verify identity using the selected method -> derive `MacKey/LTK` from `DHKey` -> start link-layer encryption -> optionally distribute `IRK/CSRK`
If you are doing embedded bring-up, keep these four questions in mind first before going into the details:
- Can this pairing actually protect against
MITM (Man-in-the-Middle)? - Why does the result still end up as
Just Workseven though security was requested? - Why does the device pair again after reconnect?
- What exactly does
RPA (Resolvable Private Address)solve, and what cost does it add to debugging and bonding?
You will keep seeing four key names. First remember what each one is for:
LTK (Long Term Key): the long-term key used for link-layer encryptionIRK (Identity Resolving Key): used to resolve resolvable private addressesCSRK (Connection Signature Resolving Key): used for signed dataDHKey (Diffie-Hellman Key): the shared secret computed by ECDH
Overview
What layer security is filling in
From an embedded development perspective, LESC is not simply “one more handshake.” It mainly fills in these four things:
- Whether both sides want to pair, and which side starts it
- How each side confirms that the peer is really the peer and not a man in the middle
- Which key the link layer will later use for encryption, and how that key is derived
- How identity and privacy are recovered in later connections after bonding
You can think of the security establishment process as this diagram:
The core takeaway is: LESC is not just “more SMP packets.” It separates identity verification from link-encryption key derivation. The first decides whether MITM is blocked; the second decides whether the link is actually encrypted afterward.
BLE security architecture
GATT/GAP] --> SM[Security Manager] SM --> SMP[SMP protocol
L2CAP channel 0x0006] SMP --> HCI[HCI layer
Key-generation commands] HCI --> Controller[Controller
Link Layer encryption] SM --> Keys[Key management] Keys --> LTK[LTK
Link encryption] Keys --> IRK[IRK
Address resolution] Keys --> CSRK[CSRK
Data signing]
Protocol layers:
- SMP (Security Manager Protocol): L2CAP channel 0x0006, responsible for pairing and key distribution
- HCI (Host Controller Interface): provides ECDH P-256 key generation and link-layer encryption commands
- Link Layer: uses LTK for AES-CCM encryption
- GAP (Generic Access Profile): defines pairing modes and security levels
Design principles
ECDH P-256 key exchange:
- Protocol mechanism: elliptic-curve Diffie-Hellman on the NIST P-256 curve
- Key generation:
DHKey = ECDH(SKa, PKb)orECDH(SKb, PKa) - Temporary nature: a new ECDH key pair is generated for each pairing, which reduces the value of passive sniffing
- HCI commands:
LE_Read_Local_P256_Public_Key,LE_Generate_DHKey
Nonce-based anti-replay:
- Protocol requirement: both sides generate 128-bit random numbers (
Na/Nb) - Commitment scheme: the responder first sends
Confirm = f4(PKb, PKa, Nb, Z)and revealsNbonly later - Protection effect: attackers cannot replay old
Pairing ConfirmorRandommessages
Multiple authentication methods:
- Depending on both sides’ IO capabilities, the stack chooses among:
- Numeric Comparison: show a 6-digit code and let the user confirm it
- Passkey Entry: one side enters the 6-digit code shown on the other side
- Just Works: no user interaction, no MITM protection, but still protects against passive eavesdropping
- OOB (Out of Band): exchange data over NFC, QR codes, or another side channel
Privacy:
- Uses
IRKto generate aResolvable Private Address (RPA) - Addresses rotate every 15 minutes by default, with a configurable range of 1 to 3600 seconds
- Resolution works by matching
AES-128(IRK, prand)against the stored hash
LE Secure Connections Pairing Flow
The pairing flow has three phases:
- Phase 1 (Feature exchange): after the link-layer connection is up, Pairing Request/Response exchange IO capability, authentication requirements, and key-distribution policy
- Phase 2 (Authentication and key generation): based on the negotiated method (
Numeric Comparison,Passkey Entry,Just Works, orOOB), identity is verified and the LTK is generated - Phase 3 (Key distribution): the encrypted connection is established and other keys such as IRK and CSRK are distributed
LESC characteristic: Phase 2 uses ECDH P-256, and the LTK is derived by f5 instead of being distributed in Phase 3.
When pairing starts:
- Lazy Pairing: the most common mode. The device discovers services first, and pairing is triggered later by
ATT Error: Insufficient Authentication (0x05)when a protected attribute is accessed. - Proactive Pairing: pairing starts right after connection, without waiting for GATT discovery.
- Central can send
Pairing Requestdirectly - Peripheral can send
Security Request (0x0B)first - Common in HID or high-security devices
- Central can send
If the product requires “protected service immediately after the first connection,” proactive pairing is usually the better choice. If quick connection and low disturbance matter more, lazy pairing is usually the default.
Phase 1: Feature exchange
[IO Cap, OOB, AuthReq, MaxKeySize, KeyDist] R->>I: ② Pairing Response
[IO Cap, OOB, AuthReq, MaxKeySize, KeyDist] Note over I,R: Negotiate pairing method
(Numeric Comparison / Passkey Entry / Just Works / OOB) I->>R: ③ Pairing Public Key
[PKa = (PKax, PKay)] (64 bytes) R->>I: ④ Pairing Public Key
[PKb = (PKbx, PKby)] (64 bytes) Note over I: Compute DHKey = ECDH(SKa, PKb) Note over R: Compute DHKey = ECDH(SKb, PKa)
Protocol message details
Pairing Request/Response (SMP opcode 0x01/0x02):
Byte layout:
┌──────────────────────────────────┐
│ Code (1 byte): 0x01/0x02 │ Opcode
├──────────────────────────────────┤
│ IO Capability (1 byte) │ IO capability
├──────────────────────────────────┤
│ OOB Data Flag (1 byte) │ OOB data flag
├──────────────────────────────────┤
│ AuthReq (1 byte) │ Authentication requirements
│ Bit 0: Bonding │ Whether bonding is enabled
│ Bit 2: MITM │ Whether MITM protection is required
│ Bit 3: SC (Secure Connections) │ Whether LESC is used
│ Bit 4: Keypress │ Passkey input notification
│ Bit 5: CT2 │ Cross-transport key derivation (ignored in pure BLE)
├──────────────────────────────────┤
│ Maximum Encryption Key Size (1) │ 7-16 (16 recommended)
├──────────────────────────────────┤
│ Initiator Key Distribution (1) │ Initiator key distribution
│ Bit 0: EncKey (LTK) │ LESC derives LTK via f5, so no distribution needed
│ Bit 1: IdKey (IRK) │ Identity Information
│ Bit 2: Sign (CSRK) │ Signing Information
│ Bit 3: Link │ BR/EDR Link Key (set to 0 in pure BLE)
├──────────────────────────────────┤
│ Responder Key Distribution (1) │ Responder key distribution (same as above)
└──────────────────────────────────┘
Example Pairing Response for a pure BLE peripheral:
02 00 00 0d 10 06 06
Bluetooth Security Manager Protocol
Opcode: Pairing Response (0x02)
IO Capability: Display Only (0x00)
OOB Data Flags: OOB Auth. Data Not Present (0x00)
AuthReq: 0x0d, Secure Connection Flag, MITM Flag, Bonding Flags: Bonding
...0 .... = Keypress Flag: False
.... 1... = Secure Connection Flag: True
.... .1.. = MITM Flag: True
.... ..01 = Bonding Flags: Bonding (0x1)
Max Encryption Key Size: 16
Initiator Key Distribution: 0x06, Signature Key (CSRK), Id Key (IRK)
.... .1.. = Signature Key (CSRK): True
.... ..1. = Id Key (IRK): True
Responder Key Distribution: 0x06, Signature Key (CSRK), Id Key (IRK)
.... .1.. = Signature Key (CSRK): True
.... ..1. = Id Key (IRK): True
Protocol design points:
IO Capability: determines the authentication methodAuthReq.SC: must be1to use LESCMaximum Encryption Key Size: negotiated key length, fixed at16for LESCKey Distribution: decides which keys are distributed in Phase 3
If you only want to figure out why you did not get to LESC, look at these four fields first: AuthReq.SC, AuthReq.MITM, IO Capability, and OOB Data Flag. A lot of “security level is not what I expected” problems come from these fields.
What to check first during embedded bring-up
| Symptom | First judgment | Fields or logs to check first |
|---|---|---|
| MITM was expected, but not achieved | The authentication method degraded | IO Capability, AuthReq.MITM, OOB Data Flag |
| LESC was expected, but did not happen | One side did not enable SC | AuthReq.SC, pairing request/response capability negotiation |
| It keeps not pairing after connection | Protected access may not have been triggered yet | ATT Error 0x05, Security Request |
| It keeps pairing again after bonding | Keys were not stored correctly or identity was not restored | bond storage, IRK/LTK recovery, Encryption Change |
Pairing Public Key (SMP opcode 0x0C):
Byte layout:
┌──────────────────────────────────┐
│ Code (1 byte): 0x0C │
├──────────────────────────────────┤
│ Public Key X (32 bytes) │ X coordinate of P-256 public key
├──────────────────────────────────┤
│ Public Key Y (32 bytes) │ Y coordinate of P-256 public key
└──────────────────────────────────┘
Protocol design: public key exchange happens in Phase 1, so later ECDH computation can run in parallel with user interaction.
Phase 2: Authentication and key generation
The core job of Phase 2 is to generate the LTK through the ECDH shared secret and the chosen authentication method. The timing differs significantly between methods:
- Numeric Comparison / Just Works: one Confirm/Random exchange
- Passkey Entry: 20 rounds of Confirm/Random exchange, one bit of the 6-digit code per round
- OOB: one exchange, but the Confirm value includes OOB data
Numeric Comparison / Just Works
[Cb = f4(PKb, PKa, Nb, 0)] I->>R: ⑥ Pairing Random
[Na] R->>I: ⑦ Pairing Random
[Nb] Note over I: Verify Cb == f4(PKb, PKa, Nb, 0)
Compute Va = g2(PKa, PKb, Na, Nb)
Display 6-digit number = Va mod 1000000 Note over R: Compute Vb = g2(PKa, PKb, Na, Nb)
Display 6-digit number = Vb mod 1000000 Note over I,R: User confirms the numbers match
→ enter Stage 2
Three-layer security design:
1. Random (Nonce) - session freshness
- Purpose: generate a unique session identifier for each pairing and prevent replay attacks
- Mechanism: both sides generate 128-bit random numbers (
Na/Nb) - Effect: even if public keys are reused, the LTK is different for each session because f5 binds the nonces
2. Confirmation - commitment scheme
- Purpose: prevent the later sender from adjusting its nonce based on the earlier sender’s nonce
- Mechanism: the responder first sends
Cb = f4(PKb, PKa, Nb, 0)as a commitment, then revealsNb - Effect: both nonces remain independently random, and the initiator can verify that the responder did not cheat
3. Va/Vb (6-digit code) - MITM protection
- Purpose: prevent an attacker from replacing a public key, which is the core man-in-the-middle attack method
- Mechanism:
Va = Vb = g2(PKa, PKb, Na, Nb) mod 1000000, then the user compares the displayed values - Effect: if the public key was replaced, the computed codes differ and the user rejects pairing
Just Works difference:
- The flow is exactly the same as Numeric Comparison
- The difference is that it skips the code display and user confirmation step
- ⚠️ No MITM protection, only passive eavesdropping protection (it cannot stop public-key replacement attacks)
If the device only has NoInputNoOutput, do not assume MITM protection is available. At that point the question becomes whether the business can tolerate that, or whether OOB is required.
Passkey Entry (20 iterations)
Protocol design: a 6-digit number (000000-999999) needs log2(1000000) ≈ 20 bits of entropy, so 20 rounds are used to verify each bit.
Purpose of the 20-iteration design - gradual disclosure
- Verify the passkey bit by bit, and abort immediately if any bit fails, without revealing later bits
- If MITM fails, the attacker gains at most 2 bits of information (success probability increases from 0.000001 to 0.000004, which is basically negligible)
Notifies input progress end loop Iteration i (i = 1 to 20) Note over I: Generate Na[i] (128-bit)
ra[i] = (P >> (20-i)) & 0x01 Note over R: Generate Nb[i] (128-bit)
rb[i] = (P >> (20-i)) & 0x01 I->>R: ⑤ Pairing Confirm
[Ca[i] = f4(PKa, PKb, Na[i], ra[i])] R->>I: ⑥ Pairing Confirm
[Cb[i] = f4(PKb, PKa, Nb[i], rb[i])] I->>R: ⑦ Pairing Random
[Na[i]] Note over R: Verify Ca[i] == f4(PKa, PKb, Na[i], rb[i]) R->>I: ⑧ Pairing Random
[Nb[i]] Note over I: Verify Cb[i] == f4(PKb, PKa, Nb[i], ra[i]) end Note over I,R: 20 iterations complete
→ enter Stage 2
Iteration mechanism:
- New nonce each round:
Na[i]andNb[i]are generated independently in each iteration - Bit extraction:
ra[i] = rb[i] = (P >> (20-i)) & 0x01, extracting the i-th bit of the passkey from high bit to low bit - Commit-verify mechanism: first exchange Confirm commitments, then exchange Random disclosures, and the receiver verifies the peer’s Confirm value immediately
- Pairing Keypress Notification: while the user is entering the passkey, input-progress notifications may optionally be sent (
SMP 0x0E), but this does not affect the cryptographic computation
Difference from Numeric Comparison:
- Passkey Entry: 20 iterations, new nonce each time,
f4usesra[i]/rb[i]as theZparameter (single bit) - Numeric Comparison: one exchange,
f4usesZ = 0
OOB authentication
Protocol design: authentication data is exchanged through an external channel (NFC, QR codes, BR/EDR, etc.), providing the highest security. OOB authentication has two independent stages:
Stage 1 (Authentication Stage 1): OOB data exchange, before BLE pairing
Stage 2 (SMP Pairing): Security Manager pairing flow
Key design differences:
- The OOB stage uses
ra/rb(128-bit random numbers) - The SMP stage uses
Na/Nb(128-bit random numbers, same as Numeric Comparison) - In the OOB stage, the
f4function uses the same public key parameters:f4(PKa, PKa, ra, 0)instead off4(PKa, PKb, Na, 0)
Set peer rb = 0 (initial assumption) Note over B: Generate rb (128-bit)
Set peer ra = 0 (initial assumption) Note over A: Compute Ca = f4(PKa, PKa, ra, 0) Note over B: Compute Cb = f4(PKb, PKb, rb, 0) A->>B: ① OOB channel exchange
[Address A, ra, Ca] B->>A: ② OOB channel exchange
[Address B, rb, Cb] Note over A,B: ========== BLE connection established ========== Note over A,B: Security Manager Pairing begins Note over A: Step 5a: verify Cb
Adjust ra based on B's OOB data flag Note over B: Step 5b: verify Ca
Adjust rb based on A's OOB data flag Note over A: Generate Na (128-bit) Note over B: Generate Nb (128-bit) A->>B: ③ Pairing Random [Na] B->>A: ④ Pairing Random [Nb] Note over A,B: Enter Stage 2
Three OOB modes:
| Mode | ra value | rb value | Security | Meaning |
|---|---|---|---|---|
| Bidirectional OOB | != 0 | != 0 | ⭐⭐⭐ | Both sides exchange data through OOB |
| Unidirectional OOB | != 0 | = 0 | ⭐⭐ | Only one side provides OOB data |
| No OOB | = 0 | = 0 | ⭐ | Both sides have OOB data flag = not present, and the flow degrades to Just Works |
Security impact:
- The values of
ra/rbdirectly affect theRparameter in DHKey Check - Unidirectional OOB still protects against passive eavesdropping, but it cannot prevent active MITM unless there is extra authentication
- No OOB mode is equivalent to Just Works, with no MITM protection
If the product really needs “protection against active MITM” as a hard requirement and the device lacks suitable HMI, OOB is usually the only reliable option.
Comparison with other authentication methods
| Feature | Numeric Comparison | Passkey Entry | OOB |
|---|---|---|---|
| Phase 2 stages | 1 stage | 1 stage | 2 stages (OOB + SMP) |
| Confirm/Random exchange | 1 exchange | 20 iterations | 1 exchange in the OOB stage; no Confirm in SMP |
f4 parameters |
f4(PKa, PKb, Na, 0) |
f4(PKa, PKb, Na[i], ra[i]) |
f4(PKa, PKa, ra, 0) |
| User interaction | Confirm a 6-digit number | Enter a 6-digit number | Scan NFC / QR code |
| MITM protection | ✅ (about 20-bit entropy) | ✅ (20-bit entropy) | ✅ (128-bit entropy) |
| Security level | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
Security advantage:
- The attacker has to break both the BLE wireless channel and the OOB channel
ra/rbprovide 128-bit entropy, which is much stronger than Numeric Comparison’s roughly 20-bit entropy- This is a better fit for high-security use cases such as payments, access control, and medical devices
Cryptographic functions (Stage 1)
f4 function (Confirm value calculation):
Ca/Cb = f4(U, V, X, Z) = AES-CMAC_X(U || V || Z)
Parameters:
- U: PKax (initiator public key X coordinate, 32 bytes)
- V: PKbx (responder public key X coordinate, 32 bytes)
- X: Na/Nb (16 bytes) — AES-CMAC key
- Z: 0x00 (Numeric Comparison) or ra/rb (Passkey Entry)
g2 function (6-digit code calculation):
Va/Vb = g2(U, V, X, Y) = AES-CMAC_X(U || V || Y) mod 2^32
6-digit code = Va mod 1000000
Parameters:
- U: PKax (initiator public key X coordinate, 32 bytes)
- V: PKbx (responder public key X coordinate, 32 bytes)
- X: Na — AES-CMAC key
- Y: Nb
Phase 2 Stage 2 and Phase 3: key derivation and distribution
Protocol design: Stage 2 (LTK computation and verification) and Phase 3 (key distribution) are independent of the authentication method. All authentication methods share the same flow.
Stage 2: LTK computation and DHKey Check
[Ea = f6(MacKey, Na, Nb, rb, IOcapA, A, B)] Note over R: Verify Ea R->>I: ⑩ DHKey Check
[Eb = f6(MacKey, Nb, Na, ra, IOcapB, B, A)] Note over I: Verify Eb Note over I,R: Verification succeeds, both sides obtain the same LTK
f5 function (MacKey and LTK derivation):
T = AES-CMAC_SALT(DHKey)
SALT = 0x6C888391AAF5A53860370BDB5A6083BE (fixed value)
MacKey = AES-CMAC_T(0x00 || "btle" || N1 || N2 || A1 || A2 || 0x00 0x01)
LTK = AES-CMAC_T(0x01 || "btle" || N1 || N2 || A1 || A2 || 0x00 0x01)
Parameters:
- DHKey: ECDH shared key (X coordinate of the P-256 point, 32 bytes)
- N1: initiator nonce (Na, 16 bytes)
- N2: responder nonce (Nb, 16 bytes)
- A1: initiator Bluetooth address (7 bytes: 6-byte address + 1-byte type)
- A2: responder Bluetooth address (7 bytes)
- Counter: 0x00 (MacKey) or 0x01 (LTK)
- keyID: "btle" (4 ASCII bytes)
- Length: 0x0100 (256 bits, stored in little-endian as 0x00 0x01)
Output:
- MacKey: 128-bit (used for the f6 function)
- LTK: 128-bit (link encryption key)
f6 function (DHKey Check calculation):
Ea/Eb = f6(MacKey, N1, N2, R, IOcap, A1, A2)
= AES-CMAC_MacKey(N1 || N2 || R || IOcap || A1 || A2)
Parameters:
- MacKey: 128-bit key derived by f5
- N1, N2: depend on the authentication method
- Numeric Comparison / Just Works / OOB:
- Initiator computes Ea: N1=Na, N2=Nb
- Responder computes Eb: N1=Nb, N2=Na
- Passkey Entry:
- Initiator computes Ea: N1=Na20, N2=Nb20 (nonce from iteration 20)
- Responder computes Eb: N1=Nb20, N2=Na20 (nonce from iteration 20)
- R: 3 bytes, depends on the authentication method:
- Passkey Entry: 6-digit passkey
- Numeric Comparison / Just Works: 0
- OOB: combined value of ra (initiator) and rb (responder), depending on unidirectional/bidirectional OOB mode
- IOcap: 3 bytes (IO capabilities of both sides)
- A1, A2: Bluetooth addresses
**Notes**:
- The order of N1/N2 and A1/A2 is reversed between initiator and responder
- In OOB mode, the `R` value is determined by the adjusted `ra/rb` from Step 5a/5b
Phase 3: Key distribution (optional)
[IRK] I->>R: ⑫ Identity Address Information
[Public address / static address] end alt Initiator distributes CSRK I->>R: ⑬ Signing Information
[CSRK] end alt Responder distributes IRK R->>I: ⑭ Identity Information
[IRK] R->>I: ⑮ Identity Address Information
[Public address / static address] end alt Responder distributes CSRK R->>I: ⑯ Signing Information
[CSRK] end Note over I,R: Pairing complete, enter bonded state
The easiest thing to confuse here is this: under LESC, LTK is not sent in Phase 3. Both sides derive it locally using f5. Phase 3 is mainly used to send IRK and CSRK, not the link-encryption key.
Key hierarchy and encryption mechanism
Key derivation flow
ECDH key exchange
↓
DHKey (256-bit, X coordinate of P-256 point)
↓ [f5 function]
├─ MacKey (128-bit) → DHKey Check (f6)
└─ LTK (128-bit) → link-layer encryption
↓ [link layer established]
LTK-encrypted connection
↓
IRK (128-bit, distributed in Phase 3) → RPA generation/resolution
CSRK (128-bit, distributed in Phase 3) → data signing
LTK link encryption (AES-CCM)
Protocol mechanism:
- Encryption algorithm: AES-CCM (Counter with CBC-MAC, M=4)
- Key: LTK (128-bit, used directly in LESC without extra derivation)
- Nonce: Packet Counter (39-bit) || Direction (1-bit) || IV (64-bit)
- MIC: 32-bit / 4 bytes (Link Layer MIC)
LESC encryption start flow:
[Rand=0, EDIV=0, IV_C] Note over C: IV_C: 32-bit random number Note over P: Find LTK (by connection identity)
Generate IV_P (32-bit random number) P->>C: LL_ENC_RSP
[IV_P] Note over C,P: Build session IV together
IV = IV_C || IV_P (64-bit) P->>C: LL_START_ENC_REQ
(unencrypted) Note over P: Enable encryption mode
Wait for encryption response C->>P: LL_START_ENC_RSP
(encrypted) Note over C: Enable encryption before sending P->>C: LL_START_ENC_RSP
(encrypted) Note over P: Verify that decryption succeeded Note over C,P: 🔒 Bidirectional verification complete, encrypted connection established
LESC encryption mechanism:
- Uses LTK directly as the AES-CCM key
- ⚠️
LL_ENC_REQusesRand=0,EDIV=0to mark an LTK derived by LESC IV_C(Central’s IV) andIV_P(Peripheral’s IV) are each 32-bit and are concatenated into a 64-bit session IV- The IV changes on every connection, which provides session independence
LL_START_ENChandshake: Peripheral sends the request first (unencrypted), and both sides send a response once (encrypted) to verify each other- The nonce is built from Packet Counter + Direction + IV (see below)
AES-CCM encrypted format:
Before encryption:
┌──────────┬──────────────────┐
│ Header │ Payload │
└──────────┴──────────────────┘
After encryption:
┌──────────┬──────────────────┬─────────┐
│ Header │ Encrypted Payload│ MIC(32) │
└──────────┴──────────────────┴─────────┘
CCM nonce (13 bytes, 104-bit):
┌────────────────┬───────────┬────────┐
│ Packet Counter │ Direction │ IV │
│ (39-bit) │ (1-bit) │(64-bit)│
└────────────────┴───────────┴────────┘
Notes:
- Packet Counter: packet sequence number in the connection (increments, prevents replay)
- Direction: 0 = Central→Peripheral, 1 = Peripheral→Central
- IV: concatenation of IV_C || IV_P from LL_ENC_REQ/RSP (IV_C and IV_P are 32-bit each)
IRK and the privacy mechanism
Resolvable Private Address (RPA):
RPA (48-bit) = prand (24-bit) || hash (24-bit)
hash = AES-128(IRK, padding || prand)[0:23]
RPA update strategy:
- RPA changes every 15 minutes (default 900 seconds, configurable range 1-3600 seconds) to prevent device tracking
prandis fully random, so it has no visible patternhashis computed withIRK, so only paired devices can resolve the RPA- An attacker who sees different RPAs at different times cannot tell whether they belong to the same device without the IRK
Privacy limits:
- RPA only hides the device address: broadcast data (Complete Local Name, Service UUIDs) may still reveal the device identity
- Broadcast fingerprinting: an attacker may correlate different RPAs over time by using broadcast interval, RSSI patterns, packet structure, and other features
- Initial pairing problem: IRK can only be exchanged after pairing, so the first connection uses a public address or a static random address
- Complete privacy solution: requires GAP Limited Discoverable Mode + dynamic broadcast data + hardware RNG support
For embedded developers, the most practical conclusion is:
RPAsolves the problem of being tracked through a long-lived fixed address; it does not solve broadcast-content leakage- Once a device enables
RPA, bring-up, allow-listing, background bonding, and packet-capture identification all become more complicated - If you do not have a clear privacy requirement, start by getting the bring-up path working with a stable address, and then decide whether to introduce
RPA
CSRK data signing
ATT Signed Write: data-integrity verification without an encrypted connection (used less often in practice)
Authentication Signature = SignCounter (4 bytes) || MAC (8 bytes)
MAC = AES-CMAC(CSRK, Opcode || Handle || Value || SignCounter)[0:63]
ATT_SIGNED_WRITE_CMD(0xD2) carries a 12-byte signatureSignCounterincreases monotonically (prevents replay)- ⚠️ It provides integrity only, not confidentiality (the payload is still sent in the clear)
Engineering decisions and debugging
IO Capabilities and authentication-method decisions
| IO Capability | Display capability | Input capability | Typical device |
|---|---|---|---|
| DisplayOnly | Yes | No | Temperature sensor with screen |
| DisplayYesNo | Yes | Yes/No buttons | Smart watch |
| KeyboardOnly | No | Keyboard | Keyboard without a display |
| NoInputNoOutput | No | No | Headphones, Beacon |
| KeyboardDisplay | Yes | Keyboard | Smartphone, PC |
Authentication-method decision order (from highest priority to lowest):
- OOB: if either side has
OOB Data Flag = 0x01(OOB data is provided), use OOB authentication (highest security) - Numeric Comparison: use it when the two IO capability combinations satisfy the conditions for confirming a displayed number
- Passkey Entry: one side can type, and the other side can display the passkey
- Just Works: all other cases (⚠️ no MITM protection)
This is only a pragmatic engineering simplification. In a real implementation or validation pass, you should still revisit the complete decision table in the Core Spec based on the IO capability combination of both sides, so you do not lump DisplayOnly, DisplayYesNo, and KeyboardDisplay together.
Packet capture and debugging cheat sheet
These are the most common debugging points in real development:
| Symptom | Priority layer | Common cause | First place to check |
|---|---|---|---|
| Pairing does not happen for a long time after connection | GATT / SMP | The application has not accessed a protected attribute yet; the host strategy is Lazy Pairing | Whether ATT Error: Insufficient Authentication (0x05) or Security Request appears |
Sent Pairing Request but did not enter LESC |
SMP | AuthReq.SC=0; one side does not support SC |
AuthReq in Pairing Request/Response |
| MITM was expected but the result is Just Works | SMP | The IO Capability combination does not qualify; OOB was not declared correctly |
IO Capability, OOB Data Flag, AuthReq.MITM |
| Pairing fails after public-key exchange | SMP / HCI | Random-number verification failed; DHKey Check failed; the user did not confirm the number | Pairing Confirm, Pairing Random, DHKey Check, Pairing Failed |
| Pairing succeeds but business access still returns permission errors | ATT / GAP | A higher security level is actually required; the link was not truly encrypted; bonding information was not restored | Encryption Change, attribute permissions, whether the link re-encrypts after reconnect |
| Peer address changes after reconnect | GAP / Privacy | The device enabled RPA | Whether IRK has been distributed and whether the host successfully resolves RPA |
When capturing packets, it is better to follow this order instead of staring at the cryptographic functions first:
- Confirm the trigger point first: is pairing triggered by access to a protected attribute, or does it start proactively after connection?
- Then check capability negotiation:
SC,MITM,IO Capability, andKey DistributioninPairing Request/Response - Then check the authentication path: did it go through
Public Key,Confirm/Random, andDHKey Check? - Finally check the outcome: did you see
Pairing Failed,Encryption Change, orIdentity Information?
Common failure paths are worth remembering too:
- You only see
Security Request, but the Central never sendsPairing Request: usually the host policy did not respond, or the application disabled auto-pairing - It fails immediately after
Pairing Random: first suspect Confirm verification failure, or a user-side Numeric Comparison / Passkey operation that was not completed - It fails after
DHKey Check: first suspect inconsistent authentication data, such as Passkey/OOB data or address-parameter handling - It was already bonded, but it pairs again every time it reconnects: first check bond persistence, IRK/LTK storage, and identity restoration
Quick reference appendix
The main text is enough for most pairing, encryption, and reconnect debugging. The following sections only keep the commands and tools that are often checked during implementation.
Common HCI commands
| Command | Opcode | Description | Spec section |
|---|---|---|---|
| LE_Read_Local_P256_Public_Key | 0x2025 | Read the local P-256 public key | Vol 4, Part E, § 7.8.36 |
| LE_Generate_DHKey | 0x2026 | Generate the ECDH shared key | Vol 4, Part E, § 7.8.37 |
| LE_Enable_Encryption | 0x2019 | The master starts link-layer encryption (sends LL_ENC_REQ) |
Vol 4, Part E, § 7.8.24 |
Common tool entry points
- Wireshark + BTVS: inspect HCI / L2CAP / SMP interactions
- Android
btsnoop_hci.log: capture HCI logs on the phone side - BlueZ
btmon: inspect HCI events on Linux hosts - nRF Connect (Mobile): scan, connect, and pair for testing
- Nordic nRF Sniffer: capture over-the-air packets
References
Standards
-
Bluetooth Core Specification
- Volume 3, Part H: Security Manager Protocol (SMP)
- Volume 6, Part B: Link Layer Specification (LL Encryption)
- Volume 4, Part E: Host Controller Interface (HCI Commands)
If you only want to get the main path straight first, start with these three spec entry points:
Vol 3, Part H: SMP message formats, authentication methods,f4/f5/f6/g2Vol 6, Part B: link-layer encryption start andLL_ENC_REQ/LL_START_ENC_*Vol 4, Part E: host-side HCI commands and events, useful for matching logs
Further reading
- BLE Architecture Overview: return to the layering boundaries and a unified debugging entry point
- BLE GAP Analysis: return to address types, security levels, and the Privacy entry point
- BLE Link Layer / PHY: return to the connection events, retransmission, and air-interface stability after encrypted link setup
- BLE GATT/ATT Analysis: continue with how permission errors map to concrete attribute access and error codes