11.13. Pareamento e vinculação

Tudo o que foi abordado até aqui movimenta bytes pelo rádio em texto claro. Qualquer pessoa com um notebook com suporte a BLE na mesma sala pode escutar os canais de anúncio, acompanhar a sequência de saltos de uma conexão aberta e ler cada leitura, escrita e notificação que trafega. Para a maioria dos dados públicos de sensores (nível de bateria, temperatura ambiente) isso não é problema. Para qualquer coisa que os dois endpoints queiram manter em sigilo – um registrador de controle que aciona um relé, uma senha, uma medição que não deveria ser amplamente transmitida – o enlace precisa ser criptografado e, idealmente, a câmera precisa saber com quem está se comunicando.

O BLE oferece ambos por meio de pareamento e vinculação.

11.13.1. Pareamento, vinculação, criptografia

Três conceitos intimamente relacionados:

  • Criptografia é o objetivo final. Uma vez que o enlace está criptografado, cada pacote nos canais de dados só pode ser decifrado pelos dois endpoints; um bisbilhoteiro vê apenas ruído.

  • Pareamento é o procedimento que os dois endpoints executam para combinar as chaves que a criptografia utiliza. É uma troca única que produz material de chave compartilhado que a camada de enlace conecta ao seu mecanismo de criptografia.

  • Vinculação é a opção de persistir as chaves em armazenamento não volátil após a conclusão do pareamento, de modo que a próxima conexão entre os mesmos dois dispositivos pula o pareamento e vai direto para a criptografia.

Em linguagem simples: pareamento é “apresentem-se”; vinculação é “lembre-se desta apresentação”; criptografia é “a partir de agora, conversem em sigilo”.

Two columns labelled "Central" and "Peripheral". A dashed line near the top labelled "BLE connection open (unencrypted)". Below it, three arrows: "pairing request" from central to peripheral, "key exchange" both directions, "pairing complete" forward. A second dashed line below labelled "link encrypted". Two thick bidirectional arrows carry "encrypted GATT traffic". An optional "store keys to flash" box on the side, labelled "bonding".

O fluxo de pareamento sobre uma conexão BLE aberta. Uma vez concluída a troca de chaves, a camada de enlace criptografa cada pacote subsequente. A vinculação é a etapa extra de gravar as chaves no flash.

11.13.2. LE Secure Connections

A troca de chaves moderna usada pelo BLE é a LE Secure Connections, construída sobre o Elliptic Curve Diffie-Hellman. Ambos os lados geram um par de chaves temporário, trocam as metades públicas e combinam o resultado com suas próprias chaves privadas para chegar ao mesmo segredo compartilhado – um segredo que um bisbilhoteiro não consegue calcular nem mesmo com um registro completo da troca.

O método mais antigo, LE Legacy, é menos seguro (um bisbilhoteiro com a troca completa geralmente consegue recuperar a chave) e existe apenas para compatibilidade retroativa com periféricos antigos. O padrão do aioble é o método moderno (le_secure=True); mantenha-o.

11.13.3. Iniciando o pareamento

Uma central pareia chamando aioble.DeviceConnection.pair() em uma conexão já aberta:

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

Após o retorno de pair, os atributos encrypted, authenticated, bonded e key_size da conexão refletem o que foi negociado.

Os quatro argumentos nomeados mais úteis:

  • bond=True – salva as chaves resultantes no flash para que a próxima conexão entre os mesmos dois dispositivos pule o handshake de pareamento. Padrão True.

  • le_secure=True – usa LE Secure Connections. Padrão True. Deixe ativado.

  • mitm=False – define se deve exigir proteção contra man-in-the-middle. Isso requer um canal fora de banda (um código numérico exibido em um lado e confirmado no outro, uma senha de acesso digitada, …) para que o usuário possa verificar que os dois dispositivos no handshake de pareamento são realmente os que ele imagina. O padrão é False (sem proteção MITM – um bisbilhoteiro passivo não consegue ler o enlace, mas um atacante redirecionando ativamente as conexões poderia se parear). Defina como True para qualquer coisa sensível, mas esteja ciente de que isso exige que o periférico realmente suporte uma capacidade de IO.

  • io=3 – a capacidade de IO que o dispositivo declara. A especificação Bluetooth define cinco: 0 apenas display, 1 display + sim/não, 2 apenas teclado, 3 sem entrada e sem saída, 4 teclado + display. Uma câmera sem interface de usuário normalmente reporta 3; se a própria câmera tiver um display, a aplicação poderia exibir a confirmação numérica e usar 1. A combinação das capacidades de IO dos dois lados decide se a proteção MITM real é alcançável.

