Why SSP Was Needed
Before Bluetooth 2.1, pairing was based on a 4-digit numeric PIN. That is only 10,000 possible values. Attackers could brute-force this in seconds, and most users never changed the factory default — 0000, 1234, 8888.
Bluetooth 2.1 + EDR introduced Secure Simple Pairing (SSP) with two goals: make pairing easier for users and make it significantly harder to attack. It replaced the weak PIN with a 16-character alphanumeric key backed by real public-key cryptography (ECDH).
Before SSP vs After SSP
| Property | Bluetooth <= 2.0 + EDR | Bluetooth 2.1 + EDR (SSP) |
|---|---|---|
| PIN Type | 4-digit numeric only | 16-character alphanumeric |
| Possible values | 10,000 | ~6 × 1022 |
| Key Exchange | Symmetric PIN only | ECDH public-key exchange |
| Eavesdropping Protection | Weak | Strong |
| MITM Protection | None | Yes (model dependent) |
| User Experience | Manual PIN entry on both sides | Often zero user interaction needed |
3.6 — SSP Security Goals
A passive attacker sits nearby and records all the radio packets flying between two Bluetooth devices. They do not interfere — they just listen. If the key exchange is weak, they can later derive the session key and decrypt everything that was recorded.
SSP fights this with two mechanisms working together:
ECDH KEY EXCHANGE IN SSP
════════════════════════════════════════════════════════════════
Device A Device B
───────────────── ─────────────────
Generate private key: a Generate private key: b
Compute public key: Compute public key:
PKa = a * G (G = curve base point) PKb = b * G
──── PKa ────────────►
◄─── PKb ────────────
Compute shared secret: Compute shared secret:
DHKey = a * PKb DHKey = b * PKa
= a * (b * G) = b * (a * G)
= ab * G ✓ = ab * G ✓
Both sides now have the SAME DHKey without ever transmitting it.
A passive attacker who captured PKa and PKb cannot derive DHKey
without solving the Elliptic Curve Discrete Logarithm Problem.
────────────────────────────────────────────────────────────────
WHY ECDH OVER CLASSIC DH?
• Classic DH needs large prime numbers (2048-bit+) for security
• ECDH achieves equivalent security with a 256-bit key
• Bluetooth controllers have limited CPU — ECDH fits perfectly
────────────────────────────────────────────────────────────────
In an MITM attack the attacker is not passive — they actively intercept traffic. Device A thinks it is talking to Device B, and Device B thinks it is talking to Device A. In reality both are connected to the attacker (Device M) in the middle. Device M reads, possibly alters, and then forwards every message.
MITM ATTACK — HOW IT LOOKS
════════════════════════════════════════════════════════════════
EXPECTED (what users believe):
┌──────────┐ ┌──────────┐
│ Device A │◄────────── Direct Link ───────────►│ Device B │
└──────────┘ └──────────┘
ACTUAL (with attacker M in the middle):
┌──────────┐ ┌───────────────────────┐ ┌──────────┐
│ Device A │◄──►│ Device M (Attacker) │◄──►│ Device B │
└──────────┘ └───────────────────────┘ └──────────┘
A↔M: M pretends to be B M↔B: M pretends to be A
What M can do:
─────────────
• Read all messages (confidentiality broken)
• Inject fake commands or data
• Silently drop or replay packets
• Impersonate either device for future sessions
How SSP detects M:
──────────────────
• Numeric Comparison: both A and B display a 6-digit number.
If M is in the middle, the numbers will NOT match.
User sees mismatch → rejects pairing → M is detected.
• If M's connection drops: A and B lose communication.
User can infer something is wrong.
3.6.3 — SSP Association Models
SSP does not force one fixed pairing method on every device. Instead, it defines four association models. The model selected during pairing depends on the I/O capabilities of both devices involved — does the device have a screen? Can it accept key input?
The Bluetooth stack on each device advertises its I/O capability during the capability exchange phase, and the two sides negotiate which model to use.
| Device A Capability | Device B Capability | Model Selected | MITM Protection |
|---|---|---|---|
| Display + Yes/No | Display + Yes/No | Numeric Comparison | Yes |
| Display only | Keyboard only | Passkey Entry | Yes |
| No display, No input | Any | Just Works | No |
| NFC enabled | NFC enabled | Out of Band (OOB) | Yes (if OOB channel is secure) |
Both devices must be able to display a 6-digit number and accept a Yes / No response from the user. During pairing, both screens show the same 6-digit code. The user simply checks whether the number matches on both screens and confirms.
This works even on devices that have a small display but no full keyboard — a single confirmation button is enough. The 6-digit number is derived from the ECDH exchange, so if an attacker is in the middle the numbers will differ.
NUMERIC COMPARISON — PAIRING FLOW
════════════════════════════════════════════════════════════════
Device A (Phone) Device B (Laptop)
────────────────── ─────────────────
1. Exchange I/O capabilities
──── IO_CAP: DisplayYesNo ────────────►
◄─── IO_CAP: DisplayYesNo ────────────
2. ECDH public key exchange
──── Public Key PKa ──────────────────►
◄─── Public Key PKb ──────────────────
3. Compute DHKey (shared secret) on both sides
4. Generate commitment values (Cb, Ca) using nonces + DHKey
5. Exchange nonces (Na, Nb)
6. Both sides compute the 6-digit confirmation value V:
V = f4(PKax, PKbx, Na, 0) (same formula, same result)
┌──────────────────┐ ┌──────────────────────────────┐
│ Screen shows: │ │ Screen shows: │
│ │ │ │
│ [ 4 8 7 2 3 ] │ │ [ 4 8 7 2 3 ] │
│ │ │ │
│ Confirm? YES/NO │ │ Confirm? YES/NO │
└──────────────────┘ └──────────────────────────────┘
↓ User presses YES ↓ User presses YES
7. Pairing succeeds — link key derived from DHKey
─── MITM scenario: M replaces PKb with PKm ─────────────────────
• A computes V using PKm instead of PKb → V_A = 312890
• B computes V using PKm instead of PKa → V_B = 895123
• Numbers do NOT match → user rejects → MITM detected ✓
Just Works is Numeric Comparison running silently — the 6-digit code is computed and verified internally by the stack but never shown to the user. Pairing completes without the user doing anything.
This is the right model for devices like mono Bluetooth headsets that have no screen and no buttons beyond play/pause. You cannot ask the user to confirm a number they cannot see.
OOB pairing exchanges authentication data over a different communication channel — typically NFC. The idea is that an attacker who can intercept Bluetooth radio packets almost certainly cannot also intercept a very short-range NFC tap at the same time.
When the user physically taps two devices together, the NFC channel transfers device addresses and random values. These are then fed into the Bluetooth pairing process in place of the numeric confirmation step.
OUT-OF-BAND PAIRING WITH NFC
════════════════════════════════════════════════════════════════
Bluetooth Channel NFC Channel (OOB)
───────────────── ─────────────────────────────
User physically taps devices
┌──────┐ ┌──────┐
│ A │◄────►│ B │
└──────┘ └──────┘
NFC range: ~4 cm
Transfers:
• BD_ADDR of A and B
• Random nonce r_A, r_B
• Hash values c_A, c_B
──── Confirm OOB data received ──────────────────────────────►
◄─── Confirm OOB data received ─────────────────────────────
Both sides verify hashes:
A verifies: c_B = f(r_B, PKb, BD_ADDR_B) ✓
B verifies: c_A = f(r_A, PKa, BD_ADDR_A) ✓
Link key derived → Pairing complete
Passkey Entry is designed for the classic keyboard-to-PC pairing scenario. One device has a display (the PC) and shows a 6-digit passkey. The other device has a keyboard (the BT keyboard) and the user types in that code.
This provides MITM protection because a human is actively verifying and transferring a secret value. An attacker in the middle would need to intercept what is being shown on the display and what the user is typing — neither of which travels over the Bluetooth radio.
PASSKEY ENTRY — PAIRING FLOW
════════════════════════════════════════════════════════════════
PC (Display only) BT Keyboard (Input only)
───────────────── ────────────────────────
1. I/O capability exchange
──── IO_CAP: DisplayOnly ──────────►
◄─── IO_CAP: KeyboardOnly ──────────
2. PC generates random 6-digit passkey (e.g. 483921)
┌─────────────────┐
│ PC Screen: │
│ │
│ PIN: 483921 │
│ │
└─────────────────┘
3. User reads passkey from PC screen
4. User types 4 8 3 9 2 1 on the Bluetooth keyboard
┌─────────────────────┐
│ User types: │
│ 4 - 8 - 3 - 9 - │
│ 2 - 1 - ENTER │
└─────────────────────┘
5. Passkey is used bit-by-bit in 20 rounds of commitment
exchanges to build the link key — passkey never sent raw
over the Bluetooth air interface.
6. Stack verifies both sides used the same passkey
→ Link key generated → Pairing complete
Association Model — Side-by-Side Comparison
MODEL DISPLAY NEEDED INPUT NEEDED PASSIVE EVE MITM ────────────── ────────────── ──────────── ─────────── ──── Numeric Comp. YES YES/NO Yes Yes Just Works NO NO Yes No Out of Band Optional Optional Yes Yes * Passkey Entry YES YES Yes Yes * OOB MITM protection depends on the OOB channel being secure. REAL-WORLD DEVICE EXAMPLES: ────────────────────────────────────────────────────────────────── Model Typical Device Pair ────────────────── ───────────────────────────────────────────── Numeric Comparison Phone + Laptop, Phone + Car infotainment Just Works Phone + Mono headset, Phone + BT speaker Out of Band Phone + Smart tag (NFC tap to pair) Passkey Entry Laptop + BT keyboard, Laptop + BT mouse
Bluetooth SSP Programming in C
#ifndef SSP_IO_CAPABILITIES_H
#define SSP_IO_CAPABILITIES_H
#include <stdint.h>
/*
* HCI I/O Capability values (Bluetooth Core Spec Vol 2, Part C, 5.2.1)
* Used in HCI_IO_Capability_Request_Reply command.
*/
#define IO_CAP_DISPLAY_ONLY 0x00 /* e.g. BT speaker with LED display */
#define IO_CAP_DISPLAY_YES_NO 0x01 /* e.g. Phone, Laptop */
#define IO_CAP_KEYBOARD_ONLY 0x02 /* e.g. BT keyboard (no screen) */
#define IO_CAP_NO_INPUT_NO_OUTPUT 0x03 /* e.g. Mono headset */
#define IO_CAP_KEYBOARD_DISPLAY 0x04 /* e.g. Full smartphone */
/*
* OOB Data Present field
*/
#define OOB_DATA_NOT_PRESENT 0x00
#define OOB_DATA_FROM_REMOTE 0x01
/*
* Authentication Requirements
* Bit 2 = MITM protection required (1 = yes)
* Bit 0 = Dedicated bonding (vs general bonding)
*/
#define AUTH_REQ_NO_BONDING_NO_MITM 0x00
#define AUTH_REQ_BONDING_NO_MITM 0x02
#define AUTH_REQ_NO_BONDING_MITM 0x04
#define AUTH_REQ_BONDING_MITM 0x05
/*
* Association model selected based on I/O capability negotiation.
* Use ssp_select_model() to determine this at runtime.
*/
typedef enum {
SSP_MODEL_NUMERIC_COMPARISON = 0,
SSP_MODEL_JUST_WORKS,
SSP_MODEL_OUT_OF_BAND,
SSP_MODEL_PASSKEY_ENTRY,
SSP_MODEL_UNKNOWN
} ssp_model_t;
typedef struct {
uint8_t io_capability; /* One of IO_CAP_* above */
uint8_t oob_present; /* OOB_DATA_NOT_PRESENT or _PRESENT */
uint8_t auth_req; /* AUTH_REQ_* flags */
} ssp_io_cap_t;
#endif /* SSP_IO_CAPABILITIES_H */
#include <stdio.h>
#include "ssp_io_capabilities.h"
/*
* ssp_select_model()
*
* Implements the SSP association model selection table from
* Bluetooth Core Spec Vol 2, Part C, Section 5.2.2.6.
*
* Returns the association model to use based on the combined
* I/O capabilities of both devices.
*/
ssp_model_t ssp_select_model(const ssp_io_cap_t *local,
const ssp_io_cap_t *remote)
{
/* OOB takes priority if both sides have OOB data */
if (local->oob_present == OOB_DATA_FROM_REMOTE ||
remote->oob_present == OOB_DATA_FROM_REMOTE) {
return SSP_MODEL_OUT_OF_BAND;
}
/*
* If neither side requires MITM, Just Works is used
* when the combined capability would normally give Numeric
* Comparison or Passkey Entry — saves user effort.
*/
uint8_t mitm_required = (local->auth_req & 0x04) ||
(remote->auth_req & 0x04);
uint8_t l = local->io_capability;
uint8_t r = remote->io_capability;
/*
* Table lookup: rows = local IO cap, columns = remote IO cap
* Values: N=NumericComparison, J=JustWorks, P=Passkey
*
* Remote
* DOnly DYN KOnly NINO KD
* Local DOnly: J N P J P
* Local DYN : N N P J N
* Local KOnly: P P P J P
* Local NINO : J J J J J
* Local KD : P N P J N
*/
/* NINO (no input, no output) always gives Just Works */
if (l == IO_CAP_NO_INPUT_NO_OUTPUT ||
r == IO_CAP_NO_INPUT_NO_OUTPUT) {
return SSP_MODEL_JUST_WORKS;
}
/* Determine if Passkey Entry applies */
int local_has_display = (l == IO_CAP_DISPLAY_ONLY ||
l == IO_CAP_DISPLAY_YES_NO ||
l == IO_CAP_KEYBOARD_DISPLAY);
int remote_has_input = (r == IO_CAP_KEYBOARD_ONLY ||
r == IO_CAP_KEYBOARD_DISPLAY);
int remote_has_display = (r == IO_CAP_DISPLAY_ONLY ||
r == IO_CAP_DISPLAY_YES_NO ||
r == IO_CAP_KEYBOARD_DISPLAY);
int local_has_input = (l == IO_CAP_KEYBOARD_ONLY ||
l == IO_CAP_KEYBOARD_DISPLAY);
/* Numeric Comparison: both sides have display + yes/no capability */
if ((l == IO_CAP_DISPLAY_YES_NO || l == IO_CAP_KEYBOARD_DISPLAY) &&
(r == IO_CAP_DISPLAY_YES_NO || r == IO_CAP_KEYBOARD_DISPLAY)) {
return SSP_MODEL_NUMERIC_COMPARISON;
}
/* Passkey Entry: one side has display, other has keyboard */
if ((local_has_display && remote_has_input) ||
(local_has_input && remote_has_display)) {
return SSP_MODEL_PASSKEY_ENTRY;
}
/*
* If MITM is not required, fall back to Just Works even if
* Numeric Comparison capability exists.
*/
if (!mitm_required) {
return SSP_MODEL_JUST_WORKS;
}
return SSP_MODEL_JUST_WORKS; /* fallback */
}
const char *ssp_model_name(ssp_model_t model)
{
switch (model) {
case SSP_MODEL_NUMERIC_COMPARISON: return "Numeric Comparison";
case SSP_MODEL_JUST_WORKS: return "Just Works";
case SSP_MODEL_OUT_OF_BAND: return "Out of Band (OOB)";
case SSP_MODEL_PASSKEY_ENTRY: return "Passkey Entry";
default: return "Unknown";
}
}
/* -------------------------------------------------------
* Handle an incoming HCI_IO_Capability_Request event.
* The host must reply with local I/O capabilities.
* ------------------------------------------------------- */
void handle_io_cap_request(int hci_fd, const uint8_t *bd_addr)
{
/*
* HCI_IO_Capability_Request_Reply command
* OGF = 0x01 (Link Control)
* OCF = 0x002B
*
* Parameters:
* BD_ADDR [6 bytes]
* IO_Capability [1 byte] — advertise our capability
* OOB_Data_Present [1 byte]
* Authentication_Requirements [1 byte]
*/
printf("[SSP] IO_Capability_Request received\n");
printf("[SSP] Replying with: DisplayYesNo, NoOOB, MITM required\n");
/*
* In a real BlueZ-based implementation you would call:
* hci_send_cmd(hci_fd, OGF_LINK_CTL, 0x002B, params_len, params)
*
* Here we just show the parameter values for illustration.
*/
uint8_t params[9];
memcpy(params, bd_addr, 6);
params[6] = IO_CAP_DISPLAY_YES_NO; /* local I/O capability */
params[7] = OOB_DATA_NOT_PRESENT; /* no NFC OOB data */
params[8] = AUTH_REQ_BONDING_MITM; /* require MITM + bond */
printf("[SSP] Params: IO_CAP=0x%02X OOB=0x%02X AUTH=0x%02X\n",
params[6], params[7], params[8]);
}
/* -------------------------------------------------------
* Handle Numeric Comparison: compare and auto-confirm.
* In a real product this would drive a UI prompt.
* ------------------------------------------------------- */
void handle_user_confirmation_request(const uint8_t *bd_addr,
uint32_t numeric_value)
{
printf("[SSP] User Confirmation Request\n");
printf("[SSP] Remote BD_ADDR: %02X:%02X:%02X:%02X:%02X:%02X\n",
bd_addr[5], bd_addr[4], bd_addr[3],
bd_addr[2], bd_addr[1], bd_addr[0]);
printf("[SSP] Numeric Value on both screens: %06u\n", numeric_value);
printf("[SSP] → Show this to user; send Positive Reply if they confirm\n");
/*
* HCI_User_Confirmation_Request_Positive_Reply (OCF=0x002C)
* or HCI_User_Confirmation_Request_Negative_Reply (OCF=0x002D)
* depending on user input.
*/
}
/* -------------------------------------------------------
* Handle Passkey Request: display passkey to user.
* ------------------------------------------------------- */
void handle_passkey_display(const uint8_t *bd_addr, uint32_t passkey)
{
printf("[SSP] Passkey Notification\n");
printf("[SSP] Display this code to user: %06u\n", passkey);
printf("[SSP] User must type this on the remote device keyboard\n");
}
int main(void)
{
printf("=== SSP Association Model Selection Demo ===\n\n");
/* Scenario 1: Phone (DisplayYesNo) + Laptop (DisplayYesNo) */
ssp_io_cap_t phone = { IO_CAP_DISPLAY_YES_NO, OOB_DATA_NOT_PRESENT, AUTH_REQ_BONDING_MITM };
ssp_io_cap_t laptop = { IO_CAP_DISPLAY_YES_NO, OOB_DATA_NOT_PRESENT, AUTH_REQ_BONDING_MITM };
printf("Phone + Laptop → %s\n",
ssp_model_name(ssp_select_model(&phone, &laptop)));
/* Scenario 2: Phone (DisplayYesNo) + Headset (NINO) */
ssp_io_cap_t headset = { IO_CAP_NO_INPUT_NO_OUTPUT, OOB_DATA_NOT_PRESENT, AUTH_REQ_NO_BONDING_NO_MITM };
printf("Phone + Headset → %s\n",
ssp_model_name(ssp_select_model(&phone, &headset)));
/* Scenario 3: Laptop (DisplayOnly) + BT Keyboard (KeyboardOnly) */
ssp_io_cap_t pc = { IO_CAP_DISPLAY_ONLY, OOB_DATA_NOT_PRESENT, AUTH_REQ_BONDING_MITM };
ssp_io_cap_t keyboard = { IO_CAP_KEYBOARD_ONLY, OOB_DATA_NOT_PRESENT, AUTH_REQ_BONDING_MITM };
printf("Laptop + Keyboard → %s\n",
ssp_model_name(ssp_select_model(&pc, &keyboard)));
/* Scenario 4: Both NFC-enabled devices */
ssp_io_cap_t nfc_a = { IO_CAP_DISPLAY_YES_NO, OOB_DATA_FROM_REMOTE, AUTH_REQ_BONDING_MITM };
ssp_io_cap_t nfc_b = { IO_CAP_DISPLAY_YES_NO, OOB_DATA_FROM_REMOTE, AUTH_REQ_BONDING_MITM };
printf("NFC device + NFC device → %s\n",
ssp_model_name(ssp_select_model(&nfc_a, &nfc_b)));
return 0;
}
gcc -o ssp_demo ssp_model_select.c ./ssp_demo # Output: # Phone + Laptop → Numeric Comparison # Phone + Headset → Just Works # Laptop + Keyboard → Passkey Entry # NFC device + NFC → Out of Band (OOB)
Quick Reference
SECURE SIMPLE PAIRING — QUICK REFERENCE
════════════════════════════════════════════════════════════════
Introduced in : Bluetooth 2.1 + EDR
Goals : Passive eavesdropping protection + MITM protection
Security Mechanisms
───────────────────
• Strong Link Key : 16-char alphanumeric PIN (vs 4-digit before)
• ECDH crypto : Shared secret without transmitting it
Computationally lighter than classic DH
I/O Capability Constants (HCI values)
──────────────────────────────────────
0x00 IO_CAP_DISPLAY_ONLY BT speaker, simple display
0x01 IO_CAP_DISPLAY_YES_NO Phone, laptop
0x02 IO_CAP_KEYBOARD_ONLY BT keyboard (no screen)
0x03 IO_CAP_NO_INPUT_NO_OUTPUT Mono headset, BT audio tag
0x04 IO_CAP_KEYBOARD_DISPLAY Full smartphone
Association Models
──────────────────
Numeric Comparison Both display 6-digit code MITM: YES
Just Works Silent, no user action MITM: NO
Out of Band (OOB) NFC tap or similar MITM: YES*
Passkey Entry One shows, one types 6 digits MITM: YES
HCI Events During SSP
─────────────────────
0x31 IO_Capability_Request → reply with our caps
0x32 IO_Capability_Response ← remote caps received
0x33 User_Confirmation_Request → user sees 6-digit code
0x34 User_Passkey_Request → user types passkey
0x35 Remote_OOB_Data_Request → provide OOB data
0x36 Simple_Pairing_Complete ← result (success/fail)
Master Bluetooth Security
Continue with BLE Security — LE Pairing, SMP protocol, and key distribution at EmbeddedPathashala — completely free for embedded developers.
