Você está na página 1de 115

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

12 Protegendo as comunicações de IoT


Este capítulocapas

Protegendo as comunicações IoT com o Datagram TLS


Escolhendo algoritmos criptográficos apropriados para dispositivos restritos
Implementando segurança de ponta a ponta para APIs de IoT
Distribuição e gerenciamento de chaves de dispositivo

Até agora, todas as APIs que você viu foram executadas em servidores nos confins
seguros de um datacenter ou sala de servidores. É fácil subestimar a segurança fí-
sica do hardware da API, porque o datacenter é um ambiente seguro com acesso
restrito e fechaduras decentes nas portas. Freqüentemente, apenas funcionários
especialmente controlados têm permissão para entrar na sala do servidor para se
aproximar do hardware. Tradicionalmente, até mesmo os clientes de uma API po-
deriam ser considerados razoavelmente seguros porque eram PCs instalados em
um ambiente de escritório. Isso mudou rapidamente quando os primeiros laptops
e depois os smartphones levaram os clientes de API para fora do escritóriomeio
Ambiente. A internet das coisas (IoT) amplia ainda mais a gama de ambientes, es-
pecialmente em ambientes industriais ou agrícolas, onde os dispositivos podem
ser implantados em ambientes remotos com pouca proteção física ou monitora-
mento. Esses dispositivos IoT conversam com APIs em serviços de mensagens
para transmitir dados do sensor para a nuvem e fornecem APIs próprias para per-
mitir que ações físicas sejam realizadas, como ajustar máquinas em uma estação
de tratamento de água ou desligar as luzes em sua casa ou escritório. Neste capí-
tulo, você verá como proteger as comunicações dos dispositivos IoT ao conversar
entre si e com APIs na nuvem. No capítulo 13, discutiremos como proteger APIs
fornecidas pelos próprios dispositivos.

DEFINIÇÃO A internet das coisas (IoT) é a tendência dos dispositivos serem co-
nectados à internet para facilitar o gerenciamento e a comunicação. IoT do consu-
midorrefere-se a dispositivos pessoais em casa conectados à internet, como uma
geladeira que automaticamente pede mais cerveja quando você está acabando. As
técnicas de IoT também são aplicadas na indústria sob o nomeIoT industrial(IIoT).

12.1 Segurança da camada de transporte

DentroEm um ambiente de API tradicional, proteger as comunicações entre um


cliente e um servidor é quase sempre baseado em TLS. A conexão TLS entre as
duas partes provavelmente será de ponta a ponta (ou quase o suficiente) e usará
algoritmos de criptografia e autenticação fortes. Por exemplo, um cliente que faz
uma solicitação para uma API REST pode fazer uma conexão HTTPS diretamente
para essa API e, em seguida, assumir que a conexão é segura. Mesmo quando a co-
nexão passa por um ou mais proxies, eles normalmente apenas configuram a co-
nexão e copiam os bytes criptografados de um soquete para outro. No mundo da
IoT, as coisas são mais complicadas por vários motivos:

O dispositivo IoT pode ser limitado, reduzindo sua capacidade de executar a


