11.13. Emparejamiento y vinculación

Todo lo que se ha tratado hasta aquí mueve bytes por la radio en claro. Cualquiera con un portátil con capacidad BLE en la misma sala puede escuchar en los canales de anuncio, seguir la secuencia de salto de una conexión abierta y leer cada lectura, escritura y notificación que la atraviese. Para la mayoría de los datos públicos de sensores (nivel de batería, temperatura ambiente) eso está bien. Para cualquier cosa que los dos extremos quieran mantener en privado – un registro de control que arma un relé, una contraseña, una medición que no debería difundirse ampliamente – el enlace debe cifrarse, y lo ideal es que la cámara sepa con quién está hablando.

BLE ofrece ambas cosas mediante el emparejamiento (pairing) y la vinculación (bonding).

11.13.1. Emparejamiento, vinculación, cifrado

Tres conceptos estrechamente relacionados:

  • El cifrado es el objetivo final. Una vez que el enlace está cifrado, cada paquete en los canales de datos solo lo pueden descifrar los dos extremos; un espía solo ve ruido.

  • El emparejamiento (pairing) es el procedimiento que ejecutan los dos extremos para acordar las claves que utiliza el cifrado. Es un intercambio único que produce material de claves compartido que la capa de enlace conecta a su motor de cifrado.

  • La vinculación (bonding) es la decisión de persistir las claves en el almacenamiento no volátil después de que finaliza el emparejamiento, de modo que la siguiente conexión entre los dos mismos dispositivos se salte el emparejamiento y pase directamente al cifrado.

En lenguaje sencillo: el emparejamiento es «preséntense»; la vinculación es «recuerda esta presentación»; el cifrado es «a partir de ahora hablen en privado».

Dos columnas etiquetadas como "Central" y "Periférico". Una línea discontinua cerca de la parte superior etiquetada como "conexión BLE abierta (sin cifrar)". Debajo de ella, tres flechas: "solicitud de emparejamiento" del central al periférico, "intercambio de claves" en ambas direcciones, "emparejamiento completo" hacia adelante. Una segunda línea discontinua debajo etiquetada como "enlace cifrado". Dos flechas bidireccionales gruesas transportan "tráfico GATT cifrado". Una caja opcional de "guardar claves en flash" a un lado, etiquetada como "vinculación".

El flujo de emparejamiento sobre una conexión BLE abierta. Una vez que se completa el intercambio de claves, la capa de enlace cifra todos los paquetes posteriores. La vinculación es el paso adicional de escribir las claves en la memoria flash.

11.13.2. LE Secure Connections

El intercambio de claves moderno que utiliza BLE es LE Secure Connections, construido sobre Elliptic Curve Diffie-Hellman. Ambas partes generan un par de claves temporal, intercambian las mitades públicas y combinan el resultado con sus propias claves privadas para llegar al mismo secreto compartido – un secreto que un espía no puede calcular ni siquiera con un registro completo del intercambio.

El método más antiguo LE Legacy es menos seguro (un espía con el intercambio completo normalmente puede recuperar la clave) y existe solo por compatibilidad con periféricos antiguos. El valor predeterminado de aioble es el método moderno (le_secure=True); manténgalo.

11.13.3. Iniciar el emparejamiento

Un central empareja llamando a aioble.DeviceConnection.pair() sobre una conexión ya abierta:

async with await device.connect() as connection:
    await connection.pair(bond=True, le_secure=True, mitm=False)
    # ... GATT work, now over an encrypted link ...

Después de que pair retorna, los atributos encrypted, authenticated, bonded y key_size de la conexión reflejan lo que se negoció.

Los cuatro argumentos de palabra clave más útiles:

  • bond=True – guarda las claves resultantes en la memoria flash para que la siguiente conexión entre los dos mismos dispositivos se salte el protocolo de emparejamiento. El valor predeterminado es True.

  • le_secure=True – usa LE Secure Connections. El valor predeterminado es True. Déjelo activado.

  • mitm=False – indica si se requiere protección frente a intermediarios (man-in-the-middle). Esto necesita un canal fuera de banda (un código numérico mostrado en un lado y confirmado en el otro, una clave de acceso tecleada, …) para que el usuario pueda verificar que los dos dispositivos del protocolo de emparejamiento son realmente los que cree. Su valor predeterminado es False (sin protección MITM – un espía pasivo no puede leer el enlace, pero un atacante que redirija activamente las conexiones podría emparejarse). Póngalo en True para cualquier cosa sensible, pero tenga en cuenta que requiere que el periférico admita realmente una capacidad de E/S.

  • io=3 – la capacidad de E/S que declara el dispositivo. La especificación de Bluetooth define cinco: 0 solo pantalla, 1 pantalla + sí/no, 2 solo teclado, 3 sin entrada ni salida, 4 teclado + pantalla. Una cámara sin interfaz de usuario normalmente informa 3; si la propia cámara tiene una pantalla, la aplicación podría mostrar la confirmación numérica y usar 1. La combinación de las capacidades de E/S de ambos lados decide si es posible una protección MITM real.

