11.13. Emparelhamento e vinculação

Tudo o que foi abordado até aqui transmite bytes pelo rádio em claro. Qualquer pessoa com um portátil com suporte BLE na mesma sala pode escutar os canais de publicidade, seguir a sequência de saltos de uma ligação aberta e ler cada leitura, escrita e notificação que passa. Para a maioria dos dados de sensor públicos (nível de bateria, temperatura ambiente) isso é aceitável. Para qualquer coisa que os dois endpoints queiram manter privada – um registo de controlo que activa um relé, uma palavra-passe, uma medição que não deve ser amplamente difundida – a ligação precisa de ser encriptada e, idealmente, a câmara precisa de saber com quem está a falar.

O BLE fornece ambos através de emparelhamento e vinculação.

11.13.1. Emparelhamento, vinculação, encriptação

Três conceitos intimamente relacionados:

  • Encriptação é o objectivo final. Uma vez encriptada a ligação, cada pacote nos canais de dados só é decifrável pelos dois endpoints; um intruso vê apenas ruído.

  • Emparelhamento é o procedimento que os dois endpoints executam para chegarem a acordo sobre as chaves que a encriptação utiliza. É uma troca única que produz material de chave partilhada que a camada de ligação insere no seu motor de encriptação.

  • Vinculação é a opção de persistir as chaves em armazenamento não volátil após o emparelhamento terminar, para que a próxima ligação entre os mesmos dois dispositivos salte o emparelhamento e vá directamente para a encriptação.

Em linguagem simples: emparelhamento é «apresentem-se»; vinculação é «lembrem-se desta apresentação»; encriptação é «falem em privado a partir de agora».

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 emparelhamento sobre uma ligação BLE aberta. Assim que a troca de chaves termina, a camada de ligação encripta todos os pacotes subsequentes. A vinculação é o passo extra de escrever as chaves em flash.

11.13.2. Ligações Seguras LE

A troca de chaves moderna utilizada pelo BLE é Ligações Seguras LE, baseada em Curvas Elípticas Diffie-Hellman. Ambos os lados geram um par de chaves temporário, trocam as metades públicas e combinam o resultado com as suas próprias chaves privadas para chegarem ao mesmo segredo partilhado – um segredo que um intruso não consegue calcular mesmo com um registo completo da troca.

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

11.13.3. Iniciar o emparelhamento

Uma central emparelha chamando aioble.DeviceConnection.pair() numa ligaçã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 na ligação reflectem o que foi negociado.

Os quatro argumentos de palavra-chave mais úteis:

  • bond=True – guardar as chaves resultantes em flash para que a próxima ligação entre os mesmos dois dispositivos salte o handshake de emparelhamento. Predefinição True.

  • le_secure=True – utilizar Ligações Seguras LE. Predefinição True. Mantenha activo.

  • mitm=False – se deve exigir protecção contra man-in-the-middle. Isto requer um canal fora de banda (um código numérico exibido num lado e confirmado no outro, uma palavra-passe digitada, …) para que o utilizador possa verificar que os dois dispositivos no handshake de emparelhamento são realmente os que pensa. A predefinição é False (sem protecção MITM – um intruso passivo não consegue ler a ligação, mas um atacante que redireccionasse activamente as ligações poderia emparelhar-se). Defina para True em qualquer coisa sensível, mas tenha em conta que requer que o periférico suporte realmente uma capacidade de IO.

  • io=3 – a capacidade de IO que o dispositivo declara. A especificação Bluetooth define cinco: 0 apenas ecrã, 1 ecrã + sim/não, 2 apenas teclado, 3 sem entrada sem saída, 4 teclado + ecrã. Uma câmara sem interface de utilizador normalmente reporta 3; se a câmara tiver um ecrã, 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 protecção MITM real é alcançável.

Os periféricos não chamam pair eles próprios – respondem ao que a central iniciar. Se a encriptação é obrigatória para uma determinada característica é uma propriedade de como está declarada na base de dados GATT; os bits de acesso que requerem encriptação fazem parte da API de baixo nível bluetooth e não estão actualmente expostos através do construtor de características do aioble.

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

Quando bond=True, o aioble escreve as chaves num ficheiro JSON no sistema de ficheiros local. O nome de ficheiro predefinido é ble_secrets.json, escrito relativamente ao directório de trabalho actual. Numa câmara recém-iniciada, o _boot.py já escolheu esse directório: /sdcard quando um cartão está montado, /flash caso contrário – portanto o ficheiro fica em /sdcard/ble_secrets.json ou /flash/ble_secrets.json. O ficheiro contém as entradas necessárias para re-encriptar a ligação na próxima vez que o par vinculado se reconectar, incluindo o endereço de identidade do par.

Uma assimetria a ter em conta: guardar acontece automaticamente quando as chaves mudam, mas carregar o ficheiro no próximo arranque não. Chame aioble.security.load_secrets() uma vez na inicialização (antes de qualquer emparelhamento ou publicidade) para que os 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 a ligação fica encriptada sem mais nenhum handshake.

Duas consequências práticas de guardar chaves em flash:

  • Esquecer um dispositivo. Elimine ble_secrets.json (ou remova a entrada relevante) para esquecer todos os pares vinculados, e depois emparelhe novamente de raiz.

  • O acesso físico expõe as chaves. Qualquer pessoa com acesso ao sistema de ficheiros da câmara pode ler o JSON. Esta é o mesmo tipo de restrição que surgiu no lado da rede com chaves TLS (Operações: chaves, expiração e resoluçã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 central) em vez de confiar em que a chave permaneça secreta.

11.13.5. O que a encriptação garante – e o que não garante

Uma ligação com emparelhamento seguido de encriptação fornece, por ordem de robustez:

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

  • Integridade. Sempre. Os pacotes modificados falham a verificação de encriptação autenticada da camada de ligação e são descartados.

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

Para a maioria dos casos de utilização de câmara – um telemóvel a emparelhar com a câmara uma vez, e depois a conectar novamente mais tarde – mitm=False é geralmente suficiente, porque o emparelhamento original acontece num ambiente controlado (o utilizador segura ambos os dispositivos na mesma sala). Para aplicações em que um dispositivo emparelhado pode encontrar pela primeira vez a câmara a longa distância ou através de um intermediário não confiável, o MITM é a configuração correcta.

11.13.6. Quando o emparelhamento é a resposta errada

O emparelhamento tem um custo real: alguns segundos de troca na primeira ligação, uso persistente de flash para cada dispositivo vinculado, e a história de recuperação de «esquecer a vinculação» se algo correr mal. Para dados genuinamente públicos – leituras de sensor ambiente publicadas como beacon, uma placa a exibir o seu nome, qualquer coisa que não muda o mundo por ser lida ou escrita – a resposta certa é não encriptar de todo, e deixar qualquer scanner próximo ler os valores.

Para todo o resto, connection.pair(bond=True) na central é a adição de uma linha que transforma a ligação de um canal público num canal privado.