Periféricos não chamam pair por conta própria – eles respondem ao que a central iniciar. Se a criptografia é exigida para uma determinada característica é uma propriedade de como ela é declarada na base de dados GATT; os bits de acesso que exigem criptografia fazem parte da API de baixo nível bluetooth e não estão atualmente expostos pelo construtor de característica do aioble.

11.13.4. Vinculação – e onde as chaves residem

Quando bond=True, o aioble grava as chaves em um arquivo JSON no sistema de arquivos local. O nome de arquivo padrão é ble_secrets.json, gravado em relação ao diretório de trabalho atual. Em uma cam recém-inicializada, o _boot.py já escolheu esse diretório: /sdcard quando um cartão está montado, /flash caso contrário – então o arquivo fica em /sdcard/ble_secrets.json ou /flash/ble_secrets.json. O arquivo contém as entradas necessárias para recriptografar o enlace na próxima vez que o par vinculado se reconectar, incluindo o endereço de identidade do par.

Uma assimetria a ter em mente: o salvamento acontece automaticamente conforme as chaves mudam, mas o carregamento do arquivo na próxima inicialização não. Chame aioble.security.load_secrets() uma vez na inicialização (antes de qualquer pareamento ou anúncio) para que pares previamente vinculados sejam reconhecidos:

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

Depois disso, na próxima vez que um par vinculado aparecer, o aioble reutiliza as chaves armazenadas e o enlace passa a ser criptografado sem nenhum handshake adicional.

Duas consequências práticas de armazenar chaves no flash:

  • Esquecer um dispositivo. Exclua o ble_secrets.json (ou remova a entrada relevante) para esquecer todos os pares vinculados e, então, refaça o pareamento do zero.

  • Acesso físico vaza chaves. Qualquer pessoa com acesso ao sistema de arquivos da câmera pode ler o JSON. Esse é o mesmo tipo de restrição que surgiu no lado da rede com as chaves TLS (Operações: chaves, expiração e solução de problemas): use chaves por dispositivo, trate qualquer chave armazenada como recuperável e confie na capacidade de revogar (aqui, removendo a vinculação no lado da central) em vez de no segredo da chave permanecer secreto.

11.13.5. O que a criptografia garante – e o que não garante

Um enlace pareie-e-depois-criptografe oferece, em ordem de robustez:

  • Confidencialidade. Sempre. Um bisbilhoteiro não consegue ler os bytes.

  • Integridade. Sempre. Pacotes modificados falham na verificação de criptografia autenticada da camada de enlace e são descartados.

  • Autenticação. Somente com mitm=True e um IO capaz. Sem isso, um man-in-the-middle que tenha interceptado a troca de pareamento original poderia ter se inserido; sem proteção MITM não há como os dois lados saberem.

Para a maioria dos casos de uso de câmera – um celular pareando com a câmera uma vez e depois se conectando novamente – mitm=False geralmente é suficiente, pois o pareamento original acontece em um ambiente controlado (o usuário segura ambos os dispositivos na mesma sala). Para aplicações em que um dispositivo pareado possa encontrar a câmera pela primeira vez a longa distância ou por meio de um intermediário não confiável, o MITM é a configuração correta.

11.13.6. Quando o pareamento é a resposta errada

O pareamento tem um custo real: alguns segundos de troca na primeira conexão, uso persistente do flash para cada dispositivo vinculado e o procedimento de recuperação de “esquecer a vinculação” se algo der errado. Para dados genuinamente públicos – leituras de sensores ambientais publicadas como um beacon, uma placa exibindo seu nome, qualquer coisa que não mude o mundo ao ser lida ou escrita – a resposta correta é não criptografar de modo algum, e deixar que qualquer scanner próximo leia os valores.

Para todo o resto, connection.pair(bond=True) na central é o acréscimo de uma linha que transforma o enlace de um canal público em um canal privado.