Los periféricos no llaman a pair por sí mismos – responden a lo que sea que inicie el central. Que se requiera o no cifrado para una característica dada es una propiedad de cómo se declara en la base de datos GATT; los bits de acceso que requieren cifrado forman parte de la API de bajo nivel bluetooth y actualmente no se exponen a través del constructor de características de aioble.

11.13.4. Vinculación – y dónde viven las claves

Cuando bond=True, aioble escribe las claves en un archivo JSON en el sistema de archivos local. El nombre de archivo predeterminado es ble_secrets.json, escrito en relación con el directorio de trabajo actual. En una cámara recién arrancada, _boot.py ya ha elegido ese directorio: /sdcard cuando hay una tarjeta montada, /flash en caso contrario – de modo que el archivo termina en /sdcard/ble_secrets.json o /flash/ble_secrets.json. El archivo contiene las entradas necesarias para volver a cifrar el enlace la próxima vez que el par vinculado se reconecte, incluida la dirección de identidad del par.

Una asimetría a tener en cuenta: el guardado ocurre automáticamente a medida que cambian las claves, pero la carga del archivo en el siguiente arranque no. Llame a aioble.security.load_secrets() una vez al inicio (antes de cualquier emparejamiento o anuncio) para que se reconozcan los pares previamente vinculados:

import aioble
aioble.security.load_secrets()        # default path: ble_secrets.json

Después de eso, la próxima vez que aparezca un par vinculado, aioble reutiliza las claves almacenadas y el enlace se cifra sin más protocolo de enlace.

Dos consecuencias prácticas de almacenar claves en la memoria flash:

  • Olvidar un dispositivo. Elimine ble_secrets.json (o quite la entrada correspondiente) para olvidar todos los pares vinculados, luego vuelva a emparejar desde cero.

  • El acceso físico filtra claves. Cualquiera con acceso al sistema de archivos de la cámara puede leer el JSON. Este es el mismo tipo de restricción que surgió en el lado de las redes con las claves TLS (Operaciones: claves, caducidad y resolución de problemas): use claves por dispositivo, trate cualquier clave almacenada como recuperable y confíe en la capacidad de revocar (aquí, eliminar la vinculación del lado del central) en lugar de en que la clave permanezca secreta.

11.13.5. Qué garantiza el cifrado – y qué no

Un enlace que primero empareja y luego cifra ofrece, en orden de robustez:

  • Confidencialidad. Siempre. Un espía no puede leer los bytes.

  • Integridad. Siempre. Los paquetes modificados no superan la comprobación de cifrado autenticado de la capa de enlace y se descartan.

  • Autenticación. Solo con mitm=True y una E/S capaz. Sin ella, un intermediario que haya interceptado el intercambio de emparejamiento original podría haberse insertado; sin protección MITM no hay forma de que los dos lados lo sepan.

Para la mayoría de los casos de uso de la cámara – un teléfono que se empareja con la cámara una vez y luego se conecta de nuevo más tarde – mitm=False suele ser suficiente, porque el emparejamiento original ocurre en un entorno controlado (el usuario tiene ambos dispositivos en la misma sala). Para aplicaciones en las que un dispositivo emparejado podría encontrarse por primera vez con la cámara a larga distancia o a través de un intermediario no confiable, MITM es la configuración correcta.

11.13.6. Cuándo el emparejamiento es la respuesta equivocada

El emparejamiento tiene un coste real: unos segundos de intercambio en la primera conexión, uso persistente de la memoria flash por cada dispositivo vinculado y la historia de recuperación de «olvidar la vinculación» si algo va mal. Para datos genuinamente públicos – lecturas de sensores ambientales publicadas como baliza, un cartel que muestra su nombre, cualquier cosa que no cambie el mundo por ser leída o escrita – la respuesta correcta es no cifrar en absoluto, y dejar que cualquier escáner cercano lea los valores.

Para todo lo demás, connection.pair(bond=True) en el central es la adición de una sola línea que convierte el enlace de un canal público en uno privado.