BLE Privacy and Address Resolution
The rotating-address scheme that stops a BLE device being tracked by its MAC, and the one 128-bit key that turns every rotation back into a single identity.
BLE privacy is the mechanism that lets a device advertise without handing every scanner in the room a permanent identifier. Instead of transmitting its real address, the device transmits an address it regenerates on a timer. To a stranger the device looks like a new device every few minutes. To a peer it has bonded with, it is still obviously itself. The whole scheme rests on one shared key and one small function.
What it is
Every BLE address is 48 bits and carries a type, signalled by the TxAdd or RxAdd bit in the PDU and by the address type field at the HCI layer. There are four kinds, and telling them apart is the first thing you do with any capture:
| Kind | Top two bits of the most significant octet | Behaviour |
|---|---|---|
| Public | not applicable, the type bit says public | The IEEE-assigned address. Permanent, and a tracking gift. |
| Random static | 0b11 |
Random, but fixed until the device is reprogrammed or reboots. Also trackable. |
| Non-resolvable private | 0b00 |
Random, rotates, resolvable by nobody. Rare, because a bonded peer cannot follow it either. |
| Resolvable private (RPA) | 0b01 |
Random, rotates, and can be tied back to one identity by a peer holding the key. The one you meet in the field. |
An RPA splits into two halves. The top 24 bits are prand, drawn fresh at each rotation with its top two bits forced to 0b01. The bottom 24 bits are hash, computed from prand with the device's Identity Resolving Key. The rotation period is a device setting; the specification's default is 15 minutes and vendors range from a few seconds to an hour.
The function is ah, defined in Core spec Vol 3 Part H 2.2.2, and it is nothing more than one AES-128 block:
ah(k, r) = e(k, padding || r) mod 2**24
padding is thirteen zero octets, r is the three octets of prand, e is plain AES-128 encryption, and mod 2**24 means "keep the three least significant octets of the result". Resolving an address is therefore: split it, run one AES block, compare 24 bits.
from Crypto.Cipher import AES
def ah(irk, prand):
return AES.new(irk, AES.MODE_ECB).encrypt(b"\x00" * 13 + prand)[13:16]
def resolves(irk, addr): # addr most significant octet first
return addr[0] & 0xC0 == 0x40 and ah(irk, addr[0:3]) == addr[3:6]
# Core spec sample data, a free self-test for any implementation
assert ah(bytes.fromhex("ec0234a357c8ad05341010a60a397d9b"),
bytes.fromhex("708194")) == bytes.fromhex("0dfbaa")
Where the IRK comes from
The IRK is distributed during bonding, in the Security Manager key distribution phase, as the Identity Information PDU, usually followed by Identity Address Information carrying the device's real identity address. Both sides may distribute one; the peripheral's IRK is the one that resolves the peripheral's adverts.
That distribution happens over the encrypted link, so an over-the-air sniffer sees ciphertext. It does not happen below HCI. In an HCI snoop taken on the phone or on a Linux host, key distribution is plain: btsmp.id_resolving_key is right there in the dissection, because HCI sits above link encryption. That difference between an air capture and a host capture decides whether privacy is an obstacle to you or not.
Why it matters
Privacy is the difference between a device you can follow through a building and a device you cannot. When it works, correlating a target across rotations is a research problem. When it does not, it is a grep.
For an attacker the practical questions are: - Is it even on? Plenty of shipped devices advertise from a public address, or from a random static one they never change, which is privacy in name only. - Do I have the IRK? If you have a host capture of a bonding, or the phone's key store, you can resolve every rotation offline for as long as the device keeps that IRK. - Is something else identifying? Privacy only randomises the address. A constant manufacturer payload, a unique service UUID, a distinctive advertising interval or a name field defeats it without any cryptography at all.
How to work with it
# All advertising addresses in a capture, with their type
tshark -r cap.pcap -Y btcommon.eir_ad.entry.type \
-T fields -e bthci_evt.bd_addr -e bthci_evt.le_peer_address_type
# The IRK, if the capture contains a bonding at the HCI layer
tshark -r cap.pcap -Y 'btsmp.opcode == 0x08' -T fields -e btsmp.id_resolving_key
On a Linux host, bluetoothctl and the kernel keep a resolving list: bonded identities the controller resolves in hardware as adverts arrive, so the higher layers only ever see the identity address. btmgmt irks shows what the kernel is holding.
Pitfalls
- An address is not an identity, and neither is a resolved address permanent. The IRK survives the bond, not the device. A factory reset or a re-pair issues a new IRK and every previously resolved address becomes unresolvable.
- The top two bits are not optional. Running
ahagainst a public or a random static address is meaningless and will produce false matches if you skip the type check. - Byte order eats implementations.
ahworks on the address most significant octet first, and BD_ADDR is little-endian on the wire. A resolver that never matches anything is nearly always a reversed buffer, not a wrong key. - Privacy without payload discipline is theatre. If the device keeps broadcasting a constant serial number in its manufacturer data, the rotating address protects nothing.
- Wireshark does not resolve for you. There is no key field to fill in that makes RPAs collapse into one identity in the dissection. You resolve them yourself, outside the tool.