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”.

Duas colunas rotuladas "Central" e "Peripheral". Uma linha tracejada perto do topo rotulada "BLE connection open (unencrypted)". Abaixo dela, três setas: "pairing request" da central para o periférico, "key exchange" em ambas as direções, "pairing complete" para frente. Uma segunda linha tracejada abaixo rotulada "link encrypted". Duas setas bidirecionais grossas carregam "encrypted GATT traffic". Uma caixa opcional "store keys to flash" à lateral, rotulada "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.