criptografia de chave pública usada no TLS. Por exemplo, o dispositivo pode ter
capacidade de CPU e memória limitadas ou pode estar operando apenas com
energia da bateria que precisa ser conservada.
Para eficiência, os dispositivos geralmente usam formatos binários compactos e
redes de baixo nível baseadas em UDP, em vez de protocolos baseados em TCP
de alto nível, como HTTP e TLS.
Uma variedade de protocolos pode ser usada para transmitir uma única mensa-
gem de um dispositivo ao seu destino, desde protocolos sem fio de curto al-
cance, comoBluetooth de baixa energia(BLE) ou Zigbee, para protocolos de
mensagens comoMQTT ou XMPP. Dispositivos de gateway podem traduzir men-
sagens de um protocolo para outro, conforme mostrado na Figura 12.1, mas
precisam descriptografar as mensagens do protocolo para fazer isso. Isso evita
que uma conexão TLS ponta a ponta simples seja usada.
Alguns algoritmos criptográficos comumente usados ​são difíceis de implemen-
tar com segurança ou eficiência em dispositivos devido a restrições de hard-
ware ou novas ameaças de invasores físicos que são menos aplicáveis ​a APIs do
lado do servidor.
DEFINIÇÃO Um dispositivo restritoreduziu significativamente a potência da
CPU, a memória, a conectividade ou a disponibilidade de energia em comparação
com um servidor ou uma máquina cliente de API tradicional. Por exemplo, a me-
mória disponível para um dispositivo pode ser medida em kilobytes em compara-
ção com os gigabytes geralmente disponíveis para a maioria dos servidores e até
smartphones. A RFC 7228 ( https://tools.ietf.org/html/rfc7228 ) descreve formas co-
muns de restrição de dispositivos.

 
Figura 12.1 Mensagens de dispositivos IoT geralmente são traduzidas de um pro-
tocolo para outro. O dispositivo original pode usar redes sem fio de baixa potên-
cia, como Bluetooth Low-Energy (BLE), para se comunicar com um gateway local
que retransmite mensagens usando protocolos de aplicativos, como MQTT ou
HTTP.

Nesta seção, você aprenderá como proteger as comunicações IoT na camada de


transporte e a escolha apropriada de algoritmos para dispositivos restritos.
DICA Existem várias bibliotecas TLS explicitamente projetadas para aplicativos
IoT, comombedTLS da ARM ( https://tls.mbed.org ),WolfSSL (
https://www.wolfssl.com ) e BearSSL ( https://bearssl.org ).

12.1.1 Datagrama TLS

TLS é projetado para proteger o tráfego enviado por TCP (Transmission Control
Protocol), que é um protocolo orientado a fluxo confiável. A maioria dos protoco-
los de aplicativos de uso comum, como HTTP, LDAP ou SMTP (e-mail), todos usam
TCP e, portanto, podem usar TLS para proteger a conexão. Mas uma implementa-
ção de TCP tem algumas desvantagens quando usada em dispositivos IoT restritos,
como os seguintes:

Uma implementação TCP é complexa e requer muito código para ser imple-
mentada corretamente. Este código ocupa um espaço precioso no dispositivo,
reduzindo a quantidade de código disponível para implementar outras funções.
Os recursos de confiabilidade do TCP exigem que o dispositivo de envio arma-
zene mensagens em buffer até que sejam confirmadas pelo receptor, o que au-
menta os requisitos de armazenamento. Muitos sensores IoT produzem fluxos
contínuos de dados em tempo real, para os quais não faz sentido retransmitir
mensagens perdidas porque dados mais recentes já as terão substituído.
Um cabeçalho TCP padrão tem pelo menos 16 bytes de comprimento, o que
pode adicionar bastante sobrecarga às mensagens curtas.
O TCP não pode usar recursos como entrega multicastque permitem que uma
única mensagem seja enviada para vários dispositivos ao mesmo tempo. Multi-
cast pode ser muito mais eficiente do que enviar mensagens para cada disposi-
tivo individualmente.
Os dispositivos IoT geralmente se colocam no modo de suspensão para preser-
var a energia da bateria quando não estão em uso. Isso faz com que as cone-
xões TCP sejam encerradas e requer que um handshake TCP caro seja execu-
tado para restabelecer a conexão quando o dispositivo for ativado. Como alter-
nativa, o dispositivo pode enviar periodicamente mensagens de manutenção de
atividade para manter a conexão aberta, ao custo de maior uso de bateria e lar-
gura de banda.

Muitos protocolos usados ​na IoT, em vez disso, optam por construir sobre o proto-
colo de datagrama do usuário de nível inferior(UDP), que é muito mais simples
que o TCP, mas fornece apenas entrega de mensagens sem conexão e não confiá-
vel. Por exemplo, o Protocolo de Aplicação Restrita(CoAP), fornece uma alterna-
tiva ao HTTP para dispositivos restritos e é baseado em UDP. Para proteger esses
protocolos, foi desenvolvida uma variação do TLS conhecida como Datagram TLS
(DTLS). 1

DEFINIÇÃO Datagram Transport Layer Security (DTLS) é uma versão do TLS


projetada para funcionar com protocolos baseados em UDP sem conexão, em vez
de protocolos baseados em TCP. Ele fornece as mesmas proteções que o TLS, ex-
ceto que os pacotes podem ser reordenados ou repetidos sem detecção.

Versões DTLS recentes correspondem a versões TLS; por exemplo, DTLS 1.2 cor-
responde a TLS 1.2 e oferece suporte a conjuntos de cifras e extensões semelhan-
tes. No momento em que escrevo, o DTLS 1.3 está sendo finalizado, o que corres-
ponde ao recém-padronizado TLS 1.3.

QUIC

Um meio-termo entre TCP e UDP é fornecido porO protocolo QUIC do Google (co-
nexões UDP rápidas com a Internet; https://en.wikipedia.org/wiki/QUIC ), que for-
mará a base da próxima versão do HTTP: HTTP/3. Camadas QUIC em cima de UDP,
mas fornece muitos dos mesmos recursos de controle de congestionamento e con-
fiabilidade que o TCP. Um recurso importante do QUIC é que ele integra o TLS 1.3
diretamente no protocolo de transporte, reduzindo a sobrecarga do handshake
TLS e garantindo que os recursos do protocolo de baixo nível também se benefi-
ciem das proteções de segurança. O Google já implantou o QUIC em produção e
cerca de 7% do tráfego da Internet agora usa o protocolo.

O QUIC foi originalmente projetado para acelerar o tráfego HTTPS do servidor da


web tradicional do Google, portanto, o tamanho compacto do código não era o ob-
jetivo principal. No entanto, o protocolo pode oferecer vantagens significativas
para dispositivos IoT em termos de uso de rede reduzido e conexões de baixa la-
tência. Experimentos anteriores, como uma análise da Universidade de Santa
Clara ( http://mng.bz/X0WG ) e outra da NetApp ( https://eggert.org/papers/2020-
ndss-quic-iot.pdf ) sugerem que o QUIC pode fornecem economias significativas
em um contexto de IoT, mas o protocolo ainda não foi publicado como um padrão
final. Embora ainda não tenha alcançado ampla adoção em aplicativos de IoT, é
provável que o QUIC se torne cada vez mais importante nos próximos anos.
Embora o Java suporte DTLS, ele o faz apenas na forma da SSLEngine classe de
baixo nível, que implementa a máquina de estado do protocolo bruto. Não há
equivalente à SSLSocket classe de alto nívelque é usado por TLS normal (base-
ado em TCP), então você deve fazer parte do trabalho sozinho. As bibliotecas para
protocolos de alto nível, como CoAP, cuidarão muito disso para você, mas como há
muitos protocolos usados ​em aplicativos IoT, nas próximas seções você aprenderá
como adicionar manualmente DTLS a um protocolo baseado em UDP.

NOTA Os exemplos de código neste capítulo continuam a usar Java para consis-
tência. Embora Java seja uma escolha popular em dispositivos e gateways IoT
mais capazes, a programação de dispositivos restritos é mais frequentemente rea-
lizada em C ou outra linguagem com suporte a dispositivos de baixo nível. O con-
selho sobre configuração segura de DTLS e outros protocolos neste capítulo é apli-
cável a todos os idiomas e bibliotecas DTLS. Pule para a seção 12.1.2 se você não
estiver usando Java.
IMPLEMENTANDO UM CLIENTE DTLS

Parainiciar um handshake DTLS em Java, você primeiro cria um SSLContext ob-


jeto, que indica como autenticar a conexão. Para uma conexão de cliente, você ini-
cializa o contexto exatamente como fez na seção 7.4.2 ao proteger a conexão com
um servidor de autorização OAuth2, conforme mostrado na listagem 12.1. Pri-
meiro, obtenha um SSLContext para DTLS chamando
SSLContext.getInstance("DTLS") . Isso retornará um contexto que permite
conexões DTLS com qualquer versão de protocolo compatível (DTLS 1.0 e DTLS
1.2 em Java 11). Você pode então carregar os certificados de autoridades de certifi-
cação confiáveis ​(CAs) e use isso para inicializar um TrustManagerFactory , as-
sim como você fez nos capítulos anteriores. O TrustManagerFactory será usado
pelo Java para determinar se o certificado do servidor é confiável. Nesse caso,
você pode usar o arquivo as.example.com.ca.p12 que você criou no capítulo 7 con-
tendo o certificado mkcert CA. O PKIX (Infraestrutura de chave pública com
X.509) o algoritmo de fábrica do gerenciador de confiança deve ser usado. Final-
mente, você pode inicializar o SSLContext objeto, passando os gerenciadores de
confiança de fábrica, usando o SSLContext.init() método. Este método recebe
três argumentos:

Uma matriz de KeyManager objetos, que são usados ​ao executar autenticação
de certificado de cliente (abordado no capítulo 11). Como este exemplo não usa
certificados de cliente, você pode deixar nulo.
A matriz de TrustManager objetos obtidos do TrustManagerFactory .
Um opcional SecureRandom objeto a ser usado ao gerar material de chave alea-
tório e outros dados durante o handshake TLS. Você pode deixar isso null na
maioria dos casos para permitir que o Java escolha um padrão sensato.

Crie um novo arquivo chamado DtlsClient.java na pasta


src/main/com/manning/apisecurityinaction e digite o conteúdo da listagem.

OBSERVAÇÃO Os exemplos nesta seção pressupõem que você esteja familiari-


zado com a programação de rede UDP em Java. Veja http://mng.bz/yr4G para uma
introdução.

Listagem 12.1 O cliente SSLContext

pacote com.manning.apisecurityinaction;

importar javax.net.ssl.*;
importar java.io.FileInputStream;
importar java.nio.file.*;
importar java.security.KeyStore;
importar org.slf4j.*;
importar estático java.nio.charset.StandardCharsets.UTF_8;

public class DtlsClient {


logger logger final estático privado =
LoggerFactory.getLogger(DtlsClient.class);
private static SSLContext getClientContext() lança exceção {
var sslContext = SSLContext.getInstance("DTLS"); ❶

var trustStore = KeyStore.getInstance("PKCS12"); ❷


trustStore.load(new FileInputStream("as.example.com.ca.p12"), ❷
"changeit".toCharArray()); ❷

var trustManagerFactory = TrustManagerFactory.getInstance( ❸


"PKIX"); ❸
trustManagerFactory.init(trustStore); ❸

sslContext.init(nulo, trustManagerFactory.getTrustManagers(), ❹
nulo); ❹
return sslContext;
}
}

❶ Crie um SSLContext para DTLS.

❷ Carregue os certificados de CA confiáveis ​como um keystore.

❸ Inicialize um TrustManagerFactory com os certificados confiáveis.

❹ Inicialize o SSLContext com o gerenciador de confiança.


Depois de criar o SSLContext , você pode usar o createEngine() métodonele
para criar um novo SSLEngine objeto. Esta é a implementação do protocolo de
baixo nível que normalmente é escondida por bibliotecas de protocolo de alto ní-
vel como a HttpClient classevocê usou no capítulo 7. Para um cliente, você deve
passar o endereço e a porta do servidor para o método ao criar o mecanismo e
configurar o mecanismo para executar o lado do cliente do handshake DTLS
chamando setUseClientMode(true ) , conforme mostrado no exemplo a
seguir.

NOTA Você não precisa digitar neste exemplo (e os outros SSLEngine exemplos),
porque forneci uma classe wrapper que oculta parte dessa complexidade e de-
monstra o uso correto do SSLEngine . Veja http://mng.bz/Mo27 . Você usará essa
classe no exemplo de cliente e servidor em breve.

var address = InetAddress.getByName("localhost");


motor var = sslContext.createEngine(endereço, 54321);
mecanismo.setUseClientMode(true);

Em seguida, você deve alocar buffers para enviar e receber pacotes de rede e para
manter os dados do aplicativo. o SSLSession associado a um mecanismo tem mé-
todos que fornecem dicas para o tamanho correto desses buffers, que você pode
consultar para garantir a alocação de espaço suficiente, conforme mostrado no
código de exemplo a seguir (novamente, você não precisa digitar isso):
var sessão = mecanismo.getSession(); ❶
var receiveBuffer = ❷
ByteBuffer.allocate(session.getPacketBufferSize()); ❷
var sendBuffer = ❷
ByteBuffer.allocate(session.getPacketBufferSize()); ❷
var applicationData = ❷
ByteBuffer.allocate(session.getApplicationBufferSize()); ❷

❶ Recupere a SSLSession do mecanismo.

❷ Use as dicas de sessão para dimensionar corretamente os buffers de dados.

Esses tamanhos de buffer iniciais são dicas e o mecanismo informará se eles pre-
cisam ser redimensionados, como você verá em breve. Os dados são movidos en-
tre os buffers usando as duas chamadas de método a seguir, também ilustradas
na figura 12.2:

sslEngine.wrap(appData, sendBuf ) faz com que o SSLEngine consuma


todos os dados do aplicativo em espera do appData buffere gravar um ou mais
pacotes DTLS na rede sendBuf que podem ser enviados para a outra parte.
sslEngine.unwrap(recvBuf, appData ) instrui a SSLEngine consumir
pacotes DTLS recebidos do recvBuf e envie quaisquer dados de aplicativo des-
criptografados para o appData buffer.
Figura 12.2 O SSLEngine usa dois métodos para mover dados entre os buffers de
aplicativo e rede: wrap() consome dados de aplicativo para enviar e grava paco-
tes DTLS no buffer de envio, enquanto unwrap() consome dados do buffer de re-
cebimento e grava dados de aplicativo não criptografados de volta no buffer de
aplicativo.

Para iniciar o handshake DTLS, chame sslEngine.beginHandshake () . Em vez


de bloquear até que o handshake seja concluído, isso configura o mecanismo para
esperar o início de um novo handshake DTLS. Seu código de aplicativo é responsá-
vel por pesquisar o mecanismo para determinar a próxima ação a ser executada e
enviar ou receber mensagens UDP conforme indicado pelo mecanismo.

Para pesquisar o mecanismo, você chama o


sslEngine.getHandshakeStatus() método, que retorna um dos seguintes va-
lores, conforme mostrado na figura 12.3:

NEED_UNWRAP indica que o mecanismo está esperando para receber uma nova
mensagem do servidor. O código do seu aplicativo deve chamar o
receive() métodoem seu UDP DatagramChannel para receber um pacote do
servidor e, em seguida, chamar o SSLEngine.unwrap() métodopassando os
dados que recebeu.
NEED_UNWRAP_AGAIN indica que há entrada restante que ainda precisa ser
processada. Você deve chamar imediatamente o unwrap() métodonovamente
com um buffer de entrada vazio para processar a mensagem. Isso pode aconte-
cer se vários registros DTLS chegarem em um único pacote UDP.
NEED_WRAP indica que o mecanismo precisa enviar uma mensagem ao servi-
dor. A aplicação deve chamar o wrap() métodocom um buffer de saída que
será preenchido com a nova mensagem DTLS, que seu aplicativo deve enviar
para o servidor.
NEED_TASK indica que o mecanismo precisa executar algum processamento
(potencialmente caro), como executar operações criptográficas. Você pode cha-
mar o getDelegatedTask() métodono mecanismo para obter um ou mais
Runnable objetos para executar. O método retorna nulo quando não há mais
tarefas a serem executadas. Você pode executá-los imediatamente ou pode exe-
cutá-los usando um pool de threads em segundo plano se não quiser bloquear
seu thread principal enquanto eles são concluídos.
FINISHED indica que o aperto de mão acabou de terminar,
enquanto NOT_HANDSHAKING indica que nenhum handshake está em anda-
mento (já terminou ou não foi iniciado). o FINISHED estadoé gerado apenas
uma vez pela última chamada para wrap() ou unwrap () e, em seguida, o me-
canismo produzirá um NOT_HANDSHAKING status.
Figura 12.3 A máquina de estado de handshake SSLEngine envolve quatro estados
principais. Nos estados NEED_UNWRAP e NEED_UNWRAP_AGAIN , você deve usar a
unwrap() chamada para fornecer os dados de rede recebidos. O NEED_WRAP es-
tado indica que novos pacotes DTLS devem ser recuperados com a wrap() cha-
mada e então enviados para a outra parte. O NEED_TASK estado é usado quando o
mecanismo precisa executar funções criptográficas caras.

A Listagem 12.2 mostra o esboço de como o loop básico para executar um


handshake DTLS SSLEngine é executado com base nos códigos de status do
handshake.

NOTA Esta listagem foi simplificada em comparação com a implementação no


repositório GitHub que acompanha o livro, mas a lógica central está correta.

Listagem 12.2 Loop de handshake do SSLEngine

engine.beginHandshake(); ❶

var handshakeStatus = engine.getHandshakeStatus(); ❷


while (handshakeStatus != HandshakeStatus.FINISHED) { ❸
resultado SSLEngineResult;
switch (status de aperto de mão) {
case NEED_UNWRAP: ❹
if (recvBuf.position() == 0) { ❹
channel.receive(recvBuf); ❹
} ❹
case NEED_UNWRAP_AGAIN: ❺
resultado = engine.unwrap(recvBuf.flip(), appData); ❻
recvBuf.compact(); ❻
checkStatus(result.getStatus()); ❼
handshakeStatus = result.getHandshakeStatus(); ❼
parar;
caso NEED_WRAP:
resultado = engine.wrap(appData.flip(), sendBuf); ❽
appData.compact(); ❽
canal.write(sendBuf.flip()); ❽
sendBuf.compact(); ❽
checkStatus(result.getStatus()); ❽
handshakeStatus = result.getHandshakeStatus(); ❽
pausa; ❽
caso NEED_TASK:
Tarefa executável; ❾
while ((task = engine.getDelegatedTask()) != null) { ❾
task.run(); ❾
} ❾
status = engine.getHandshakeStatus(); ❾
predefinição:
lançar novo IllegalStateException();
}

❶ Acionar um novo handshake DTLS.

❷ Alocar buffers para rede e dados de aplicativos.


❸ Faça um loop até que o aperto de mão termine.

❹ No estado NEED_UNWRAP, você deve aguardar um pacote de rede, caso ainda não
tenha sido recebido.

❺ Deixe a instrução switch passar para o caso NEED_UNWRAP_AGAIN.

❻ Processe todos os pacotes DTLS recebidos chamando engine.unwrap().

❼ Verifique o status do resultado da chamada unwrap() e atualize o estado do


handshake.

❽ No estado NEED_WRAP, chame o método wrap() e envie os pacotes DTLS


resultantes.

❾ Para NEED_TASK, basta executar quaisquer tarefas delegadas ou enviá-las para


um pool de threads.

o wrap () e unwrap () as chamadas retornam um código de status para a opera-


ção, bem como um novo status de handshake, que você deve verificar para garan-
tir que a operação foi concluída corretamente. Os códigos de status possíveis são
mostrados na tabela 12.1. Se precisar redimensionar um buffer, você pode consul-
tar o atual SSLSession para determinar os tamanhos recomendados de aplica-
tivo e buffer de rede e compará-los com a quantidade de espaço restante no buf-
fer. Se o buffer for muito pequeno, você deve alocar um novo buffer e copiar to-
dos os dados existentes para o novo buffer. Em seguida, repita a operação
novamente.

Tabela 12.1 Códigos de status de operação do SSLEngine

código de estado Significado

OK A operação foi completa com sucesso.

BUFFER_UNDERFLOW A operação falhou porque não havia dados de en-


trada suficientes. Verifique se o buffer de entrada
tem espaço restante suficiente. Para uma operação
de desdobramento, você deve receber outro pacote
de rede se esse status ocorrer.

BUFFER_OVERFLOW A operação falhou porque não havia espaço sufici-


ente no buffer de saída. Verifique se o buffer é
grande o suficiente e redimensione-o, se necessário.

CLOSED A outra parte indicou que está encerrando a cone-


xão, portanto, você deve processar todos os pacotes
restantes e fechá-los SSLEngine também.
Usando a DtlsDatagramChannel classedo repositório GitHub que acompanha o
livro, agora você pode implementar um aplicativo de exemplo de cliente DTLS
funcional. A classe de amostra requer que o canal UDP subjacente esteja conecta-
doantes que ocorra o handshake DTLS. Isso restringe o canal a enviar pacotes
para apenas um único host e receber pacotes apenas desse host também. Esta não
é uma limitação do DTLS, mas apenas uma simplificação feita para manter o có-
digo de exemplo curto. Uma consequência dessa decisão é que o servidor que
você desenvolverá na próxima seção só pode lidar com um único cliente por vez e
descartará pacotes de outros clientes. Não é muito mais difícil lidar com clientes
simultâneos, mas você precisa associar um único cliente SSLEngine a cada
cliente.

DEFINIÇÃO Um canal UDP (ou soquete) está conectadoquando está restrito a


apenas enviar ou receber pacotes de um único host. O uso de canais conectados
simplifica a programação e pode ser mais eficiente, mas os pacotes de outros cli-
entes serão descartados silenciosamente. o connect() métodoé usado para co-
nectar um Java DatagramChannel .

A Listagem 12.3 mostra um exemplo de cliente que se conecta a um servidor e en-


tão envia o conteúdo de um arquivo de texto linha por linha. Cada linha é enviada
como um pacote UDP individual e será criptografada usando DTLS. Depois que os
pacotes são enviados, o cliente consulta o SSLSession para imprimir o conjunto
de cifras DTLS que foi usado para a conexão. Abra o arquivo DtlsClient.java criado
anteriormente e inclua o método principal mostrado na listagem. Crie um arquivo
de texto chamado test.txt na pasta raiz do projeto e adicione algum texto de exem-
plo a ele, como versos de Shakespeare, suas citações favoritas ou qualquer coisa
de sua preferência.

OBSERVAÇÃO Você não poderá usar esse cliente até escrever o servidor para
acompanhá-lo na próxima seção.

Listagem 12.3 O cliente DTLS

public static void main(String... args) lança Exception {


try (var channel = new DtlsDatagramChannel(getClientContext()); ❶
var in = Files.newBufferedReader(Paths.get("test.txt"))) { ❷
logger.info("Conectando ao localhost:54321");
canal.connect("localhost", 54321); ❸

Linha de corda;
while ((line = in.readLine()) != null) { ❹
logger.info("Enviando pacote ao servidor: {}", linha); ❹
canal.send(line.getBytes(UTF_8)); ❹
}

logger.info("Todos os pacotes enviados");


logger.info("Conjunto de cifras usado: {}",
channel.getSession().getCipherSuite()); ❺
}
}

❶ Abra o canal DTLS com o cliente SSLContext.

❷ Abra um arquivo de texto para enviar ao servidor.

❸ Conecte-se ao servidor em execução na máquina local e na porta 54321.

❹ Envie as linhas de texto para o servidor.

❺ Imprimir detalhes da conexão DTLS.

Após a conclusão do cliente, ele fechará automaticamente o DtlsDatagramChan-


nel , que acionará o desligamento do SSLEngine objeto associado. Fechar uma
sessão DTLS não é tão simples quanto apenas fechar o canal UDP, porque cada
parte deve enviar uma à outra um alerta de notificação de fechamentomensagem
para sinalizar que a sessão DTLS está sendo encerrada. Em Java, o processo é se-
melhante ao loop de handshake que você viu anteriormente na Listagem 12.2. Pri-
meiro, o cliente deve indicar que não enviará mais nenhum pacote chamando o
closeOutbound() métodono motor. Você deve então chamar o wrap() méto-
dopara permitir que o mecanismo produza a mensagem de alerta de notificação
de fechamento e envie essa mensagem ao servidor, conforme mostrado na Lista-
gem 12.4. Após o envio do alerta, você deve processar as mensagens recebidas até
receber uma notificação de fechamento correspondente do servidor, momento
em que retornará SSLEngine true do isInboundDone() métodoe você pode en-
tão fechar o UDP subjacente DatagramChannel .

Se o outro lado fechar o canal primeiro, então a próxima chamada


para unwrap () retornará um CLOSED status. Nesse caso, você deve inverter a or-
dem das operações: primeiro feche o lado de entrada e processe todas as mensa-
gens recebidas e, em seguida, feche o lado de saída e envie sua própria notifica-
ção de fechamentomensagem.

Listagem 12.4 Manipulando o desligamento

public void close() lança IOException {


sslEngine.closeOutbound(); ❶
sslEngine.wrap(appData.flip(), sendBuf); ❷
appData.compact(); ❷
canal.write(sendBuf.flip()); ❷
sendBuf.compact(); ❷

while (!sslEngine.isInboundDone()) { ❸
channel.receive(recvBuf); ❸
sslEngine.unwrap(recvBuf.flip(), appData); ❸
recvBuf.compact(); ❸
}
sslEngine.closeInbound(); ❹
canal.close(); ❹
}

❶ Indicar que nenhum outro pacote de aplicativo de saída será enviado.

❷ Chame wrap() para gerar a mensagem de notificação de fechamento e enviá-la ao


servidor.

❸ Aguarde até que uma notificação de fechamento seja recebida do servidor.

❹ Indique que o lado de entrada também está pronto e feche o canal UDP.

IMPLEMENTANDO UM SERVIDOR DTLS

Inicializandouma SSLContext para um servidor é semelhante ao cliente, exceto


que neste caso você usa um KeyManagerFactory para fornecer o certificado e a
chave privada do servidor. Como você não está usando autenticação de certifi-
cado de cliente, pode deixar a TrustManager matrizcomo null . A Listagem 12.5
mostra o código para criar um contexto DTLS do lado do servidor. Crie um novo
arquivo denominado DtlsServer.java próximo ao cliente e digite o conteúdo da
listagem.

Listagem 12.5 O servidor SSLContext


pacote com.manning.apisecurityinaction;

importar java.io.FileInputStream;
importar java.nio.ByteBuffer;
importar java.security.KeyStore;
importar javax.net.ssl.*;
importar org.slf4j.*;

importar estático java.nio.charset.StandardCharsets.UTF_8;

public class DtlsServer {


private static SSLContext getServerContext() lança exceção {
var sslContext = SSLContext.getInstance("DTLS"); ❶

var keyStore = KeyStore.getInstance("PKCS12"); ❷


keyStore.load(new FileInputStream("localhost.p12"), ❷
"changeit".toCharArray()); ❷
var keyManager = KeyManagerFactory.getInstance("PKIX"); ❸
keyManager.init(keyStore, "changeit".toCharArray()); ❸

sslContext.init(keyManager.getKeyManagers(), null, null); ❹


return sslContext;
}
}
❶ Crie um DTLS SSLContext novamente.

❷ Carregue o certificado do servidor e a chave privada de um keystore.

❸ Inicialize o KeyManagerFactory com o keystore.

❹ Inicialize o SSLContext com o gerenciador de chaves.

Neste exemplo, o servidor estará rodando em localhost, então use mkcert para
gerar um par de chaves e um certificado assinado, se ainda não tiver um, execu-
tando 2

mkcert -pkcs12 host local

na pasta raiz do projeto. Você pode então implementar o servidor DTLS conforme
mostrado na Listagem 12.6. Assim como no exemplo do cliente, você pode usar a
DtlsDatagramChannel classepara simplificar o aperto de mão. Nos bastidores,
ocorrerá o mesmo processo de aperto de mão, mas a ordem
de wrap () e unwrap () as operações serão diferentes devido aos diferentes pa-
péis desempenhados no aperto de mão. Abra o arquivo DtlsServer.java que você
criou anteriormente e adicione o main método mostrado na listagem.

OBSERVE o DtlsDatagramChannel fornecido no repositório GitHub que acom-


panha o livro conectará automaticamente o DatagramChannel ao primeiro cli-
ente do qual recebe um pacote e descarta pacotes de outros clientes até que esse
cliente se desconecte.

Listagem 12.6 O servidor DTLS

public static void main(String... args) lança Exception {


try (var channel = new DtlsDatagramChannel(getServerContext())) { ❶
channel.bind(54321); ❶
logger.info("Ouvindo na porta 54321");

var buffer = ByteBuffer.allocate(2048); ❷

enquanto (verdadeiro) {
canal.receber(buffer); ❸
buffer.flip();
var data = UTF_8.decode(buffer).toString(); ❹
logger.info("Recebido: {}", dados); ❹
buffer.compact();
}
}
}

❶ Crie o DtlsDatagramChannel e vincule-o à porta 54321.

❷ Alocar um buffer para os dados recebidos do cliente.


❸ Receba pacotes UDP descriptografados do cliente.

❹ Imprima os dados recebidos.

Agora você pode iniciar o servidor executando o seguinte comando:

mvn clean compile exec:java \


-Dexec.mainClass=com.manning.apisecurityinaction.DtlsServer

Isso produzirá muitas linhas de saída à medida que compila e executa o código.
Você verá a seguinte linha de saída assim que o servidor for inicializado e estiver
ouvindo pacotes UDP dos clientes:

[com.manning.apisecurityinaction.DtlsServer.main()] INFO
➥ com.manning.apisecurityinaction.DtlsServer - Escutando na porta
➥ 54321

Agora você pode executar o cliente em outra janela de terminal executando:

mvn clean compile exec:java \


-Dexec.mainClass=com.manning.apisecurityinaction.DtlsClient

DICA Se você quiser ver os detalhes das mensagens do protocolo DTLS sendo en-
viadas entre o cliente e o servidor, adicione o argumento -
Djavax.net.debug=all à linha de comando do Maven. Isso produzirá um regis-
tro detalhado das mensagens de handshake.

O cliente inicializará, conectará ao servidor e enviará todas as linhas de texto do


arquivo de entrada para o servidor, que as receberá e as imprimirá. Após a con-
clusão do cliente, ele imprimirá o conjunto de cifras DTLS usado para que você
possa ver o que foi negociado. Na próxima seção, você verá como a escolha pa-
drão feita pelo Java pode não ser apropriada para aplicações IoT e como escolher
uma opção maisadequado substituição.

OBSERVAÇÃO Este exemplo destina-se a demonstrar o uso de DTLS apenas e


não é um protocolo de rede pronto para produção. Se você separar o cliente e o
servidor em uma rede, é provável que alguns pacotes sejam perdidos. Use um
protocolo de aplicativo de nível superior, como CoAP, se seu aplicativo exigir en-
trega de pacote confiável (ou use TLS normal sobre TCP).

12.1.2 Conjuntos de cifras para dispositivos restritos

DentroNos capítulos anteriores, você seguiu a orientação do Mozilla 3 ao escolher


conjuntos de cifras TLS seguros (lembre-se do capítulo 7 que um conjunto de ci-
fras é uma coleção de algoritmos criptográficos escolhidos para funcionarem bem
juntos). Essa orientação visa proteger os aplicativos de servidor da Web tradicio-
nais e seus clientes, mas esses conjuntos de cifras nem sempre são adequados
para uso de IoT por vários motivos:
O tamanho do código necessário para implementar esses conjuntos com segu-
rança pode ser muito grande e requer muitas primitivas criptográficas. Por
exemplo, o conjunto de cifras ECDHE-RSA-AES256-SHA384 requer a implemen-
tação da Curva Elíptica Diffie-Hellman (ECDH) contrato de chave, assinaturas
RSA, operações de criptografia e descriptografia AES e a função de hash SHA-
384 com HMAC!
As recomendações modernas promovem fortemente o uso de AES no modo
Galois/Counter(GCM), porque isso é extremamente rápido e seguro em chips In-
tel modernos devido à aceleração de hardware. Mas pode ser difícil imple-
mentá-lo com segurança em software em dispositivos restritos e falha catastro-
ficamente se for mal utilizado.
Alguns algoritmos criptográficos, como SHA-512 ou SHA-384, raramente são
acelerados por hardware e são projetados para funcionar bem quando imple-
mentados em software em arquiteturas de 64 bits. Pode haver uma penalidade
de desempenho ao implementar esses algoritmos em arquiteturas de 32 bits,
muito comuns em dispositivos IoT. Em ambientes de baixo consumo de energia,
microcontroladores de 8 bits ainda são comumente usados, o que torna a im-
plementação de tais algoritmos ainda mais desafiadora.
As recomendações modernas concentram-se em conjuntos de cifras que forne-
cem sigilo de encaminhamento, conforme discutido no capítulo 7 (também co-
nhecido como sigilo de encaminhamento perfeito).). Essa é uma propriedade de
segurança muito importante, mas aumenta o custo computacional desses con-
juntos de cifras. Todos os conjuntos de cifras secretas diretas em TLS requerem
a implementação de um algoritmo de assinatura (como RSA) e um algoritmo de
acordo de chave (geralmente, ECDH), o que aumenta o tamanho do código. 4

Reutilização de nonce e AES-GCM em DTLS

O modo de criptografia autenticada simétrica mais popular usado em aplicativos


TLS modernos é baseado em AES em Galois/Counter Mode (GCM). O GCM exige
que cada pacote seja criptografado usando um nonce exclusivo e perde quase
toda a segurança se o mesmo nonce for usado para criptografar dois pacotes dife-
rentes. Quando o GCM foi introduzido pela primeira vez para o TLS 1.2, ele exigia
que um nonce de 8 bytes fosse enviado explicitamente com cada registro. Embora
esse nonce possa ser um contador simples, algumas implementações decidiram
gerá-lo aleatoriamente. Como 8 bytes não é grande o suficiente para gerar aleato-
riamente com segurança, essas implementações foram consideradas suscetíveis à
reutilização acidental de nonce. Para evitar esse problema, o TLS 1.3 introduziu
um novo esquema baseado em nonces implícitos: o nonce para um registro TLS é
derivado do número de sequência que o TLS já acompanha para cada conexão.
Essa foi uma melhoria de segurança significativa porque as implementações de
TLS devem acompanhar com precisão o número de sequência do registro para ga-
rantir a operação adequada do protocolo, portanto, a reutilização acidental de
nonce resultará em uma falha imediata do protocolo (e é mais provável que seja
detectada pelos testes). Você pode ler mais sobre esse desenvolvimento em
https://blog.cloudflare.com/tls-nonce-nse/ .

Devido à natureza não confiável dos protocolos baseados em UDP, o DTLS exige
que os números de sequência de registro sejam explicitamente adicionados a to-
dos os pacotes para que os pacotes retransmitidos ou reordenados possam ser de-
tectados e manipulados. Combinado com o fato de que o DTLS é mais tolerante
com pacotes duplicados, isso torna mais provável a reutilização acidental de bugs
nonce em aplicativos DTLS usando AES GCM. Portanto, você deve preferir conjun-
tos de cifras alternativos ao usar DTLS, como os discutidos nesta seção. Na seção
12.3.3, você aprenderá sobre algoritmos de criptografia autenticados que podem
ser usados ​em seu aplicativo e que são mais robustos contra a reutilização de
nonce.
A Figura 12.4 mostra uma visão geral dos componentes de software e algoritmos
necessários para dar suporte a um conjunto de conjuntos de cifras TLS comu-
mente usados ​para conexões da Web. O TLS suporta uma variedade de algoritmos
de troca de chaves usados ​durante o handshake inicial, cada um dos quais precisa
de diferentes primitivas criptográficas para serem implementadas. Alguns deles
também exigem a implementação de assinaturas digitais, novamente com várias
opções de algoritmos. Alguns algoritmos de assinatura suportam diferentes parâ-
metros de grupo, como curvas elípticas usadas para assinaturas ECDSA, que re-
querem código adicional. Após a conclusão do handshake, há várias opções de
modos de cifra e algoritmos MAC para proteger os dados do aplicativo. A própria
autenticação de certificado X.509 requer código adicional. Isso pode adicionar
uma quantidade significativa de código para incluir em um dispositivo restrito.
Figura 12.4 Uma seção transversal de algoritmos e componentes que devem ser
implementados para oferecer suporte a conexões da Web TLS comuns. Algoritmos
de troca de chaves e assinatura são usados ​durante o handshake inicial e, em se-
guida, modos de cifra e MACs são usados ​para proteger os dados do aplicativo as-
sim que uma sessão é estabelecida. Os certificados X.509 exigem muito código
complexo para análise, validação e verificação de certificados revogados.

Por esses motivos, outros conjuntos de cifras costumam ser populares em aplicati-
vos de IoT. Como uma alternativa para encaminhar conjuntos de cifras secretas,
existem conjuntos de cifras mais antigos baseados em criptografia RSA ou acordo
de chave Diffie-Hellman estático (ou a variante de curva elíptica, ECDH). Infeliz-
mente, ambas as famílias de algoritmos têm deficiências de segurança significati-
vas, não diretamente relacionadas à falta de sigilo de encaminhamento. A troca
de chaves RSA usa um modo antigo de criptografia (conhecido como PKCS#1 ver-
são 1.5) que é muito difícil de implementar com segurança e resultou em muitas
vulnerabilidades nas implementações de TLS. O acordo de chave ECDH estático
tem seus próprios pontos fracos de segurança em potencial, como ataques de
curva inválidosque pode revelar a chave privada de longo prazo do servidor; ra-
ramente é implementado. Por esses motivos, você deve preferir encaminhar con-
juntos de cifras secretas sempre que possível, pois eles fornecem melhor proteção
contra vulnerabilidades criptográficas comuns. O TLS 1.3 removeu completa-
mente esses modos mais antigos devido à sua insegurança.
DEFINIÇÃO Um ataque de curva inválidaé um ataque a chaves criptográficas
de curva elíptica. Um invasor envia à vítima uma chave pública em uma curva
elíptica diferente (mas relacionada) à chave privada da vítima. Se a biblioteca TLS
da vítima não validar a chave pública recebida com cuidado, o resultado pode va-
zar informações sobre a chave privada. Embora conjuntos de cifras ECDH efême-
ros (aqueles com ECDHE no nome) também sejam vulneráveis ​a ataques de curva
inválida, eles são muito mais difíceis de explorar porque cada chave privada é
usada apenas uma vez.

Mesmo se você usar um conjunto de cifras mais antigo, é necessária uma imple-
mentação de DTLS para incluir suporte a assinaturas para validar os certificados
apresentados pelo servidor (e opcionalmente pelo cliente) durante o handshake.
Uma extensão para TLS e DTLS permite que os certificados sejam substituídos por
chaves públicas brutas( https://tools.ietf.org/html/rfc7250 ). Isso permite que a
análise complexa do certificado e o código de validação sejam eliminados, junta-
mente com o suporte para muitos algoritmos de assinatura, resultando em uma
grande redução no tamanho do código. A desvantagem é que as chaves devem ser
distribuídas manualmente para todos os dispositivos, mas isso pode ser uma
abordagem viável em alguns ambientes. Outra alternativa é usar chaves pré-com-
partilhadas, sobre o qual você aprenderá mais na seção 12.2.

DEFINIÇÃO Chaves públicas brutaspode ser usado para eliminar o código com-
plexo necessário para analisar e verificar certificados X.509 e verificar assinatu-
ras nesses certificados. Uma chave pública bruta deve ser distribuída manual-
mente aos dispositivos em um canal seguro (por exemplo, durante a fabricação).

A situação é um pouco melhor quando você observa a criptografia simétrica


usada para proteger os dados do aplicativo após a conclusão do handshake TLS e
da troca de chaves. Existem dois algoritmos criptográficos alternativos que podem
ser usados ​em vez dos modos usuais AES-GCM e AES-CBC:

Conjuntos de cifras baseados em AES no modo CCMfornecem criptografia au-


tenticada usando apenas um circuito de criptografia AES, proporcionando uma
redução no tamanho do código em comparação ao modo CBC e é um pouco
mais robusto em comparação ao GCM. O CCM tornou-se amplamente adotado
em aplicativos e padrões de IoT, mas também possui alguns recursos indesejá-
veis, conforme discutido em uma crítica do modo por Phillip Rogaway e David
Wagner ( https://web.cs.ucdavis.edu/~rogaway/papers /ccm.pdf ).
Os conjuntos de cifras ChaCha20-Poly1305pode ser implementado com segu-
rança em software com relativamente pouco código e bom desempenho em
uma variedade de arquiteturas de CPU. O Google adaptou esses conjuntos de ci-
fras para TLS para fornecer melhor desempenho e segurança em dispositivos
móveis que não possuem aceleração de hardware AES.

DEFINIÇÃO AES-CCM (Contador com CBC-MAC) é um algoritmo de criptografia


autenticado baseado exclusivamente no uso de um circuito de criptografia AES
para todas as operações. Ele usa AES no modo Counter para criptografia e descrip-
tografia e um código de autenticação de mensagem (MAC) baseado em AES no
modo CBC para autenticação. ChaCha20-Poly1305é uma cifra de fluxo e MAC pro-
jetada por Daniel Bernstein que é muito rápida e fácil de implementar em
software.

Ambas as opções têm menos pontos fracos em comparação com os modos AES-
GCM ou AES-CBC mais antigos quando implementados em dispositivos restritos. 5
Se seus dispositivos tiverem suporte de hardware para AES, por exemplo, em um
chip de elemento seguro dedicado, o CCM pode ser uma opção atraente. Na maio-
ria dos outros casos, ChaCha20-Poly1305 pode ser mais fácil de implementar com
segurança. Java tem suporte para conjuntos de cifras ChaCha20-Poly1305 desde
Java 12. Se você tiver o Java 12 instalado, poderá forçar o uso de ChaCha20-
Poly1305 especificando um SSLParameters objeto e passando-o para o
setSSLParameters() métodono SSLEngine . A Listagem 12.7 mostra como con-
figurar os parâmetros para permitir apenas conjuntos de cifras baseados em
ChaCha20-Poly1305. Se você tiver o Java 12, abra o arquivo DtlsClient.java e in-
clua o novo método na classe. Caso contrário, pule este exemplo.

DICA Se você precisar oferecer suporte a servidores ou clientes que executam


versões mais antigas do DTLS, adicione o
TLS_EMPTY_RENEGOTIATION_INFO_SCSV conjunto de cifras de marcador. Caso
contrário, o Java pode não conseguir negociar uma conexão com algum software
mais antigo. Este conjunto de cifras é ativado por padrão, portanto, certifique-se
de reativá-lo ao especificar conjuntos de cifras personalizados.

Listagem 12.7 Forçando o uso de ChaCha20-Poly1305

private static SSLParameters sslParameters() {


var params = DtlsDatagramChannel.defaultSslParameters(); ❶
params.setCipherSuites(new String[] { ❷
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", ❷
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", ❷
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", ❷
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV" ❸
});
parâmetros de retorno;
}

❶ Use os padrões do DtlsDatagramChannel.

❷ Habilitar apenas conjuntos de cifras que usam ChaCha20-Poly1305.

❸ Inclua este conjunto de cifras se precisar oferecer suporte a várias versões do


DTLS.

Depois de adicionar o novo método, você pode atualizar a chamada para o Dtls-
DatagramChannel constructor no mesmo arquivo para passar os parâmetros
customizados:

tente (var canal = new DtlsDatagramChannel(getClientContext(),


sslParameters());

Se você fizer essa alteração e executar novamente o cliente, verá que a conexão
agora usa ChaCha20-Poly1305, desde que o cliente e o servidor estejam usando
Java 12 ou posterior.

AVISO O exemplo na listagem 12.7 usa os parâmetros padrão da classe DtlsData-


gramChannel. Se você criar seus próprios parâmetros, certifique-se de definir um
algoritmo de identificação de terminal. Caso contrário, o Java não validará se o
certificado do servidor corresponde ao nome do host ao qual você se conectou e a
conexão pode ser vulnerável a ataques man-in-the-middle. Você pode definir o al-
goritmo de identificação chamando
"params.setEndpointIdenticationAlgorithm("HTTPS")" .

AES-CCM ainda não é compatível com Java, embora haja trabalho em andamento
para adicionar suporte. A biblioteca Bouncy Castle (
https://www.bouncycastle.org/java.html ) oferece suporte a conjuntos de cifras
CCM com DTLS, mas apenas por meio de uma API diferente e não do padrão SS-
LEngine API. Há um exemplo usando a API Bouncy Castle DTLS com CCM na se-
ção 12.2.1.
Os conjuntos de cifras CCM vêm em duas variações:

Os conjuntos de cifras originais, cujos nomes terminam em _CCM, usam uma


marca de autenticação de 128 bits.
Conjuntos de cifras que terminam em _CCM_8, que usam uma marca de auten-
ticação de 64 bits mais curta. Isso pode ser útil se você precisar salvar cada byte
em mensagens de rede, mas fornece proteções muito mais fracas contra falsifi-
cação e adulteração de mensagens.

Portanto, você deve preferir usar as variantes com uma marca de autenticação de
128 bits, a menos que tenha outras medidas para evitar a falsificação de mensa-
gens, como proteções de rede fortes, e saiba que precisa reduzir as despesas ge-
rais da rede. Você deve aplicar um limite de taxa estrito aos endpoints da API
onde há risco de ataques de força bruta contra marcas de autenticação; consulte o
capítulo 3 para obter detalhes sobre comoApliquetaxa de limitação.

questionário

1. Qual status de handshake do SSLEngine indica que uma mensagem precisa ser
enviada pela rede?
1. NEED_TASK
2. NEED_WRAP
3. NEED_UNWRAP
4. NEED_UNWRAP_AGAIN
2. Qual dos seguintes é um risco aumentado ao usar conjuntos de cifras AES-GCM
para aplicativos IoT em comparação com outros modos?
1. Um ataque inovador ao AES
2. Reutilização de nonce levando a uma perda de segurança
3. Textos cifrados excessivamente grandes causando fragmentação de pacotes
4. A descriptografia é muito cara para dispositivos restritos

As respostas estão no final do capítulo.

12.2 Chaves pré-compartilhadas

DentroEm alguns ambientes particularmente restritos, os dispositivos podem não


ser capazes de executar a criptografia de chave pública necessária para um
handshake TLS. Por exemplo, restrições rígidas na memória disponível e no tama-
nho do código podem dificultar o suporte à assinatura de chave pública ou aos al-
goritmos de acordo de chave. Nesses ambientes, você ainda pode usar TLS (ou
DTLS) usando conjuntos de cifras baseados em chaves pré-compartilhadas (PSK)
em vez de certificados para autenticação. Os conjuntos de cifras PSK podem resul-
tar em uma redução drástica na quantidade de código necessária para implemen-
tar o TLS, conforme mostrado na Figura 12.5, porque a análise do certificado e o
código de validação, juntamente com as assinaturas e os modos de troca de cha-
ves públicas, podem ser eliminados.

DEFINIÇÃO Uma chave pré-compartilhada (PSK) é uma chave simétrica que é


compartilhada diretamente com o cliente e o servidor antecipadamente. Um PSK
pode ser usado para evitar as sobrecargas da criptografia de chave pública em
dispositivos restritos.

No TLS 1.2 e DTLS 1.2, um PSK pode ser usado especificando conjuntos de cifras
PSK dedicados, como TLS_PSK_WITH_AES_128_CCM . No TLS 1.3 e no próximo
DTLS 1.3, o uso de um PSK é negociado usando uma extensão que o cliente envia
na mensagem inicial ClientHello. Depois que um conjunto de cifras PSK é selecio-
nado, o servidor e o cliente derivam chaves de sessão do PSK e valores aleatórios
com os quais cada um contribui durante o handshake, garantindo que chaves ex-
clusivas ainda sejam usadas para cada sessão. A chave de sessão é usada para cal-
cular uma tag HMAC sobre todas as mensagens de handshake, fornecendo auten-
ticação da sessão: somente alguém com acesso ao PSK poderia derivar a mesma
chave HMAC e calcular a tag de autenticação correta.
Figura 12.5 O uso de conjuntos de cifras de chave pré-compartilhada (PSK) per-
mite que as implementações removam muito código complexo de uma implemen-
tação TLS. Os algoritmos de assinatura não são mais necessários e podem ser re-
movidos, assim como a maioria dos algoritmos de troca de chaves. A complexa ló-
gica de análise e validação do certificado X.509 também pode ser excluída, dei-
xando apenas as primitivas básicas de criptografia simétrica.

CUIDADO Embora chaves de sessão exclusivas sejam geradas para cada sessão,
os conjuntos básicos de cifras PSK carecem de sigilo de encaminhamento: um in-
vasor que comprometa o PSK pode derivar facilmente as chaves de sessão para
cada sessão anterior se capturar as mensagens de handshake. A Seção 12.2.4 dis-
cute conjuntos de cifras PSK com sigilo de encaminhamento.

Como o PSK é baseado em criptografia simétrica, com o cliente e o servidor


usando a mesma chave, ele fornece autenticação mútua de ambas as partes. Ao
contrário da autenticação de certificado de cliente, no entanto, não há nenhum
nome associado ao cliente além de um identificador opaco para o PSK, portanto,
um servidor deve manter um mapeamento entre PSKs e o cliente associado ou
confiar em outro método para autenticar a identidade do cliente.

AVISO Embora o TLS permita que o PSK tenha qualquer tamanho, você deve
usar apenas um PSK criptograficamente forte, como um valor de 128 bits de um
gerador de números aleatórios seguro. Os conjuntos de cifras PSK não são ade-
quados para uso com senhas porque um invasor pode executar um dicionário off-
line ou um ataque de força bruta depois de ver um aperto de mão PSK.

12.2.1 Implementando um servidor PSK

Listagem 12.8mostra como carregar um PSK de um keystore. Para este exemplo,


você pode carregar a chave HMAC existente que você criou no capítulo 6, mas é
uma boa prática usar chaves distintas para diferentes usos em um aplicativo,
mesmo que elas usem o mesmo algoritmo. Um PSK é apenas um array aleatório
de bytes, então você pode chamar o getEncoded() métodopara obter os bytes
brutos do Key objeto. Crie um novo arquivo chamado PskServer.java em
src/main/java/com/manning/apisecurityinaction e copie o conteúdo da listagem.
Você vai detalhar o resto do servidor em um momento.

Listagem 12.8 Carregando um PSK

pacote com.manning.apisecurityinaction;

importar estático java.nio.charset.StandardCharsets.UTF_8;


importar java.io.FileInputStream;
importar java.net.*;
importar java.security.*;
import org.bouncycastle.tls.*;
importar org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto;
public class PskServer {
byte estático[] loadPsk(char[] senha) lança exceção {
var keyStore = KeyStore.getInstance("PKCS12"); ❶
keyStore.load(new FileInputStream("keystore.p12"), senha); ❶
return keyStore.getKey("hmac-key", senha).getEncoded(); ❷
}
}

❶ Carregue o armazenamento de chaves.

❷ Carregue a chave e extraia os bytes brutos.

A Listagem 12.9 mostra um servidor DTLS básico com chaves pré-compartilhadas


escritas usando a API Bouncy Castle. As etapas a seguir são usadas para inicializar
o servidor e realizar um handshake PSK com o cliente:

Primeiro carregue o PSK do keystore.


Então você precisa inicializar um PSKTlsServer objeto, que requer dois argu-
mentos: um BcTlsCrypto objeto e um TlsPSKIdentityManager , que é usado
para consultar o PSK de um determinado cliente. Você voltará ao gerenciador
de identidades em breve.
a PSKTlsServer classeapenas anuncia suporte para TLS normal por padrão,
embora suporte DTLS muito bem. Substituir o getSupportedVersions() mé-
todopara garantir que o suporte DTLS 1.2 esteja ativado; caso contrário, o
aperto de mão falhará. As versões de protocolo suportadas são comunicadas
durante o handshake e alguns clientes podem falhar se houver versões TLS e
DTLS na lista.
Assim como o DtlsDatagramChannel você usou antes, o Bouncy Castle requer
que o soquete UDP seja conectado antes que ocorra o handshake DTLS. Como o
servidor não sabe onde o cliente está localizado, você pode esperar até que um
pacote seja recebido de qualquer cliente e então chamar connect () com o en-
dereço de soquete do cliente.
Crie um DTLSServerProtocol e UDPTransport objetos e, em seguida, chame
o método accept no objeto de protocolo para executar o handshake DTLS. Isso
retorna um DTLSTransport objeto que você pode usar para enviar e receber
pacotes criptografados e autenticados com o cliente.

DICA Embora a API Bouncy Castle seja simples ao usar PSKs, acho que é compli-
cada e difícil de depurar se você quiser usar autenticação de certificado e prefiro
o SSLEngine API.

Listagem 12.9 Servidor DTLS PSK

public static void main(String[] args) lança exceção {


var psk = loadPsk(args[0].toCharArray()); ❶
var crypto = new BcTlsCrypto(new SecureRandom());
var server = new PSKTlsServer(crypto, getIdentityManager(psk)) { ❷
@Override ❷
protected ProtocolVersion[] getSupportedVersions() { ❷
return ProtocolVersion.DTLSv12.only(); ❷
} ❷
}; ❷
var buffer = novo byte[2048];
var serverSocket = new DatagramSocket(54321);
var pacote = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(pacote); ❸
serverSocket.connect(packet.getSocketAddress()); ❸

var protocolo = new DTLSServerProtocol(); ❹


var transport = new UDPTransport(serverSocket, 1500); ❹
var dtls = protocol.accept(servidor, transporte); ❹

while (true) { ❺
var len = dtls.receive(buffer, 0, buffer.length, 60000); ❺
if (len == -1) quebra; ❺
var data = new String(buffer, 0, len, UTF_8); ❺
System.out.println("Recebido: " + dados); ❺
} ❺
}

❶ Carregue o PSK do keystore.


❷ Crie um novo PSKTlsServer e substitua as versões suportadas para permitir DTLS.

❸ BouncyCastle requer que o socket seja conectado antes do handshake.

❹ Crie um protocolo DTLS e execute o handshake usando o PSK.

❺ Receber mensagens do cliente e imprimi-las.

A parte que falta no quebra-cabeça é o gerenciador de identidade PSK, que é res-


ponsável por determinar qual PSK usar com cada cliente. A Listagem 12.10 mos-
tra uma implementação muito simples dessa interface para o exemplo, que re-
torna o mesmo PSK para cada cliente. O cliente envia um identificador como
parte do handshake PSK, portanto, uma implementação mais sofisticada pode
procurar diferentes PSKs para cada cliente. O servidor também pode fornecer
uma dica para ajudar o cliente a determinar qual PSK deve usar, caso tenha vá-
rios PSKs. Você pode deixar isso null aqui, que instrui o servidor a não enviar
uma dica. Abra o arquivo PskServer.java e adicione o método da listagem 12.10
para completar o servidorimplementação.

DICA Uma solução escalável seria o servidor gerar PSKs distintos para cada cli-
ente a partir de uma chave mestra usando HKDF, conforme discutido no capítulo
11.

Listagem 12.10 O gerenciador de identidade PSK


static TlsPSKIdentityManager getIdentityManager(byte[] psk) {
return new TlsPSKIdentityManager() {
@Sobrepor
public byte[] getHint() { ❶
return null; ❶
} ❶

@Sobrepor
public byte[] getPSK(byte[] identidade) { ❷
return psk; ❷
} ❷
};
}

❶ Deixe a dica PSK não especificada.

❷ Retorne o mesmo PSK para todos os clientes.

12.2.2 O cliente PSK

oO cliente PSK é muito semelhante ao servidor, conforme mostrado na Listagem


12.11. Como antes, você cria um novo BcTlsCrypto objeto e use isso para iniciali-
zar um PSKTlsClient objeto. Nesse caso, você passa o PSK e um identificador
para ele. Se você ainda não possui um bom identificador para o seu PSK, um hash
seguro do PSK funciona bem. Você pode usar o Crypto.hash() métododa biblio-
teca Salty Coffee do capítulo 6, que usa SHA-512. Quanto ao servidor, você precisa
substituir o getSupportedVersions() métodopara garantir que o suporte DTLS
esteja ativado. Você pode então se conectar ao servidor e executar o handshake
DTLS usando o DTLSClientProtocol objeto. o connect() métodoretorna
um DTLSTransport objeto que você pode usar para enviar e receber pacotes
criptografados com o servidor.

Crie um novo arquivo denominado PskClient.java junto com a classe do servidor e


digite o conteúdo da listagem para criar o servidor. Se o seu editor não os adicio-
nar automaticamente, você precisará adicionar as seguintes importações ao início
do arquivo:

importar estático java.nio.charset.StandardCharsets.UTF_8;


importar java.io.FileInputStream;
importar java.net.*;
importar java.security.*;
import org.bouncycastle.tls.*;
importar org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto;

Listagem 12.11 O cliente PSK

pacote com.manning.apisecurityinaction;
public class PskClient {
public static void main(String[] args) lança exceção {
var psk = PskServer.loadPsk(args[0].toCharArray()); ❶
var pskId = Crypto.hash(psk); ❶

var crypto = new BcTlsCrypto(new SecureRandom()); ❷


var cliente = new PSKTlsClient(crypto, pskId, psk) { ❷
@Sobrepor
protected ProtocolVersion[] getSupportedVersions() { ❸
return ProtocolVersion.DTLSv12.only(); ❸
} ❸
};

var address = InetAddress.getByName("localhost");


var socket = new DatagramSocket();
socket.connect(endereço, 54321); ❹
socket.send(new DatagramPacket(new byte[0], 0)); ❹
var transport = new UDPTransport(socket, 1500); ❺
var protocol = new DTLSClientProtocol(); ❺
var dtls = protocol.connect(client, transport); ❺

try (var in = Files.newBufferedReader(Paths.get("test.txt"))) {


Linha de corda;
while ((linha = in.readLine()) != null) {
System.out.println("Enviando: " + linha);
var buf = line.getBytes(UTF_8);
dtls.send(buf, 0, buf.length); ❻
}
}
}
}

❶ Carregue o PSK e gere um ID para ele.

❷ Crie um PSKTlsClient com o PSK.

❸ Substitua as versões suportadas para garantir o suporte DTLS.

❹ Conecte-se ao servidor e envie um pacote fictício para iniciar o handshake.

❺ Crie a instância DTLSClientProtocol e execute o handshake por UDP.

❻ Envie pacotes criptografados usando o objeto DTLSTransport retornado.

Agora você pode testar o handshake executando o servidor e o cliente em janelas


de terminal separadas. Abra dois terminais e mude para o diretório raiz do pro-
jeto em ambos. Em seguida, execute o seguinte no primeiro:

mvn clean compile exec:java \


-Dexec.mainClass=com.manning.apisecurityinaction.PskServer \
-Dexec.args=alterar ❶

❶ Especifique a senha do keystore como um argumento.


Isso compilará e executará a classe do servidor. Se você alterou a senha do
keystore, forneça o valor correto na linha de comando. Abra a segunda janela do
terminal e execute o cliente também:

mvn executável:java \
-Dexec.mainClass=com.manning.apisecurityinaction.PskClient \
-Dexec.args=alterar

Após a conclusão da compilação, você verá o cliente enviando as linhas de texto


para o servidor e o servidor recebendoeles.

OBSERVAÇÃO Como nos exemplos anteriores, este código de amostra não faz
nenhuma tentativa de manipular pacotes perdidos após a conclusão do
handshake.

12.2.3 Compatível com conjuntos de cifras PSK brutos

PorPor padrão, o Bouncy Castle segue as recomendações do IETF e permite ape-


nas conjuntos de cifras PSK combinados com um contrato de chave Diffie-Hell-
man efêmero para fornecer sigilo avançado. Esses conjuntos de cifras são discuti-
dos na seção 12.1.4. Embora sejam mais seguros do que os conjuntos de cifra PSK
brutos, eles não são adequados para dispositivos muito restritos que não podem
realizar criptografia de chave pública. Para habilitar os conjuntos de cifra PSK
brutos, você deve substituir o getSupportedCipherSuites() métodotanto no
cliente quanto no servidor. A Listagem 12.12 mostra como substituir esse método
para o servidor, nesse caso, fornecendo suporte para apenas um único conjunto
de cifras PSK usando AES-CCM para forçar seu uso. Uma alteração idêntica pode
ser feita no PSKTlsClient objeto.

Listagem 12.12 Habilitando conjuntos de cifra PSK brutos

var server = new PSKTlsServer(crypto, getIdentityManager(psk)) {


@Sobrepor
Protected ProtocolVersion[] getSupportedVersions() {
return ProtocolVersion.DTLSv12.only();
}
@Sobrepor
protected int[] getSupportedCipherSuites() { ❶
return new int[] { ❶
CipherSuite.TLS_PSK_WITH_AES_128_CCM ❶
}; ❶
} ❶
};

❶ Substitua o método getSupportedCipherSuites para retornar pacotes PSK brutos.

O Bouncy Castle oferece suporte a uma ampla variedade de conjuntos de cifras


PSK brutos no DTLS 1.2, mostrados na tabela 12.2. A maioria deles também possui
equivalentes em TLS 1.3. Não listei as variantes mais antigas usando o modo CBC
ou aquelas com cifras incomuns, como Camellia (o equivalente japonês do AES);
você geralmente deve evitá-los em IoTformulários.
Tabela 12.2 Conjuntos de cifras PSK brutas

Conjunto de cifras Descrição

TLS_PSK_WITH_AES_128_CCM AES no modo CCM com


chave de 128 bits e tag de
autenticação de 128 bits

TLS_PSK_WITH_AES_128_CCM_8 AES no modo CCM com


chaves de 128 bits e tags
de autenticação de 64 bits

TLS_PSK_WITH_AES_256_CCM AES no modo CCM com


chaves de 256 bits e tags
de autenticação de 128
bits

TLS_PSK_WITH_AES_256_CCM_8 AES no modo CCM com


chaves de 256 bits e tags
de autenticação de 64 bits

TLS_PSK_WITH_AES_128_GCM_SHA256 AES no modo GCM com


chaves de 128 bits
TLS_PSK_WITH_AES_256_GCM_SHA384 AES no modo GCM com
chaves de 256 bits

TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 ChaCha20-Poly1305 com


chaves de 256 bits

12.2.4 PSK com sigilo de encaminhamento

EUmencionou na seção 12.1.3 que os conjuntos de cifra PSK brutos carecem de si-
gilo de encaminhamento: se o PSK estiver comprometido, todo o tráfego captu-
rado anteriormente pode ser facilmente descriptografado. Se a confidencialidade
dos dados for importante para o seu aplicativo e seus dispositivos puderem supor-
tar uma quantidade limitada de criptografia de chave pública, você pode optar
por conjuntos de cifras PSK combinados com um contrato de chave Diffie-Hell-
man efêmero para garantir o sigilo futuro. Nesses conjuntos de cifras, a autentica-
ção do cliente e do servidor ainda é garantida pelo PSK, mas ambas as partes ge-
ram pares de chaves público-privadas aleatórios e trocam as chaves públicas du-
rante o handshake, conforme mostrado na figura 12.6. A saída de um acordo de
chave Diffie-Hellman entre a chave privada efêmera de cada lado e a chave pú-
blica efêmera da outra parte é então misturada na derivação das chaves de ses-
são. A mágica de Diffie-Hellman garante que as chaves de sessão não possam ser
recuperadas por um invasor que observe as mensagens de handshake, mesmo
que posteriormente recuperem o PSK. As chaves privadas efêmeras são apagadas
da memória assim que o handshake é concluído.

Protocolos personalizados e a estrutura do protocolo Noise

Embora para a maioria dos aplicativos IoT TLS ou DTLS deva ser perfeitamente
adequado para suas necessidades, você pode se sentir tentado a projetar seu pró-
prio protocolo criptográfico que seja um ajuste personalizado para seu aplicativo.
Isso é quase sempre um erro, porque mesmo criptógrafos experientes cometeram
erros graves ao projetar protocolos. Apesar desse conselho amplamente repetido,
muitos protocolos de segurança IoT personalizados foram desenvolvidos e novos
continuam a ser feitos. Se você acha que deve desenvolver um protocolo persona-
lizado para seu aplicativo e não pode usar TLS ou DTLS, a estrutura de protocolo
Noise ( https://noiseprotocol.org) pode ser usado como ponto de partida. Ruído
descreve como construir um protocolo seguro a partir de alguns blocos de cons-
trução básicos e descreve uma variedade de handshakes que atingem diferentes
objetivos de segurança. Mais importante ainda, o Noise é projetado e revisado por
especialistas e tem sido usado em aplicativos do mundo real, como o protocolo
WireGuard VPN ( https://www.wireguard.com ).
Figura 12.6 Conjuntos de cifras PSK com sigilo de encaminhamento usam pares de
chaves efêmeras além do PSK. O cliente e o servidor trocam chaves públicas efê-
meras em mensagens de troca de chaves durante o handshake TLS. Um acordo de
chave Diffie-Hellman é então realizado entre a chave privada efêmera de cada
lado e a chave pública efêmera recebida, que produz um valor secreto idêntico
que é então misturado no processo de derivação de chave TLS.

A Tabela 12.3 mostra alguns conjuntos de codificação PSK recomendados para


TLS ou DTLS 1.2 que fornecem sigilo de encaminhamento. As chaves Diffie-Hell-
man efêmeras podem ser baseadas no Diffie-Hellman original de campo finito,
caso em que os nomes das suítes contêm DHE, ou na curva elíptica Diffie-Hell-
man, caso em que contêm ECDHE. Em geral, as variantes ECDHE são mais adequa-
das para dispositivos restritos porque os parâmetros seguros para DHE exigem
grandes tamanhos de chave de 2.048 bits ou mais. A curva elíptica X25519 mais
recente é eficiente e segura quando implementada em software, mas só recente-
mente foi padronizada para uso em TLS 1.3. 6 A curva secp256r1 (também conhe-
cida como prime256v1 ou P-256) é comumente implementada por microchips de
elemento seguro de baixo custo e também é uma escolha razoável.
Tabela 12.3 Conjuntos de cifras PSK com sigilo de encaminhamento

Conjunto de cifras Descrição

TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 PSK com ECDHE


seguido por AES-
CCM com chaves
de 128 bits e tags
de autenticação de
128 bits. SHA-256
é usado para deri-
vação de chave e
autenticação de
handshake.
TLS_DHE_PSK_WITH_AES_128_CCM PSK com DHE se-
guido por AES-
CCM com chaves
de 128 ou 256 bits.
Eles também usam
TLS_DHE_PSK_WITH_AES_256_CCM SHA-256 para de-
rivação de chave e
autenticação de
handshake.

TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 PSK com DHE ou


ECDHE seguido
por ChaCha20-
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256
Poly1305.

Todos os conjuntos de cifras CCM também vêm em uma variante CCM_8 que usa
uma marca de autenticação curta de 64 bits. Conforme discutido anteriormente,
essas variantes devem ser usadas apenas se você precisar salvar cada byte de uso
da rede e tiver certeza de que possui medidas alternativas para garantir a autenti-
cidade do tráfego de rede. O AES-GCM também é compatível com conjuntos de ci-
fras PSK, mas eu não o recomendaria em ambientes restritos devido ao maior
risco denoncereuso.
questionário

3. Verdadeiro ou falso: conjuntos de cifras PSK sem sigilo de encaminhamento de-


rivam as mesmas chaves de criptografia para cada sessão.
4. Qual das seguintes primitivas criptográficas é usada para garantir sigilo de en-
caminhamento em conjuntos de cifras PSK que suportam isso?
1. criptografia RSA
2. assinaturas RSA
3. Derivação de chave HKDF
4. Acordo-chave Diffie-Hellman
5. Assinaturas digitais de curva elíptica

As respostas estão no final do capítulo.

12.3 Segurança de ponta a ponta

TLSe DTLS fornecem excelente segurança quando um cliente API pode falar dire-
tamente com o servidor. No entanto, conforme mencionado na introdução da se-
ção 12.1, em um aplicativo IoT típico, as mensagens podem trafegar por vários
protocolos diferentes. Por exemplo, dados de sensores produzidos por dispositi-
vos podem ser enviados por redes sem fio de baixa potência para um gateway lo-
cal, que os coloca em uma fila de mensagens MQTT para transmissão a outro ser-
viço, que agrega os dados e executa uma solicitação HTTP POST para uma nuvem
API REST para análise e armazenamento. Embora cada salto nessa jornada possa
ser protegido usando TLS, as mensagens estão disponíveis sem criptografia en-
quanto são processadas nos nós intermediários. Isso torna esses nós intermediá-
rios um alvo atraente para invasores porque, uma vez comprometidos, eles po-
dem visualizar e manipular todos os dados que passam por esse dispositivo.

A solução é fornecer segurança de ponta a ponta de todos os dados, independente-


mente da segurança da camada de transporte. Em vez de depender do protocolo
de transporte para fornecer criptografia e autenticação, a própria mensagem é
criptografada e autenticada. Por exemplo, uma API que espera solicitações com
uma carga JSON (ou uma alternativa binária eficiente) pode ser adaptada para
aceitar dados que foram criptografados com um algoritmo de criptografia autenti-
cado, que então descriptografa manualmente e verifica conforme mostrado na fi-
gura 12.7. Isso garante que uma solicitação de API criptografada pelo cliente origi-
nal só possa ser descriptografada pela API de destino, não importa quantos proto-
colos de rede diferentes sejam usados ​para transportar a solicitação do cliente ao
seu destino.
Figura 12.7 Na segurança de ponta a ponta, as solicitações de API são criptografa-
das individualmente e autenticadas pelo dispositivo cliente. Essas solicitações
criptografadas podem atravessar vários protocolos de transporte sem serem des-
criptografadas. A API pode então descriptografar a solicitação e verificar se ela
não foi adulterada antes de processar a solicitação da API.
OBSERVAÇÃO A segurança de ponta a ponta não substitui a segurança da ca-
mada de transporte. As mensagens do protocolo de transporte contêm cabeçalhos
e outros detalhes que não são protegidos por criptografia ou autenticação de
ponta a ponta. Você deve procurar incluir segurança em ambas as camadas de sua
arquitetura.

A segurança de ponta a ponta envolve mais do que simplesmente criptografar e


descriptografar pacotes de dados. Protocolos de transporte seguros, como o TLS,
também garantem que ambas as partes sejam devidamente autenticadas e que os
pacotes de dados não possam ser reordenados ou repetidos. Nas próximas seções,
você verá como garantir que as mesmas proteções sejam fornecidas ao usar a se-
gurança de ponta a ponta.

12.3.1 COSE

Se Se você deseja garantir a segurança de ponta a ponta das solicitações para uma
API REST regular baseada em JSON, pode ficar tentado a consultar os padrões
JOSE (JSON Object Signing and Encryption) discutidos no capítulo 6. Para aplicati-
vos de IoT, JSON geralmente é substituídas por codificações binárias mais eficien-
tes que fazem melhor uso da memória restrita e da largura de banda da rede e
que possuem implementações de software compactas. Por exemplo, dados numé-
ricos, como leituras de sensores, geralmente são codificados como strings deci-
mais em JSON, com apenas 10 valores possíveis para cada byte, o que é um des-
perdício em comparação com uma codificação binária compactada dos mesmos
dados.

Várias alternativas binárias ao JSON se tornaram populares nos últimos anos para
superar esses problemas. Uma escolha popular é a representação de objetos biná-
rios concisos(CBOR), que fornece um formato binário compacto que segue aproxi-
madamente o mesmo modelo do JSON, fornecendo suporte para objetos que con-
sistem em campos de valor-chave, matrizes, texto e strings binárias e números in-
teiros e de ponto flutuante. Como o JSON, o CBOR pode ser analisado e processado
sem um esquema. Além do CBOR, os padrões CBOR Object Signing and Encryption
(COSE; https://tools.ietf.org/html/rfc8152 ) fornecem recursos criptográficos seme-
lhantes aos do JOSE para JSON.

DEFINIÇÃO CBOR (Concise Binary Object Representation) é uma alternativa bi-


nária ao JSON. COSE (CBOR Object Signing and Encryption) fornece recursos de
criptografia e assinatura digital para CBOR e é vagamente baseado no JOSE.

Embora o COSE seja vagamente baseado no JOSE, ele divergiu bastante, tanto nos
algoritmos suportados quanto na forma como as mensagens são formatadas. Por
exemplo, no MAC simétrico JOSE, algoritmos como HMAC fazem parte do JWS
(JSON Web Signatures) e tratados como equivalentes aos algoritmos de assinatura
de chave pública. No COSE, os MACs são tratados mais como algoritmos de cripto-
grafia autenticados, permitindo que o mesmo acordo de chave e algoritmos de
agrupamento de chave sejam usados ​para transmitir uma chave MAC por
mensagem.

Em termos de algoritmos, o COSE suporta muitos dos mesmos algoritmos do JOSE


e adiciona algoritmos adicionais que são mais adequados para dispositivos restri-
tos, como AES-CCM e ChaCha20-Poly1305 para criptografia autenticada e versão
truncada do HMAC-SHA-256 que produz uma marca de autenticação menor de 64
bits. Ele também remove alguns algoritmos com pontos fracos percebidos, como
RSA com preenchimento PKCS#1 v1.5 e AES no modo CBC com uma tag HMAC se-
parada. Infelizmente, descartar o suporte para o modo CBC significa que todos os
algoritmos de criptografia autenticados por COSE exigem nonces que são muito
pequenos para serem gerados aleatoriamente. Isso é um problema porque, ao im-
plementar a criptografia de ponta a ponta, não há chaves de sessão ou números
de sequência de registro que possam ser usados ​para implementar com segurança
um nonce determinístico.

Felizmente, COSE tem uma solução na forma de HKDF (função de derivação de


chave baseada em hash) que você usou no capítulo 11. Em vez de usar uma chave
para criptografar diretamente uma mensagem, você pode usar a chave junto com
um nonce aleatório para derivar uma chave exclusiva para cada mensagem.
Como os problemas de reutilização de nonce ocorrem apenas se você reutilizar
um nonce com a mesma chave, isso reduz consideravelmente o risco de reutiliza-
ção acidental de nonce, assumindo que seus dispositivos tenham acesso a uma
fonte adequada de dados aleatórios (consulte a seção 12.3.2 se não tiverem ).

Para demonstrar o uso de COSE para criptografar mensagens, você pode usar a
implementação de referência Java do grupo de trabalho COSE. Abra o arquivo
pom.xml em seu editor e adicione as seguintes linhas à seção de dependências: 7

<dependência>
<groupId>com.augustcellars.cose</groupId>
<artifactId>cose-java</artifactId>
<version>1.1.0</version>
</dependência>

A Listagem 12.13 mostra um exemplo de criptografia de uma mensagem com


COSE usando HKDF para derivar uma chave exclusiva para a mensagem e AES-
CCM com uma chave de 128 bits para a criptografia da mensagem, que requer a
instalação do Bouncy Castle como um provedor de criptografia. Para este exem-
plo, você pode reutilizar o PSK dos exemplos na seção 12.2.1. COSE requer um
Recipient objetoa ser criado para cada destinatário de uma mensagem e o algo-
ritmo HKDF é especificado neste nível. Isso permite que diferentes derivações de
chave ou algoritmos de empacotamento sejam usados ​para diferentes destinatá-
rios da mesma mensagem, mas, neste exemplo, há apenas um único destinatário.
O algoritmo é especificado adicionando um atributo ao objeto destinatário. Você
deve adicionar esses atributos ao PROTECTED cabeçalhoregião, para garantir que
eles sejam autenticados. O nonce aleatório também é adicionado ao objeto desti-
natário, como o HKDF_Context_PartyU_nonce atributo; vou explicar
o PartyU parte em breve. Em seguida, você cria um EncryptMessage objeto e
define algum conteúdo para a mensagem. Aqui usei uma string simples, mas você
também pode passar qualquer array de bytes. Finalmente, você especifica o algo-
ritmo de criptografia de conteúdo como um atributo da mensagem (uma variante
do AES-CCM neste caso) e, em seguida, criptografa-o.

Listagem 12.13 Criptografando uma mensagem com COSE HKDF

Security.addProvider(new BouncyCastleProvider()); ❶
var keyMaterial = PskServer.loadPsk("changeit".toCharArray()); ❷

var destinatário = new Destinatário(); ❸


var keyData = CBORObject.NewMap() ❸
.Add(KeyKeys.KeyType.AsCBOR(), KeyKeys.KeyType_Octet) ❸
.Add(KeyKeys.Octet_K.AsCBOR(), keyMaterial); ❸
destinatário.SetKey(new OneKey(keyData)); ❸
destinatário.addAttribute(HeaderKeys.Algorithm, ❹
AlgorithmID.HKDF_HMAC_SHA_256.AsCBOR(), ❹
Attribute.PROTECTED); ❹
var nonce = novo byte[16]; ❺
novo SecureRandom().nextBytes(nonce); ❺
destinatário.addAttribute(HeaderKeys.HKDF_Context_PartyU_nonce, ❺
CBORObject.FromObject(nonce), Attribute.PROTECTED); ❺
var mensagem = new EncryptMessage(); ❻
message.SetContent("Olá, Mundo!"); ❻
message.addAttribute(HeaderKeys.Algorithm, ❻
AlgorithmID.AES_CCM_16_128_128.AsCBOR(), ❻
Attribute.PROTECTED); ❻
message.addRecipient(destinatário); ❻

message.encrypt(); ❼
System.out.println(Base64url.encode(message.EncodeToBytes())); ❼

❶ Instale o Bouncy Castle para obter suporte AES-CCM.

❷ Carregue a chave do armazenamento de chaves.

❸ Codifique a chave como um objeto de chave COSE e adicione ao destinatário.

❹ O algoritmo KDF é especificado como um atributo do destinatário.

❺ O nonce também é definido como um atributo no destinatário.

❻ Crie a mensagem e especifique o algoritmo de criptografia de conteúdo.

❼ Criptografe a mensagem e gere o resultado codificado.


O algoritmo HKDF no COSE suporta a especificação de vários campos além do
nonce PartyU, conforme mostrado na tabela 12.4, que permite que a chave deri-
vada seja vinculada a vários atributos, garantindo que chaves distintas sejam de-
rivadas para usos diferentes. Cada atributo pode ser definido para a Parte U ou
Parte V, que são apenas nomes arbitrários para os participantes em um protocolo
de comunicação. No COSE, a convenção é que o remetente de uma mensagem é a
Parte U e o destinatário é a Parte V. Simplesmente trocando as funções da Parte U
e da Parte V, você pode garantir que chaves distintas sejam derivadas para cada
direção de comunicação, o que fornece uma proteção útil contra ataques de refle-
xão. Cada parte pode contribuir com um nonce para o KDF, bem como informa-
ções de identidade e qualquer outra informação contextual. Por exemplo, se sua
API pode receber muitos tipos diferentes de solicitações, você pode incluir o tipo
de solicitação no contexto para garantir que diferentes chaves sejam usadas para
diferentes tipos de solicitações.

DEFINIÇÃO Um ataque de reflexãoocorre quando um invasor intercepta uma


mensagem de Alice para Bob e reproduz essa mensagem de volta para Alice. Se a
autenticação de mensagem simétrica for usada, Alice pode não conseguir distin-
guir isso de uma mensagem genuína de Bob. O uso de chaves distintas para men-
sagens de Alice para Bob do que para mensagens de Bob para Alice evita esses
ataques.
Tabela 12.4 Campos de contexto COSE HKDF

Campo Propósito

Identidade Um identificador para a parte U e V. Pode ser um nome de


PartyU usuário ou nome de domínio ou algum outro identificador es-
pecífico do aplicativo.
Identidade
PartyV

PartyU Nonces contribuídos por uma ou ambas as partes. Estes po-


nonce dem ser arrays de bytes aleatórios arbitrários ou números in-
teiros. Embora possam ser contadores simples, é melhor gerá-
PartyV los aleatoriamente na maioria dos casos.
nonce

PartyU Qualquer informação de contexto adicional específica do


outro aplicativo que deve ser incluída na derivação de chave.

ParteV
outro
Os campos de contexto do HKDF podem ser comunicados explicitamente como
parte da mensagem ou podem ser acordados pelas partes com antecedência e in-
cluídos no cálculo do KDF sem serem incluídos na mensagem. Se um nonce alea-
tório for usado, isso obviamente precisa ser incluído na mensagem; caso contrá-
rio, a outra parte não conseguirá adivinhar. Como os campos estão incluídos no
processo de derivação da chave, não há necessidade de autenticá-los separada-
mente como parte da mensagem: qualquer tentativa de adulterá-los fará com que
uma chave incorreta seja derivada. Por esse motivo, você pode colocá-los em um
UNPROTECTED cabeçalhoque não é protegido por um MAC.

Embora o HKDF seja projetado para uso com MACs baseados em hash, o COSE
também define uma variante dele que pode usar um MAC baseado em AES no
modo CBC, conhecido como HKDF-AES-MAC (essa possibilidade foi explicitamente
discutida no Apêndice D do original Proposta HKDF, consulte
https://eprint.iacr.org/2010/264.pdf ). Isso elimina a necessidade de uma imple-
mentação de função hash, economizando algum tamanho de código em dispositi-
vos restritos. Isso pode ser particularmente importante em dispositivos de baixo
consumo de energia porque alguns chips de elemento seguro fornecem suporte
de hardware para AES (e até mesmo criptografia de chave pública), mas não têm
suporte para SHA-256 ou outras funções de hash, exigindo que os dispositivos vol-
tem a ser mais lentos e menos eficientes implementações de software.
NOTA Você se lembrará do capítulo 11 que o HKDF consiste em duas funções:
uma função de extraçãoque deriva uma chave mestra de algum material de chave
de entrada e uma função de expansão que deriva uma ou mais novas chaves da
chave mestra. Quando usado com uma função de hash, o HKDF do COSE executa
ambas as funções. Quando utilizado com AES realiza apenas a fase de expansão;
isso é bom porque a chave de entrada já é uniformemente aleatória, conforme ex-
plicado no capítulo 11. 8

Além da criptografia autenticada simétrica, o COSE oferece suporte a uma varie-


dade de opções de assinatura e criptografia de chave pública, que são muito se-
melhantes ao JOSE, portanto, não as cobrirei em detalhes aqui. Um algoritmo de
chave pública em COSE que vale a pena destacar no contexto de aplicações IoT é o
suporte para curva elíptica Diffie-Hellman (ECDH) com chaves estáticas tanto
para o remetente quanto para o destinatário, conhecidas como ECDH-SS. Ao con-
trário do esquema de criptografia ECDH-ES suportado pelo JOSE, o ECDH-SS for-
nece autenticação do remetente, evitando a necessidade de uma assinatura sepa-
rada sobre o conteúdo de cada mensagem. A desvantagem é que o ECDH-SS sem-
pre deriva a mesma chave para o mesmo par de remetente e destinatário e, por-
tanto, pode ser vulnerável a ataques de repetição e ataques de reflexão e carece
de qualquer tipo de sigilo de encaminhamento. No entanto, quando usado com
HKDF e fazendo uso dos campos de contexto na tabela 12.4 para vincular as cha-
ves derivadas ao contexto em que são usadas, o ECDH-SS pode ser um bloco de
construção muito útil em IoT formulários.
12.3.2 Alternativas ao COSE

Embora O COSE é, em muitos aspectos, melhor projetado do que o JOSE e está co-
meçando a ser amplamente adotado em padrões como o FIDO 2 para chaves de
segurança de hardware ( https://fidoalliance .org/fido2/ ), ele ainda sofre do
mesmo problema de tentar fazer muito. Ele oferece suporte a uma ampla varie-
dade de algoritmos criptográficos, com diferentes objetivos e qualidades de segu-
rança. No momento em que escrevo, contei 61 variantes de algoritmo registradas
no registro de algoritmos COSE ( http://mng.bz/awDz ), a grande maioria das quais
está marcada como recomendada. Esse desejo de cobrir todas as bases pode tor-
nar difícil para os desenvolvedores saber quais algoritmos escolher e, embora
muitos deles sejam bons algoritmos, eles podem levar a problemas de segurança
quando mal utilizados, como os problemas de reutilização acidental de nonce que
você aprendeu no últimas seções.

SHA-3 e STROBE

O Instituto Nacional de Padrões e Tecnologia dos Estados Unidos (NIST) concluiu


recentemente uma competição internacional para selecionar o algoritmo que se
tornará o SHA-3, o sucessor da amplamente usada família de funções de hash
SHA-2. Para proteger contra possíveis fraquezas futuras no SHA-2, o algoritmo
vencedor (originalmente conhecido como Keccak) foi escolhido em parte porque é
muito diferente em estrutura do SHA-2. O SHA-3 é baseado em uma primitiva
criptográfica elegante e flexível conhecida como construção de esponja. Embora o
SHA-3 seja relativamente lento em software, ele é adequado para implementações
de hardware eficientes. A equipe Keccak implementou posteriormente uma am-
pla variedade de primitivas criptográficas com base na mesma construção de es-
ponja central: outras funções de hash, MACs e algoritmos de criptografia autenti-
cados. Veja https://keccak.team para mais detalhes.

Estrutura STROBE de Mike Hamburg( https://strobe.sourceforge.io ) baseia-se no


trabalho SHA-3 para criar uma estrutura para protocolos criptográficos para apli-
cativos IoT. O design permite que um único pequeno núcleo de código forneça
uma ampla variedade de proteções criptográficas, tornando-se uma alternativa
atraente ao AES para dispositivos restritos. Se o suporte de hardware para as fun-
ções principais do Keccak se tornar amplamente disponível, estruturas como o
STROBE podem se tornar muito atraentes.
Se você precisar de interoperabilidade baseada em padrões com outro software, o
COSE pode ser uma ótima escolha para um ecossistema de IoT, desde que você o
aborde com cuidado. Em muitos casos, no entanto, a interoperabilidade não é um
requisito porque você controla todos os softwares e dispositivos que estão sendo
implantados. Neste uma abordagem mais simples pode ser adotada, como usar
NaCl(the Networking and Cryptography Library; https://nacl.cr.yp.to ) para cripto-
grafar e autenticar um pacote de dados exatamente como você fez no capítulo 6.
Você ainda pode usar CBOR ou outra codificação binária compacta para os pró-
prios dados, mas NaCl (ou uma reescrita dele, como libsodium) cuida da escolha
de algoritmos criptográficos apropriados, examinados por especialistas genuínos.
A Listagem 12.14 mostra como é fácil criptografar um objeto CBOR usando a Se-
cretBox funcionalidade do NaCl (neste caso, por meio da biblioteca Java Salty
Coffee pura que você usou no capítulo 6), que é aproximadamente equivalente ao
exemplo COSE da seção anterior. Primeiro, você carrega ou gera a chave secreta
e, em seguida, criptografa seus dados CBOR usando essa chave.

Listagem 12.14 Criptografando CBOR com NaCl

var chave = SecretBox.key(); ❶


var cborMap = CBORObject.NewMap() ❷
.Add("foo", "bar") ❷
.Add("data", 12345); ❷
var box = SecretBox.encrypt(key, cborMap.EncodeToBytes()); ❸
System.out.println(caixa);

❶ Crie ou carregue uma chave.

❷ Gerar alguns dados CBOR.

❸ Criptografe os dados.

A caixa secreta do NaCl é relativamente adequada para aplicações IoT por vários
motivos:
Ele usa um nonce de 192 bits por mensagem, o que minimiza o risco de reutili-
zação acidental de nonce ao usar valores gerados aleatoriamente. Este é o ta-
manho máximo do nonce, portanto, você pode usar um valor mais curto se pre-
cisar economizar espaço e preenchê-lo com zeros antes de descriptografar. Re-
duzir o tamanho aumenta o risco de reutilização acidental de nonce, portanto,
evite reduzi-lo para muito menos que 128 bits.
ocifra XSalsa20 eO Poly1305 MAC usado pelo NaCl pode ser implementado de
forma compacta em software em uma ampla gama de dispositivos. Eles são
particularmente adequados para arquiteturas de 32 bits, mas também existem
implementações rápidas para microcontroladores de 8 bits. Portanto, eles são
uma boa escolha em plataformas sem suporte AES de hardware.
A marca de autenticação de 128 bits usada pelo Poly1305 é uma boa compensa-
ção entre segurança e expansão de mensagem. Embora existam algoritmos
MAC mais fortes, a tag de autenticação só precisa permanecer segura durante o
tempo de vida da mensagem (até que ela expire, por exemplo), enquanto o con-
teúdo da mensagem pode precisar permanecer em segredo por muito mais
tempo.

Se seus dispositivos são capazes de executar criptografia de chave pública, o NaCl


também fornece criptografia autenticada de chave pública conveniente e efici-
ente na forma da CryptoBox classe, mostrada na Listagem 12.15. o
CryptoBox algoritmofunciona muito como o algoritmo ECDH-SS do COSE, pois
executa um acordo de chave estática entre as duas partes. Cada parte tem seu
próprio par de chaves junto com a chave pública da outra parte (consulte a seção
12.4 para uma discussão sobre distribuição de chaves). Para criptografar, você usa
sua própria chave privada e a chave pública do destinatário e, para descriptogra-
far, o destinatário usa sua chave privada e sua chave pública. Isso mostra que
mesmo a criptografia de chave pública não é muito mais trabalhosa quando você
usa uma biblioteca bem projetada como NaCl.

AVISO Ao contrário do HKDF do COSE, a derivação de chave executada na caixa


criptográfica do NaCl não vincula a chave derivada a nenhum material de con-
texto. Você deve certificar-se de que as próprias mensagens contenham as identi-
dades do remetente e do destinatário e contexto suficiente para evitar ataques de
reflexão ou repetição.

Listagem 12.15 Usando a CryptoBox do NaCl

var senderKeys = CryptoBox.keyPair(); ❶


var destinatárioChaves = CryptoBox.keyPair(); ❶
var cborMap = CBORObject.NewMap()
.Add("foo", "barra")
.Add("dados", 12345);
var enviado = CryptoBox.encrypt(senderKeys.getPrivate(), ❷
destinatárioKeys.getPublic(), cborMap.EncodeToBytes()); ❷

var recvd = CryptoBox.fromString(sent.toString());


var cbor = recvd.decrypt(recipientKeys.getPrivate(), ❸
senderKeys.getPublic()); ❸
System.out.println(CBORObject.DecodeFromBytes(cbor));

❶ O remetente e o destinatário possuem, cada um, um par de chaves.

❷ Criptografe usando sua chave privada e a chave pública do destinatário.

❸ O destinatário descriptografa com sua chave privada e sua chave pública.

12.3.3 Criptografia autenticada resistente ao uso indevido

Embora NaCl e COSE podem ser usados ​de forma a minimizar o risco de reutiliza-
ção de nonce, eles apenas o fazem na suposição de que um dispositivo tenha
acesso a alguma fonte confiável de dados aleatórios. Esse nem sempre é o caso de
dispositivos restritos, que geralmente não têm acesso a boas fontes de entropia ou
mesmo a relógios confiáveis ​que poderiam ser usados ​para nonces determinísti-
cos. A pressão para reduzir o tamanho das mensagens também pode resultar em
desenvolvedores usando nonces muito pequenos para serem gerados aleatoria-
mente com segurança. Um invasor também pode influenciar as condições para
tornar a reutilização de nonce mais provável, como adulterar o relógio ou explo-
rar pontos fracos nos protocolos de rede, como ocorreu nos ataques KRACKcontra
WPA2 ( https://www.krackattacks .com ). No pior caso, onde um nonce é reutili-
zado para muitas mensagens, os algoritmos em NaCl e COSE falham catastrofica-
mente, permitindo que um invasor recupere muitas informações sobre os dados
criptografados e, em alguns casos, adultere esses dados ou construa falsificações.

Para evitar esse problema, os criptógrafos desenvolveram novos modos de opera-


ção para cifras que são muito mais resistentes à reutilização de nonce acidental
ou maliciosa. Esses modos de operação alcançam uma meta de segurança cha-
mada criptografia autenticada resistente ao uso indevido (MRAE). O algoritmo
mais conhecido é o SIV-AES, baseado em um modo de operação conhecido como
SyntheticVetor de inicialização (SIV; https://tools.ietf.org/html/rfc5297 ). Em uso
normal com nonces exclusivos, o modo SIV fornece as mesmas garantias que
qualquer outra cifra de criptografia autenticada. Mas se um nonce for reutilizado,
um modo MRAE não falha tão catastroficamente: um invasor só poderia dizer se
exatamente a mesma mensagem foi criptografada com a mesma chave e nonce.
Nenhuma perda de autenticidade ou integridade ocorre. Isso torna o SIV-AES e
outros modos MRAE muito mais seguros para uso em ambientes onde pode ser di-
fícil garantir nonces exclusivos, como dispositivos IoT.

DEFINIÇÃO Uma cifra fornece criptografia autenticada resistente ao uso inde-


vido (MRAE) se a reutilização acidental ou deliberada do nonce resultar em ape-
nas uma pequena perda de segurança. Um invasor só pode aprender se a mesma
mensagem foi criptografada duas vezes com o mesmo nonce e chave e não há
perda de autenticidade.Modo de vetor de inicialização sintética (SIV)é um modo
MRAE bem conhecido, e SIV-AESo uso mais comum dela.
O modo SIV funciona calculando o nonce (também conhecido como Vetor de Inici-
alizaçãoou IV) usando umfunção pseudo-aleatória(PRF) em vez de usar um valor
ou contador puramente aleatório. Muitos MACs usados ​para autenticação também
são PRFs, então o SIV reutiliza o MAC usado para autenticação para fornecer tam-
bém o IV, conforme mostrado na figura 12.8.

Figura 12.8 O modo SIV usa a marca de autenticação MAC como o IV para cripto-
grafia. Isso garante que o IV só se repita se a mensagem for idêntica, eliminando
problemas de reutilização nonce que podem causar falhas de segurança catastró-
ficas. O SIV-AES é particularmente adequado para ambientes IoT porque precisa
apenas de um circuito de criptografia AES para realizar todas as operações (até
mesmo a descriptografia).

CUIDADO Nem todos os MACs são PRFs, portanto, você deve seguir as imple-
mentações padrão do modo SIV em vez de inventar o seu próprio.

O processo de criptografia funciona fazendo duas passagens pela entrada:

1. Primeiro, um MAC é calculado sobre a entrada de texto simples e quaisquer da-


dos associados. 9 A etiqueta MAC é conhecida como Synthetic IV ou SIV.
2. Em seguida, o texto simples é criptografado usando uma chave diferente
usando a tag MAC da etapa 1 como o nonce.

As propriedades de segurança do MAC garantem que é extremamente improvável


que duas mensagens diferentes resultem na mesma marca MAC e, portanto, ga-
rante que o mesmo nonce não seja reutilizado com duas mensagens diferentes. O
SIV é enviado junto com a mensagem, assim como seria um tag MAC normal. A
descriptografia funciona ao contrário: primeiro o texto cifrado é descriptografado
usando o SIV e, em seguida, a tag MAC correta é calculada e comparada com o SIV.
Se as tags não corresponderem, a mensagem será rejeitada.

AVISO Como a marca de autenticação só pode ser validada após a mensagem ter
sido descriptografada, você deve ter cuidado para não processar nenhum dado
descriptografado antes que essa etapa crucial de autenticação seja concluída.
No SIV-AES, o MAC é AES-CMAC, que é uma versão melhorada do AES-CBC-MAC
usado no COSE. A criptografia é realizada usando AES no modo CTR. Isso significa
que o SIV-AES tem a mesma propriedade interessante do AES-CCM: requer apenas
um circuito de criptografia AES para todas as operações (até mesmo a descripto-
grafia), portanto, pode ser implementado de forma compacta.

Ataques de canal lateral e de falha

Embora o modo SIV proteja contra uso indevido acidental ou deliberado de non-
ces, ele não protege contra todos os ataques possíveis em um ambiente IoT.
Quando um invasor pode ter acesso físico direto aos dispositivos, especialmente
onde há proteção ou vigilância física limitada, você também pode precisar consi-
derar outros ataques. Um chip de elemento seguropode fornecer alguma proteção
contra adulteração e tentativas de ler as chaves diretamente da memória, mas as
chaves e outros segredos também podem vazar por muitos canais laterais. Um ca-
nal lateral ocorre quando informações sobre um segredo podem ser deduzidas
pela medição de aspectos físicos de cálculos usando esse segredo, como o
seguinte:

O tempo das operações pode revelar informações sobre a chave. As implemen-


tações criptográficas modernas são projetadas para serem tempo constan-
tepara evitar o vazamento de informações sobre a chave dessa maneira. Muitas
implementações de software do AES não são de tempo constante, portanto, ci-
fras alternativas, como ChaCha20, são frequentemente preferidas por esse
motivo.
A quantidade de energia usada por um dispositivo pode variar dependendo do
valor dos dados secretos que está processando. Análise de poder diferenci-
alpode ser usado para recuperar dados secretos examinando quanta energia é
usada ao processar diferentes entradas.
Emissões produzidas durante o processamento, incluindo radiação eletromag-
nética, calor ou mesmo sons, foram usadas para recuperar dados secretos de
cálculos criptográficos.

Além de observar passivamente os aspectos físicos de um dispositivo, um invasor


também pode interferir diretamente em um dispositivo na tentativa de recuperar
segredos. Em um ataque de falha, um invasor interrompe o funcionamento nor-
mal de um dispositivo na esperança de que a operação defeituosa revele algumas
informações sobre os segredos que está processando. Por exemplo, ajustar a fonte
de alimentação (conhecido como falha) em um momento bem escolhido pode fa-
zer com que um algoritmo reutilize um nonce, vazando informações sobre men-
sagens ou uma chave privada. Em alguns casos, algoritmos determinísticos, como
o SIV-AES, podem facilitar os ataques de falha para um invasor.

A proteção contra ataques de canal lateral e de falha está muito além do escopo
deste livro. Bibliotecas e dispositivos criptográficos documentarão se foram proje-
tados para resistir a esses ataques. Os produtos podem ser certificados de acordo
com padrões como FIPS 140-2 ou Commons Criteria, que fornecem alguma garan-
tia de que o dispositivo resistirá a alguns ataques físicos, mas você precisa ler as
letras miúdas para determinar exatamente quais ameaças foram testadas.
Até agora, o modo que descrevi sempre produzirá o mesmo nonce e o mesmo
texto cifrado sempre que a mesma mensagem de texto simples for criptografada.
Se você se lembra do capítulo 6, esse esquema de criptografia não é seguro por-
que um invasor pode saber facilmente se a mesma mensagem foi enviada várias
vezes. Por exemplo, se você tiver um sensor enviando pacotes de dados contendo
leituras do sensor em uma pequena faixa de valores, um observador poderá des-
cobrir quais são as leituras criptografadas do sensor depois de ver o suficiente de-
las. É por isso que os modos normais de criptografia adicionam um único nonce
ou IV aleatório em cada mensagem: para garantir que diferentes textos cifrados
sejam produzidos mesmo que a mesma mensagem seja criptografada. O modo SIV
resolve esse problema permitindo que você inclua um IV aleatório nos dados as-
sociados que acompanham a mensagem. Como esses dados associados também
estão incluídos no cálculo do MAC, ele garante que o SIV calculado será diferente,
mesmo que a mensagem seja a mesma. Para tornar isso um pouco mais fácil, o
modo SIV permite que mais de um bloco de dados associado seja fornecido à cifra
- até 126 blocos no SIV-AES.

A Listagem 12.16 mostra um exemplo de criptografia de alguns dados com SIV-


AES em Java usando uma biblioteca de software livre que implementa o modo
usando primitivas AES do Bouncy Castle. 10 Para incluir a biblioteca, abra o ar-
quivo pom.xml e adicione as seguintes linhas à seção de dependências:

<dependência>
<groupId>org.cryptomator</groupId>
<artifactId>modo siv</artifactId>
<version>1.3.2</version>
</dependência>

O modo SIV requer duas chaves separadas: uma para o MAC e outra para cripto-
grafia e descriptografia. A especificação que define o SIV-AES (
https://tools.ietf.org/html/rfc5297 ) descreve como uma única chave com o dobro
do tamanho normal pode ser dividida em duas, com a primeira metade se tor-
nando a chave MAC e a segunda metade da chave de criptografia. Isso é demons-
trado na Listagem 12.16 dividindo a chave PSK de 256 bits existente em duas cha-
ves de 128 bits. Você também pode derivar as duas chaves de uma única chave
mestra usando HKDF, como aprendeu no capítulo 11. A biblioteca usada na lista-
gem fornece encrypt () e decrypt() métodosque usam a chave de criptogra-
fia, a chave MAC, o texto simples (ou texto cifrado para descriptografia) e qual-
quer número de blocos de dados associados. Neste exemplo, você passará um ca-
beçalho e um IV aleatório. A especificação SIV recomenda que qualquer IV aleató-
rio seja incluído como o último associado dadosquadra.
DICA A SivMode classe da biblioteca é thread-safe e projetada para ser reutili-
zada. Se você usar essa biblioteca em produção, deverá criar uma única instância
dessa classe e reutilizá-la para todas as chamadas.

Listagem 12.16 Criptografando dados com SIV-AES

var psk = PskServer.loadPsk("changeit".toCharArray()); ❶


var macKey = new SecretKeySpec(Arrays.copyOfRange(psk, 0, 16), ❶
"AES"); ❶
var encKey = new SecretKeySpec(Arrays.copyOfRange(psk, 16, 32), ❶
"AES"); ❶
var randomIv = novo byte[16]; ❷
new SecureRandom().nextBytes(randomIv); ❷
var cabeçalho = "Cabeçalho de teste".getBytes();
var corpo = CBORObject.NewMap()
.Add("sensor", "F5671434")
.Add("leitura", 1234).EncodeToBytes();

var siv = new SivMode();


var texto cifrado = siv.encrypt(encKey, macKey, corpo, ❸
cabeçalho, randomIv); ❸
var texto simples = siv.decrypt(encKey, macKey, ciphertext, ❹
header, randomIv); ❹

❶ Carregue a chave e divida em MAC separado e chaves de criptografia.


❷ Gere um IV aleatório com a melhor entropia disponível.

❸ Criptografar o corpo passando o cabeçalho e o IV aleatório como dados associados.

❹ Descriptografe passando os mesmos blocos de dados associados.

questionário

5. Os modos de operação de criptografia autenticada resistente ao uso indevido


(MRAE) protegem contra qual das seguintes falhas de segurança?
1. Superaquecimento
2. reutilização nonce
3. senhas fracas
4. Ataques de canal lateral
5. Perdendo suas chaves secretas
6. Verdadeiro ou falso: o SIV-AES é igualmente seguro, mesmo que você repita um
nonce.

As respostas estão no final do capítulo.

12.4 Distribuição e gerenciamento de chaves

Dentrouma arquitetura de API normal, o problema de como as chaves são distri-


buídas para clientes e servidores é resolvido usando uma infraestrutura de chave
pública (PKI), conforme você aprendeu no capítulo 10. Para recapitular:
Nesta arquitetura, cada dispositivo possui sua própria chave privada e chave
pública associada.
A chave pública é empacotada em um certificado assinado por uma autoridade
de certificação (CA) e cada dispositivo possui uma cópia permanente da chave
pública da CA.
Quando um dispositivo se conecta a outro dispositivo (ou recebe uma conexão),
ele apresenta seu certificado para se identificar. O dispositivo autentica com a
chave privada associada para provar que é o titular legítimo deste certificado.
O destinatário pode verificar a identidade do outro dispositivo verificando se
seu certificado foi assinado pela CA confiável e se não expirou, foi revogado ou
se tornou inválido de qualquer outra forma.

Essa arquitetura também pode ser usada em ambientes IoT e é frequentemente


usada para dispositivos mais capazes. Mas dispositivos restritos que não possuem
a capacidade de criptografia de chave pública são incapazes de fazer uso de uma
PKI e, portanto, outras alternativas devem ser usadas, baseadas em criptografia
simétrica. A criptografia simétrica é eficiente, mas exige que o cliente e o servidor
da API tenham acesso à mesma chave, o que pode ser um desafio se houver um
grande número de dispositivos envolvidos. As técnicas de distribuição de chaves
descritas nas próximas seções visam resolver esse problema.
12.4.1 Fornecimento único de chave

oA abordagem mais simples é fornecer a cada dispositivo uma chave no momento


da fabricação do dispositivo ou em um estágio posterior, quando um lote de dis-
positivos for inicialmente adquirido por uma organização. Uma ou mais chaves
são geradas com segurança e armazenadas permanentemente na memória so-
mente leitura(ROM) ou EEPROM (ROM programável apagável eletricamente) no
dispositivo. As mesmas chaves são criptografadas e empacotadas junto com as in-
formações de identidade do dispositivo e armazenadas em um diretório central
como o LDAP, onde podem ser acessadas por servidores de API para autenticar e
descriptografar solicitações de clientes ou para criptografar respostas a serem en-
viadas a esses dispositivos. A arquitetura é mostrada na figura 12.9. Um módulo
de segurança de hardware(HSM) pode ser usado para armazenar com segurança
as chaves mestras de criptografia dentro da fábrica para evitar
comprometimento.
Figura 12.9 Chaves de dispositivo exclusivas podem ser geradas e instaladas em
um dispositivo durante a fabricação. As chaves do dispositivo são criptografadas e
armazenadas junto com os detalhes do dispositivo em um diretório ou banco de
dados LDAP. As APIs podem recuperar posteriormente as chaves criptografadas
do dispositivo e descriptografá-las para proteger as comunicações com esse
dispositivo.
Uma alternativa para gerar chaves completamente aleatórias durante a fabrica-
ção é derivar chaves específicas do dispositivo a partir de uma chave mestra e al-
gumas informações específicas do dispositivo. Por exemplo, você pode usar o
HKDF do capítulo 11 para derivar uma chave específica de dispositivo exclusiva
com base em um número de série exclusivo ou endereço de hardware ethernet
atribuído a cada dispositivo. A chave derivada é armazenada no dispositivo como
antes, mas o servidor API pode derivar a chave para cada dispositivo sem precisar
armazená-los todos em um banco de dados. Quando o dispositivo se conecta ao
servidor, ele autentica enviando as informações exclusivas (junto com um ca-
rimbo de data/hora ou um desafio aleatório para evitar a repetição), usando sua
chave de dispositivo para criar um MAC. O servidor pode então derivar a mesma
chave de dispositivo da chave mestra e usá-la para verificar o MAC. Por exemplo,
O Azure IoT Hub Device Provisioning Service da Microsoft usa um esquema seme-
lhante a este para registro de grupo de dispositivos usando uma chave simétrica;
Para mais informações, vejahttp://mng.bz/gg4l .

12.4.2 Servidores de distribuição de chaves

Em vez deem vez de instalar uma única chave uma vez quando um dispositivo é
adquirido pela primeira vez, você pode distribuir chaves periodicamente para
dispositivos usando um servidor de distribuição de chaves. Nesse modelo, o dispo-
sitivo usa sua chave inicial para se registrar no servidor de distribuição de chaves
e, em seguida, recebe uma nova chave que pode ser usada para comunicações fu-
turas. O servidor de distribuição de chaves também pode disponibilizar essa
chave para servidores de API quando eles precisarem se comunicar com esse
dispositivo.

SAIBA MAIS O produto E4 da Teserakt ( https://teserakt.io/e4/ ) inclui um servi-


dor de distribuição de chaves que pode distribuir chaves criptografadas para dis-
positivos através do protocolo de mensagens MQTT. A Teserakt publicou uma sé-
rie de artigos sobre o design de sua arquitetura IoT segura, projetada por criptó-
grafos respeitados, em http://mng.bz/5pKz .

Após a conclusão do processo de registro inicial, o servidor de distribuição de cha-


ves pode fornecer periodicamente uma nova chave para o dispositivo, criptogra-
fada usando a chave antiga. Isso permite que o dispositivo altere frequentemente
suas chaves sem precisar gerá-las localmente, o que é importante porque os dis-
positivos restritos geralmente têm acesso severamente limitado às fontes de
entropia.

Atestado remoto e execução confiável

Alguns dispositivos podem ser equipados com hardware seguro que pode ser
usado para estabelecer confiança em um dispositivo quando ele é conectado pela
primeira vez à rede de uma organização. Por exemplo, o dispositivo pode ter um
Trusted Platform Module (TPM), que é um tipo de módulo de segurança de hard-
ware (HSM) popularizado pela Microsoft. Um TPM pode provar a um servidor re-
moto que é um modelo específico de dispositivo de um fabricante conhecido com
um número de série específico, em um processo conhecido como atestado remoto.
O atestado remoto é obtido usando um protocolo de desafio-resposta baseado em
uma chave privada, conhecida como Chave de Endosso(EK), que é queimado no
dispositivo no momento da fabricação. O TPM usa o EK para assinar uma declara-
ção de atestado indicando a marca e o modelo do dispositivo e também pode for-
necer detalhes sobre o estado atual do dispositivo e do hardware conectado.
Como essas medições do estado do dispositivo são feitas pelo firmware em execu-
ção no TPM seguro, elas fornecem fortes evidências de que o dispositivo não foi
adulterado.

Embora o atestado de TPM seja forte, um TPM não é um componente barato para
adicionar aos seus dispositivos IoT. Algumas CPUs incluem suporte para um ambi-
ente de execução confiável(TEE), como o ARM TrustZone, que permite que o soft-
ware assinado seja executado em um modo de execução seguro especial, isolado
do sistema operacional normal e de outros códigos. Embora menos resistente a
ataques físicos do que um TPM, um TEE pode ser usado para implementar fun-
ções críticas de segurança, como atestado remoto. Um TEE também pode ser
usado como HSM de um homem pobre, fornecendo uma camada adicional de se-
gurança sobre soluções de software puro.
Em vez de escrever um servidor de distribuição de chaves dedicado, também é
possível distribuir chaves usando um protocolo existente, como OAuth2. Um ras-
cunho de padrão para OAuth2 (atualmente expirado, mas revivido periodica-
mente pelo grupo de trabalho OAuth) descreve como distribuir chaves simétricas
criptografadas juntamente com um token de acesso OAuth2 ( http://mng.bz/6AZy )
e RFC 7800 descreve como tal chave pode ser codificado em um JSON Web Token (
https://tools.ietf.org/html/rfc7800#section-3.3 ). A mesma técnica pode ser usada
com CBOR Web Tokens ( http://mng.bz/oRaM). Essas técnicas permitem que um
dispositivo receba uma nova chave sempre que obtém um token de acesso, e qual-
quer servidor de API com o qual ele se comunique pode recuperar a chave de ma-
neira padrão do próprio token de acesso ou por meio da introspecção do token. O
uso de OAuth2 em um ambiente IoT é discutido mais adiante emcapítulo 13.

12.4.3 Catraca para sigilo de encaminhamento

Seseus dispositivos IoT estão enviando dados confidenciais em solicitações de API,


usar a mesma chave de criptografia durante toda a vida útil do dispositivo pode
representar um risco. Se a chave do dispositivo for comprometida, um invasor po-
derá não apenas descriptografar qualquer comunicação futura, mas também to-
das as mensagens anteriores enviadas por esse dispositivo. Para evitar isso, você
precisa usar mecanismos criptográficos que forneçam sigilo de encaminhamento,
conforme discutido na seção 12.2. Nessa seção, examinamos os mecanismos de
chave pública para obter sigilo direto, mas você também pode atingir essa meta
de segurança usando criptografia puramente simétrica por meio de uma técnica
conhecida como catraca.

DEFINIÇÃO Ratcheting em criptografia é uma técnica para substituir uma


chave simétrica periodicamente para garantir o sigilo de encaminhamento. A
nova chave é derivada da chave antiga usando uma função unidirecional, conhe-
cida como catraca, porque ela se move apenas em uma direção. É impossível deri-
var uma chave antiga da nova chave para que as conversas anteriores sejam se-
guras, mesmo que a nova chave esteja comprometida.

Existem várias maneiras de derivar a nova chave da antiga. Por exemplo, você
pode derivar a nova chave usando HKDF com uma string de contexto fixa como
no exemplo a seguir:

var newKey = HKDF.expand(oldKey, "iot-key-ratchet", 32, "HMAC");

DICA É uma prática recomendada usar HKDF para derivar duas (ou mais) cha-
ves: uma é usada apenas para HKDF, para derivar a próxima chave catraca, en-
quanto a outra é usada para criptografia ou autenticação. A chave de catraca às
vezes é chamada de chave de correnteou chave de encadeamento.

Se a chave não for usada para HMAC, mas usada para criptografia usando AES ou
outro algoritmo, você poderá reservar um valor nonce ou IV específico para ser
usado para a catraca e derivar a nova chave como a criptografia de uma mensa-
gem totalmente zero usando aquele IV reservado, conforme mostrado na Lista-
gem 12.17 usando AES no modo Counter. Neste exemplo, um IV de 128 bits de to-
dos os bits 1 é reservado para a operação de catraca porque é altamente imprová-
vel que esse valor seja gerado por um contador ou por um IV gerado
aleatoriamente.

AVISO Você deve garantir que o IV especial usado para a catraca nunca seja
usado para criptografar uma mensagem.

Listagem 12.17 Ratcheting com AES-CTR

private static byte[] ratchet(byte[] oldKey) lança exceção {


var cipher = Cipher.getInstance("AES/CTR/NoPadding");
var iv = novo byte[16]; ❶
Arrays.fill(iv, (byte) 0xFF); ❶
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(oldKey, "AES"), ❷
new IvParameterSpec(iv)); ❷
return cipher.doFinal(new byte[32]); ❸
}

❶ Reserve um IV fixo que seja usado apenas para catraca.

❷ Inicialize a cifra usando a chave antiga e a catraca fixa IV.

❸ Criptografe 32 zero bytes e use a saída como a nova chave.


Depois de executar uma catraca, você deve garantir que a chave antiga seja limpa
da memória para que não possa ser recuperada, conforme mostrado no exemplo
a seguir:

var newKey = catraca(chave);


Arrays.fill(chave, (byte) 0); ❶
chave = novaChave; ❷

❶ Substitua a chave antiga por zero bytes.

❷ Substitua a chave antiga pela nova.

DICA Em Java e linguagens semelhantes, o coletor de lixo pode duplicar o con-


teúdo das variáveis ​na memória, portanto, as cópias podem permanecer mesmo
se você tentar limpar os dados. Você pode
usar ByteBuffer.allocateDirect() para criar memória off-heapque não é ge-
renciado pelo coletor de lixo.

A catraca só funciona se o cliente e o servidor puderem determinar quando


ocorre uma catraca; caso contrário, eles acabarão usando chaves diferentes. Você
deve, portanto, realizar operações de catraca em momentos bem definidos. Por
exemplo, cada dispositivo pode ativar sua chave à meia-noite todos os dias, ou a
cada hora, ou talvez até a cada 10 mensagens. 11 A taxa na qual as catracas de-
vem ser executadas depende do número de solicitações que o dispositivo envia e
da sensibilidade dos dados que estão sendo transmitidos.

A catraca após um número fixo de mensagens pode ajudar a detectar o compro-


metimento: se um invasor estiver usando a chave secreta roubada de um disposi-
tivo, o servidor da API receberá mensagens extras além de qualquer dispositivo
enviado e, portanto, executará a catraca antes do dispositivo legítimo . Se o dispo-
sitivo descobrir que o servidor está executando a catraca antes do esperado, isso é
uma evidência de que outra parte comprometeu o segredo do dispositivochave.

12.4.4 Segurança pós-compromisso

Emborao sigilo de encaminhamento protege as comunicações antigas se um dis-


positivo for posteriormente comprometido, mas não diz nada sobre a segurança
das comunicações futuras. Tem havido muitas histórias na imprensa nos últimos
anos de dispositivos IoT comprometidos, portanto, ser capaz de recuperar a segu-
rança após um comprometimento é uma meta de segurança útil, conhecida como
segurança pós-comprometimento.

DEFINIÇÃO Segurança pós-compromisso (ou sigilo futuro) é alcançado se um


dispositivo puder garantir a segurança de comunicações futuras depois que um
dispositivo for comprometido. Não deve ser confundido com sigilo direto que pro-
tege a confidencialidade de comunicações passadas.
A segurança pós-comprometimento assume que o comprometimento não é per-
manente e, na maioria dos casos, não é possível manter a segurança na presença
de um comprometimento persistente. No entanto, em alguns casos, pode ser pos-
sível restabelecer a segurança após o término do comprometimento. Por exemplo,
uma vulnerabilidade de path traversalpode permitir que um invasor remoto visu-
alize o conteúdo dos arquivos em um dispositivo, mas não os modifique. Depois
que a vulnerabilidade é encontrada e corrigida, o acesso do invasor é removido.

DEFINIÇÃO Uma vulnerabilidade de passagem de caminhoocorre quando um


servidor da Web permite que um invasor acesse arquivos que não deveriam ser
disponibilizados manipulando o caminho da URL nas solicitações. Por exemplo, se
o servidor da Web publicar dados em uma pasta /data, um invasor poderá enviar
uma solicitação para /data/../../../etc/shadow. 12 Se o servidor da Web não verificar
cuidadosamente os caminhos, ele poderá fornecer o arquivo de senha local.

Se o invasor conseguir roubar a chave secreta de longo prazo usada pelo disposi-
tivo, pode ser impossível recuperar a segurança sem o envolvimento humano. Na
pior das hipóteses, o dispositivo pode precisar ser substituído ou restaurado para
as configurações de fábrica e reconfigurado. Os mecanismos de catraca discutidos
na seção 12.4.3 não protegem contra comprometimento, porque se o invasor obti-
ver acesso à chave de catraca atual, ele poderá calcular facilmente todas as cha-
ves futuras.
Medidas de segurança de hardware, como um elemento seguro, TPM ou TEE (con-
sulte a seção 12.4.1) podem fornecer segurança pós-comprometimento, garan-
tindo que um invasor nunca obtenha acesso direto à chave secreta. Um invasor
que tenha controle ativo do dispositivo pode usar o hardware para comprometer
as comunicações enquanto tiver acesso, mas uma vez que o acesso seja removido,
ele não poderá mais descriptografar ou interferir em comunicações futuras.

Uma forma mais fraca de segurança pós-compromisso pode ser alcançada se uma
fonte externa de material de chave for misturada em um processo de catraca peri-
odicamente. Se o cliente e o servidor concordarem com esse material de chave
sem que o invasor saiba disso, quaisquer novas chaves derivadas serão imprevisí-
veis para o invasor e a segurança será restaurada. Isso é mais fraco do que usar
hardware seguro, porque se o invasor roubou a chave do dispositivo, então, em
princípio, ele pode espionar ou interferir em todas as comunicações futuras e in-
terceptar ou controlar esse material de chave. No entanto, se até mesmo uma
única troca de comunicação puder ocorrer sem a interferência do invasor, a segu-
rança poderá ser restaurada.

Existem dois métodos principais para trocar material de chave entre o servidor e
o cliente:
Eles podem trocar diretamente novos valores aleatórios criptografados usando
a chave antiga. Por exemplo, um servidor de distribuição de chaves pode en-
viar periodicamente ao cliente uma nova chave criptografada com a antiga,
conforme descrito na seção 12.4.2, ou ambas as partes podem enviar nonces
aleatórios que são misturados no processo de derivação de chave usado na ca-
traca (seção 12.4 .3). Essa é a abordagem mais fraca porque um invasor passivo
capaz de espionar pode usar os valores aleatórios diretamente para derivar as
novas chaves.
Eles podem usar o acordo de chave Diffie-Hellmancom novas chaves aleatórias
(efêmeras) para derivar um novo material de chave. Diffie-Hellman é um algo-
ritmo de chave pública no qual o cliente e o servidor apenas trocam chaves pú-
blicas, mas usam chaves privadas locais para derivar um segredo comparti-
lhado. O Diffie-Hellman é seguro contra bisbilhoteiros passivos, mas um inva-
sor capaz de representar o dispositivo com uma chave secreta roubada ainda
pode realizar um ataque man-in-the-middle ativocomprometer a segurança. Os
dispositivos IoT implantados em locais acessíveis podem ser particularmente
vulneráveis ​a ataques man-in-the-middle porque um invasor pode ter acesso fí-
sico às conexões de rede.

DEFINIÇÃO Aataque man-in-the-middle (MitM)ocorre quando um invasor in-


terfere ativamente nas comunicações e se faz passar por uma ou ambas as partes.
Protocolos como o TLS contêm proteções contra ataques MitM, mas ainda podem
ocorrer se chaves secretas de longo prazo usadas para autenticação forem
comprometidas.

A segurança pós-compromisso é uma meta difícil de alcançar e a maioria das so-


luções vem com custos em termos de requisitos de hardware ou criptografia mais
complexa. Em muitos aplicativos de IoT, o orçamento seria melhor gasto tentando
evitar o comprometimento em primeiro lugar, mas para dispositivos ou dados
particularmente sensíveis, você pode querer considerar a adição de um elemento
seguro ou outro mecanismo de segurança de hardware aos seus dispositivos.

questionário

7. Verdadeiro ou falso: Ratcheting pode fornecer segurança pós-compromisso.

A resposta está no final do capítulo.

Respostas para perguntas do questionário

1. b. NEED_WRAP indica que o SSLEngine precisa enviar dados para a outra parte
durante o handshake.
2. b. O AES-GCM falha catastroficamente se um nonce for reutilizado, e isso é mais
provável em aplicativos IoT.
3. Falso. Novas chaves são derivadas para cada sessão trocando valores aleatórios
durante o handshake.
4. d. O acordo de chaves Diffie-Hellman com novos pares de chaves efêmeras é
usado para garantir o sigilo de encaminhamento.
5. b. Os modos MRAE são mais robustos no caso de reutilização nonce.
6. Falso. O SIV-AES é menos seguro se um nonce for reutilizado, mas perde uma
quantidade relativamente pequena de segurança em comparação com outros
modos. Você ainda deve tentar usar nonces exclusivos para cada mensagem.
7. Falso. A catraca atinge o sigilo de encaminhamento, mas não a segurança pós-
compromisso. Depois que um invasor compromete a chave de catraca, ele pode
derivar todos osfuturochaves.

Resumo

Os dispositivos IoT podem ser limitados em poder de CPU, memória, armazena-


mento ou capacidade de rede ou duração da bateria. As práticas de segurança
de API padrão, baseadas em protocolos e tecnologias da Web, são pouco ade-
quadas a esses ambientes e alternativas mais eficientes devem ser usadas.
Protocolos de rede baseados em UDP podem ser protegidos usando Datagrama
TLS. Podem ser usados ​conjuntos de cifras alternativos que são mais adequados
para dispositivos restritos, como aqueles que usam AES-CCM ou ChaCha20-
Poly1305.
Os certificados X.509 são complexos para verificar e exigem validação de assi-
natura adicional e código de análise, aumentando o custo de suporte a comuni-
cações seguras. As chaves pré-compartilhadas podem eliminar essa sobrecarga
e usar criptografia simétrica mais eficiente. Dispositivos mais capazes podem
combinar conjuntos de cifras PSK com Diffie-Hellman efêmero para obter sigilo
avançado.
As comunicações IoT geralmente precisam atravessar vários saltos de rede em-
pregando diferentes protocolos de transporte. A criptografia e a autenticação
de ponta a ponta podem ser usadas para garantir que a confidencialidade e a
integridade das solicitações e respostas da API não sejam comprometidas se um
host intermediário for atacado. Os padrões COSE fornecem recursos semelhan-
tes ao JOSE com melhor adequação para dispositivos IoT, mas alternativas
como o NaCl podem ser mais simples e seguras.
Dispositivos restritos geralmente não têm acesso a boas fontes de entropia para
gerar nonces aleatórios, aumentando o risco de vulnerabilidades de reutiliza-
ção de nonce. Os modos de criptografia de autenticação resistentes ao uso inde-
vido, como SIV-AES, são uma escolha muito mais segura para esses dispositivos
e oferecem benefícios semelhantes ao AES-CCM para o tamanho do código.
A distribuição de chaves é um problema complexo para ambientes IoT, que
pode ser resolvido por meio de técnicas simples de gerenciamento de chaves,
como o uso de servidores de distribuição de chaves. Um grande número de cha-
ves de dispositivo pode ser gerenciado por meio da derivação de chave, e ca-
traca pode ser usada para garantir sigilo de encaminhamento. Os recursos de
segurança de hardware fornecem proteção adicional contradispositivos.

1.
O DTLS está limitado a proteger conexões UDP unicast e não pode proteger trans-
missões multicast atualmente.

2.
Consulte o capítulo 3 se você ainda não instalou o mkcert.

3.
Consulte https://wiki.mozilla.org/Security/Server_Side_TLS .

4.
Thomas Pornin, o autor da biblioteca BearSSL, tem notas detalhadas sobre o custo
de diferentes algoritmos criptográficos TLS em https://bearssl.org/support.html .

5.
ChaCha20-Poly1305 também sofre de problemas de reutilização nonce semelhan-
tes ao GCM, mas em menor grau. GCM perde todas as garantias de autenticidade
após a reutilização de um único nonce, enquanto ChaCha20-Poly1305 só perde es-
sas garantias para mensagens criptografadas com o nonce duplicado.
6.
O suporte para X25519 também foi adicionado ao TLS 1.2 e anterior em uma atua-
lização subsequente; consulte https://tools.ietf.org/html/rfc8422 .

7.
O autor da implementação de referência, Jim Schaad, também administra uma vi-
nícola chamada August Cellars em Oregon, caso você esteja se perguntando sobre
o nome de domínio.

8.
É lamentável que o COSE tente lidar com ambos os casos em uma única classe de
algoritmos. Exigir a função de expansão para HKDF com uma função de hash é
ineficiente quando a entrada já é uniformemente aleatória. Por outro lado, ig-
norá-lo para AES é potencialmente inseguro se a entrada não for uniformemente
aleatória.

9.
Os mais atentos podem perceber que esta é uma variação do esquema MAC-then-
Encrypt que dissemos no capítulo 6 não é garantido como seguro. Embora isso ge-
ralmente seja verdade, o modo SIV tem uma prova de segurança, portanto, é uma
exceção à regra.

10.
Com 4,5 MB, o Bouncy Castle não se qualifica como uma implementação com-
pacta, mas mostra como o SIV-AES pode ser facilmente implementado no
servidor.
11.
O serviço de mensagens seguras Signal é famoso por seu algoritmo de “catraca
dupla” ( https://signal.org/docs/specificações/doubleratchet/ ), que garante que
uma nova chave seja derivada após cada mensagem.

12.
As explorações reais de passagem de caminho geralmente são mais complexas do
que isso, contando com bugs sutis nas rotinas de análise de URL.

Você também pode gostar