A library for implementing secure Bluetooth Low Energy (BLE) pairing and encryption on Raspberry Pi Pico using the arduino-pico core.
The pico-ble-secure
library provides an easy-to-use interface for implementing secure BLE connections on Raspberry Pi Pico microcontrollers. It supports various security levels, pairing methods, and bond management.
This library extends the functionality of the arduino-pico core's BTstack implementation to make secure BLE connections more accessible to developers.
-
Multiple Security Levels:
- LOW: No encryption, no authentication
- MEDIUM: Encryption without MITM protection ("Just Works" pairing)
- HIGH: Encryption with MITM protection
- HIGH_SC: Encryption with MITM protection and Secure Connections (highest security)
-
Flexible Pairing Methods:
- Just Works pairing
- Passkey Display
- Passkey Entry
- Numeric Comparison
-
Bonding Support:
- Store and manage paired device information
- Reconnect to previously bonded devices
-
Callback System:
- Receive notifications for all pairing events
- Display pairing codes
- Confirm pairing with numeric comparison
- Automatic pairing management through connection callbacks
- Add this to your
platformio.ini
file:
[env:rpipicow]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
framework = arduino
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
build_flags =
-DPIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH
-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV4
lib_deps =
pico-ble-secure
- PlatformIO will automatically download and install the library.
- Download the library as a ZIP file
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library
- Select the downloaded ZIP file
- Raspberry Pi Pico W or Pico 2 W
- Arduino-pico core with BTstack support
- I2C/SPI display for showing passkeys
- Buttons for user input (e.g., confirming numeric comparison)
- Can use onboard BOOTSEL button
- LEDs for visual feedback during pairing
- Can use onboard LED
- Keypad or other input methods for entering passkeys
#include <Arduino.h>
#include <BTstackLib.h>
#include <BLESecure.h>
// Callbacks for BLE events
void bleDeviceConnected(BLEStatus status, BLEDevice* device) {
if (status == BLE_STATUS_OK) {
Serial.println("Device connected!");
// No need to manually call BLESecure.requestPairing() here
// It's automatically handled if requestPairingOnConnect is enabled
}
}
void bleDeviceDisconnected(BLEDevice* device) {
Serial.println("Device disconnected!");
}
void setup() {
Serial.begin(115200);
// Set device name
BTstack.setup("SecureBLE");
// Initialize BLE security with Numeric Comparison capability
BLESecure.begin(IO_CAPABILITY_DISPLAY_YES_NO);
// Set security level to HIGH - encryption with MITM protection
BLESecure.setSecurityLevel(SECURITY_HIGH, true);
// Request pairing automatically when a device connects
BLESecure.requestPairingOnConnect(true);
// Register connection callbacks through BLESecure (not BTstack)
BLESecure.setBLEDeviceConnectedCallback(bleDeviceConnected);
BLESecure.setBLEDeviceDisconnectedCallback(bleDeviceDisconnected);
// Start advertising
BTstack.startAdvertising();
}
void loop() {
// Process BLE events
BTstack.loop();
delay(10);
}
Register connection and disconnection callbacks through BLESecure instead of directly through BTstack:
// Register callbacks through BLESecure for automatic pairing management
BLESecure.setBLEDeviceConnectedCallback(bleDeviceConnected);
BLESecure.setBLEDeviceDisconnectedCallback(bleDeviceDisconnected);
This allows the BLESecure library to automatically handle pairing requests based on your requestPairingOnConnect
setting.
Choose the appropriate security level for your application:
// Just Works pairing (vulnerable to MITM attacks)
BLESecure.setSecurityLevel(SECURITY_MEDIUM, true);
// Secure pairing with MITM protection
BLESecure.setSecurityLevel(SECURITY_HIGH, true);
// Highest security with Secure Connections
BLESecure.setSecurityLevel(SECURITY_HIGH_SC, true);
// Callback for displaying passkey during pairing
void onPasskeyDisplay(uint32_t passkey) {
Serial.print("Please enter this passkey on your device: ");
Serial.println(passkey);
}
// Callback for numeric comparison during pairing
void onNumericComparison(uint32_t passkey, BLEDevice* device) {
Serial.print("Do the following numbers match? ");
Serial.println(passkey);
// Get user confirmation (e.g., via button press)
// Then accept or reject:
BLESecure.acceptNumericComparison(true); // or false to reject
}
// Callback for pairing status updates
void onPairingStatus(BLEPairingStatus status, BLEDevice* device) {
switch (status) {
case PAIRING_IDLE:
Serial.println("Pairing idle");
break;
case PAIRING_STARTED:
Serial.println("Pairing started");
break;
case PAIRING_COMPLETE:
Serial.println("Pairing complete - connection is now secure!");
break;
case PAIRING_FAILED:
Serial.println("Pairing failed");
break;
}
}
void setup() {
// ... other setup code ...
// Register callbacks
BLESecure.setPasskeyDisplayCallback(onPasskeyDisplay);
BLESecure.setNumericComparisonCallback(onNumericComparison);
BLESecure.setPairingStatusCallback(onPairingStatus);
}
When a Pico device has been bonded with a central device (like a smartphone) and then the Pico is flashed with new firmware, the following error sequence may occur when attempting to reconnect:
Re-encryption started with bonded device
Pairing started
Re-encryption failed, status: 61
Pairing failed
This occurs because:
- The central device (smartphone) still has the bonding information
- The Pico has lost all bonding data after being flashed
- The central device attempts to use the old bonding keys to re-establish a secure connection
- The pairing fails because the Pico no longer recognizes these keys
There are several approaches to handle this situation:
The simplest approach for development is to remove the bonded device from your smartphone/central device:
- On Android: Go to Settings → Bluetooth → Previously Connected Devices → Tap the gear or arrow icon next to your device → "Forget" or "Unpair"
- On iOS: Go to Settings → Bluetooth → Tap the "i" next to your device → "Forget This Device"
For production devices, consider implementing persistent storage of bonding information:
- Store bonding keys in flash memory (e.g., using the LittleFS on Pico)
- Restore bonding information after firmware updates
- See the arduino-pico documentation for file system usage
Implement automatic bond clearing after a failed reconnection attempt:
// In your onPairingStatus callback
void onPairingStatus(BLEPairingStatus status, BLEDevice *device) {
switch (status) {
case PAIRING_FAILED:
Serial.println("Pairing failed - attempting to clear existing bond");
// In a real application, you might want to:
// 1. Request the central device to forget the bond
// 2. Try a fresh pairing after a delay
// For now, we can just disconnect to force a fresh connection
if (device) {
gap_disconnect(device->getHandle());
}
break;
// Other cases...
}
}
During development, use Option 1 (removing the bond on the central device) for simplicity. For production devices, consider implementing a more robust solution with persistent storage (Option 2).
This behavior is common in BLE development and not specific to this library. Any time bonding information is lost on either the peripheral or central device, re-encryption will fail, and the bond must be reestablished.
Version 1.1.0 introduces a new way to handle connection events and automatic pairing. Here are the key changes:
-
Connection Callbacks: Register connection and disconnection callbacks through BLESecure instead of BTstack:
// Old way: BTstack.setBLEDeviceConnectedCallback(bleDeviceConnected); BTstack.setBLEDeviceDisconnectedCallback(bleDeviceDisconnected); // New way: BLESecure.setBLEDeviceConnectedCallback(bleDeviceConnected); BLESecure.setBLEDeviceDisconnectedCallback(bleDeviceDisconnected);
-
Automatic Pairing: When
requestPairingOnConnect(true)
is set, pairing is now automatically initiated when a device connects. You no longer need to manually check the flag and callrequestPairing()
in your connection callback:// Old way: void bleDeviceConnected(BLEStatus status, BLEDevice* device) { if (status == BLE_STATUS_OK) { // Manually check and request pairing if (BLESecure._requestPairingOnConnect) { BLESecure.requestPairing(device); } } } // New way: void bleDeviceConnected(BLEStatus status, BLEDevice* device) { if (status == BLE_STATUS_OK) { // Pairing is automatically handled if enabled // No manual check needed } }
These changes simplify the code required to implement secure BLE connections and ensure consistent behavior across applications.
The library includes several example platformIO projects demonstrating its usage:
- SecurePairingMedium: Basic encryption without MITM protection using the "Just Works" method
- SecurePairingHigh: Encryption with MITM protection using passkey or numeric comparison
- SecurePairingHighSC: The highest security level using Secure Connections
- ClearBondingTest: Clears bonding information in flash memory via BOOTSEL button press
- connect pico-W to computer with USB
- compile and upload the example platformio project
- open Serial Monitor in platformio
- open nRF Connect on your phone
- scan for devices
- connect to the Pico device
- named either
MediumSecBLE
,HighSecBLE
, orHighSCSecBLE
- named either
- follow the prompts for pairing
- on android, you may see two popups for pairing (click both)
- for
HighSecBLE
andHighSCSecBLE
, you will see a passkey on the Serial Monitor- enter the passkey on your phone before timeout
- subscribe to the characteristic notifications to receive encrypted messages
void begin(io_capability_t ioCapability = IO_CAPABILITY_DISPLAY_YES_NO)
: Initialize the security manager with specified IO capability
void setSecurityLevel(BLESecurityLevel level, bool enableBonding = true)
: Set the security level for connectionsvoid allowReconnectionWithoutDatabaseEntry(bool allow)
: Allow LTK reconstruction without a device DB entry (for peripheral role)void setFixedPasskey(uint32_t passkey)
: Set fixed passkey (0-999999) or NULL for random
void requestPairingOnConnect(bool enable)
: Request pairing automatically when a device connectsbool requestPairing(BLEDevice* device)
: Manually request pairing with a connected devicebool bondWithDevice(BLEDevice* device)
: Bond with a device (store keys for reconnection)bool removeBonding(BLEDevice* device)
: Remove bonding information for a devicevoid clearAllBondings()
: Remove all stored bonding information
void setBLEDeviceConnectedCallback(void (*callback)(BLEStatus status, BLEDevice* device))
: Register callback for device connection eventsvoid setBLEDeviceDisconnectedCallback(void (*callback)(BLEDevice* device))
: Register callback for device disconnection events
void setPasskeyDisplayCallback(void (*callback)(uint32_t passkey))
: Callback for handling passkey displayvoid setPasskeyEntryCallback(void (*callback)(void))
: Callback for handling passkey entry requestsvoid setPairingStatusCallback(void (*callback)(BLEPairingStatus status, BLEDevice* device))
: Callback for pairing status updatesvoid setNumericComparisonCallback(void (*callback)(uint32_t passkey, BLEDevice* device))
: Callback for numeric comparison
void setEnteredPasskey(uint32_t passkey)
: Set passkey for entry method (call this from the passkey entry callback)void acceptNumericComparison(bool accept)
: Accept or reject numeric comparisonBLEPairingStatus getPairingStatus()
: Get the current pairing statusbool isEncrypted(BLEDevice* device)
: Get the encryption status for a connection
This library is designed for:
- Raspberry Pi Pico and Pico W
- Arduino-pico core with BTstack support
This library is licensed under the MIT License - see the LICENSE file for details.
Note: This library depends on the arduino-pico core which is licensed under LGPL-2.1. When using this library, you must comply with the terms of both licenses.
Contributions to improve the library are welcome! Please submit pull requests or open issues on the repository.
This library is based on the BTstackLib library for Arduino-Core and Raspberry Pi Pico. Special thanks to the BTstack developers for their work on BLE stack implementation and arduino-pico core maintainers.