ESP32 BLE Secure Client Example (Static Passkey)

This example demonstrates how to build a secure ESP32 BLE client using a static passkey. It scans for a BLE server, connects securely using MITM protection, reads both insecure and secure characteristics, subscribes to notifications, and writes data to both — showcasing full encrypted BLE communication.

1. Introduction

This example builds on the standard ESP32 BLE client, but adds something critical:

👉 security

Instead of a simple connection, this client performs:

  • secure pairing
  • MITM-protected authentication
  • encrypted communication

It uses a static passkey (123456) shared between client and server.

This is exactly how many real-world BLE devices work:

  • medical devices
  • smart locks
  • industrial sensors
  • authenticated IoT nodes

2. What this example does

The workflow is:

  1. Clears previous pairing data
  2. Configures BLE security (passkey + MITM)
  3. Scans for a device advertising a specific service
  4. Connects as a secure BLE client
  5. Reads:
  6. Subscribes to notifications
  7. Writes data to both characteristics periodically

3. Key concept: Secure vs Insecure characteristics

This example uses two characteristics:

static BLEUUID insecureCharUUID(...)
static BLEUUID secureCharUUID(...)

Insecure characteristic

  • No authentication required
  • Can be read immediately

Secure characteristic

  • Requires encryption + MITM
  • Triggers pairing process

👉 This is the core demonstration of BLE security behavior

4. Static passkey setup

#define CLIENT_PIN 123456

And later:

pSecurity->setPassKey(true, CLIENT_PIN);

This means:

  • The passkey is fixed (not random)
  • No user input required
  • Client automatically authenticates

⚠️ Must match the server passkey exactly

5. Clearing pairing data (important)

nvs_flash_erase();
nvs_flash_init();

This clears stored bonding keys.

Why this matters:

  • ESP32 stores pairing info in flash
  • Without clearing, it may skip authentication
  • This ensures fresh pairing every run

👉 This is critical for testing security behavior


6. BLE security configuration

6.1 Authentication mode

pSecurity->setAuthenticationMode(false, true, true);

This enables:

  • Secure connection
  • MITM protection
  • Encryption

Without MITM:

👉 BLE would fall back to Just Works pairing (less secure)

6.2 IO capability

pSecurity->setCapability(ESP_IO_CAP_IN);

This tells BLE:

  • device behaves like keyboard input

Even though the passkey is static, this is required to:

👉 enable proper MITM authentication flow

7. Bluedroid vs NimBLE behavior

The example highlights an important difference:

Bluedroid (ESP32 default)

  • Security starts on connect
  • You cannot access secure data without pairing

NimBLE (C3/S3 default)

  • Security starts on demand
  • You can read insecure data before authentication

👉 This explains why the code behaves slightly differently across chips

8. Connection process

Create client

BLEClient *pClient = BLEDevice::createClient();

Connect to server

pClient->connect(myDevice);

Set MTU

pClient->setMTU(517);

This improves:

  • throughput
  • efficiency

9. Service and characteristic discovery

pClient->getService(serviceUUID);

Then:

getCharacteristic(insecureCharUUID);
getCharacteristic(secureCharUUID);

If either fails → disconnect

👉 Good defensive design

10. Reading characteristics

Insecure read

pRemoteInsecureCharacteristic->readValue();

Works immediately.

Secure read

pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_MITM);
pRemoteSecureCharacteristic->readValue();

This triggers:

👉 authentication + pairing process

11. Notifications

registerForNotify(notifyCallback);

Both characteristics can send updates.

Callback prints:

  • UUID
  • data length
  • raw payload

12. Writing data

Inside loop:

Insecure write

pRemoteInsecureCharacteristic->writeValue(...)

Secure write

pRemoteSecureCharacteristic->writeValue(...)

The difference:

  • insecure → always works
  • secure → requires encryption

13. Scanning and device detection

advertisedDevice.isAdvertisingService(serviceUUID)

Once found:

doConnect = true;

14. Main loop logic

Connect when ready

if (doConnect)

When connected

  • write to both characteristics
  • demonstrate secure communication

When disconnected

BLEDevice::getScan()->start(0);

Restart scanning

15. Full runtime flow

  1. Clear pairing data
  2. Start scan
  3. Find server
  4. Connect
  5. Read insecure data
  6. Trigger authentication
  7. Read secure data
  8. Receive notifications
  9. Write to both characteristics
  10. Repeat

16. Important requirements

You need matching server

This example is designed to work with:

👉 Server_secure_static_passkey

Without it:

  • no connection
  • no authentication
  • nothing happens

17. Common pitfalls

1. Passkey mismatch

→ connection fails silently or disconnects

2. MITM disabled

→ no real security (Just Works used)

3. Not clearing NVS

→ pairing skipped unexpectedly

4. Wrong IO capability

→ authentication method may fail

18. Real-world use cases

This pattern is used in:

  • secure IoT sensors
  • industrial BLE devices
  • smart home authentication
  • medical devices
  • encrypted telemetry

19. Improvements for production

1. Replace static passkey

Use dynamic or user-entered key

2. Add bonding

Store keys for faster reconnection

3. Handle reconnect logic better

Add retry/backoff strategy

4. Parse data properly

Instead of raw Serial output

5. Add device filtering

By address or RSSI

Honest take

This example is:

  • very powerful
  • rarely understood properly
  • poorly explained in official docs

Biggest hidden concept:

👉 BLE security is triggered by access to protected attributes — not just connection

Once you understand that, everything clicks.

Conclusion

This example demonstrates one of the most important BLE concepts:

👉 secure communication using pairing + encryption

It shows:

  • how BLE security actually works
  • how authentication is triggered
  • how secure vs insecure data behaves

If you understand this example, you’re already ahead of most ESP32 BLE developers.

Share your love

Leave a Reply

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