14.13. Pairing and bonding¶
Everything covered up to here moves bytes over the radio in the clear. Anyone with a BLE-capable laptop in the same room can listen on the advertising channels, follow the hopping sequence of an open connection, and read out every read, write, and notification that goes across. For most public sensor data (battery level, ambient temperature) that is fine. For anything the two endpoints want to keep private – a control register that arms a relay, a password, a measurement that should not be widely broadcast – the link needs to be encrypted, and ideally the camera needs to know who it is talking to.
BLE provides both through pairing and bonding.
14.13.1. Pairing, bonding, encryption¶
Three closely related concepts:
Encryption is the bottom-line goal. Once the link is encrypted, every packet on the data channels is decipherable only by the two endpoints; an eavesdropper sees noise.
Pairing is the procedure the two endpoints run to agree on the keys that encryption uses. It is a one-time exchange that produces shared key material the link layer plugs into its encryption engine.
Bonding is the choice to persist the keys to non-volatile storage after pairing finishes, so the next connection between the same two devices skips pairing and goes straight to encryption.
Plain English: pairing is “introduce yourselves”; bonding is “remember this introduction”; encryption is “speak in private from now on”.
The pairing flow on top of an open BLE connection. Once the key exchange completes, the link layer encrypts every subsequent packet. Bonding is the extra step of writing the keys to flash.¶
14.13.2. LE Secure Connections¶
The modern key exchange used by BLE is LE Secure Connections, built on Elliptic Curve Diffie-Hellman. Both sides generate a temporary key pair, exchange the public halves, and combine the result with their own private keys to arrive at the same shared secret – a secret an eavesdropper cannot compute even with a full record of the exchange.
The older LE Legacy method is less safe (an
eavesdropper with the full exchange can usually
recover the key) and exists only for backward
compatibility with old peripherals. The aioble
default is the modern method (le_secure=True);
keep it.
14.13.3. Initiating pairing¶
A central pairs by calling
aioble.DeviceConnection.pair() on an already-open
connection:
async with await device.connect() as connection:
await connection.pair(bond=True, le_secure=True, mitm=False)
# ... GATT work, now over an encrypted link ...
After pair returns, the
encrypted,
authenticated,
bonded, and
key_size attributes on
the connection reflect what was negotiated.
The four most useful keyword arguments:
bond=True– save the resulting keys to flash so the next connection between the same two devices skips the pairing handshake. DefaultTrue.le_secure=True– use LE Secure Connections. DefaultTrue. Leave it on.mitm=False– whether to require man-in-the-middle protection. This needs an out-of-band channel (a numeric code displayed on one side and confirmed on the other, a passkey typed in, …) so the user can verify the two devices in the pairing handshake are actually the ones they think. Defaults toFalse(no MITM protection – a passive eavesdropper cannot read the link, but an attacker actively redirecting connections could pair themselves in). Set toTruefor anything sensitive, but be aware that it requires the peripheral to actually support an IO capability.io=3– the IO capability the device claims. The Bluetooth spec defines five:0display only,1display + yes/no,2keyboard only,3no input no output,4keyboard + display. A camera with no UI typically reports3; if the camera itself has a display the application could display the numeric confirmation and use1. The combination of the two sides’ IO capabilities decides whether real MITM protection is achievable.
Peripherals do not call pair themselves – they
respond to whatever the central initiates. Whether
encryption is required for a given characteristic is a
property of how it is declared in the GATT database;
encryption-required access bits are part of the
low-level bluetooth API and not currently
exposed through the aioble characteristic
constructor.
14.13.4. Bonding – and where the keys live¶
When bond=True, aioble writes the keys to a
JSON file on the local filesystem. The default filename
is ble_secrets.json, written relative to the current
working directory. On a freshly booted cam _boot.py
has already chosen that directory: /sdcard when a
card is mounted, /flash otherwise – so the file
lands at /sdcard/ble_secrets.json or
/flash/ble_secrets.json. The file holds the entries
needed to re-encrypt the link the next time the bonded
peer reconnects, including the peer’s identity address.
One asymmetry to keep in mind: saving happens
automatically as keys change, but loading the file on
the next boot does not. Call
aioble.security.load_secrets() once at startup
(before any pairing or advertising) so previously bonded
peers are recognised:
import aioble
aioble.security.load_secrets() # default path: ble_secrets.json
After that, the next time a bonded peer shows up,
aioble re-uses the stored keys and the link goes
encrypted with no further handshake.
Two practical consequences of storing keys on flash:
Forgetting a device. Delete
ble_secrets.json(or remove the relevant entry) to forget all bonded peers, then re-pair from scratch.Physical access leaks keys. Anyone with access to the camera’s filesystem can read the JSON. This is the same kind of constraint that came up on the networking side with TLS keys (Operations: keys, expiry, and troubleshooting): use per-device keys, treat any stored key as recoverable, and rely on the ability to revoke (here, removing the bond on the central side) rather than on the key staying secret.
14.13.5. What encryption guarantees – and what it does not¶
A pair-then-encrypt link gives, in order of strength:
Confidentiality. Always. An eavesdropper cannot read the bytes.
Integrity. Always. Modified packets fail the link-layer authenticated-encryption check and are dropped.
Authentication. Only with
mitm=Trueand a capable IO. Without it, a man-in-the-middle that intercepted the original pairing exchange could have inserted themselves; without MITM protection there is no way for the two sides to know.
For most camera use cases – a phone pairing with the
camera once, then connecting again later – mitm=False
is usually enough, because the original pairing happens
in a controlled environment (the user holds both devices
in the same room). For applications where a paired
device might first encounter the camera over a long
distance or through an untrusted intermediary, MITM is
the right setting.
14.13.6. When pairing is the wrong answer¶
Pairing has a real cost: a few seconds of exchange on first connect, persistent flash use for every bonded device, and the recovery story of “forget the bond” if something goes wrong. For genuinely public data – ambient sensor readings published as a beacon, a sign displaying its name, anything that does not change the world by being read or written – the right answer is not to encrypt at all, and let any nearby scanner read the values.
For everything else, connection.pair(bond=True) on
the central is the one-line addition that turns the
link from a public channel into a private one.