Capítulo 15 — Infraestrutura crítica e segurança setorial: laboratório guiado
Mapa da aula
Aula 15 publicada. Mapa, exemplo, EX043.
flowchart TD
N0["15.1 Integridade e autenticidade"]
N1["15.2 Forma canônica"]
N2["15.3 Comparação em tempo constante"]
N3["15.4 Atualidade e ordem"]
N4["15.5 Construção guiada"]
N5["15.6 O que esta construção não faz"]
N6["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX043"]
Desenho lógico
flowchart LR
N0["15.1 Integridade e autenticidade"]
N1["15.2 Forma canônica"]
N2["15.3 Comparação em tempo constante"]
N3["15.4 Atualidade e ordem"]
N4["15.5 Construção guiada"]
N5["15.6 O que esta construção não faz"]
N6["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX043"]
[15.1 Integridade e autenticidade]
|
v
[15.2 Forma canônica]
|
v
[15.3 Comparação em tempo constante]
|
v
[15.4 Atualidade e ordem]
|
v
[15.5 Construção guiada]
|
v
[15.6 O que esta construção não faz]
|
v
[Síntese do capítulo]
|
v
[Exemplo resolvido] --> [Exercício EX043]
Objetivos de aprendizagem e conexão
Depois deste capítulo, você deve ser capaz de:
- explicar o que um código de autenticação de mensagem (MAC) prova e o que ele não prova;
- implementar a selagem e a verificação com HMAC-SHA-256, com comparação em tempo constante;
- acrescentar atualidade (janela de tempo) e ordem (números de sequência) para derrotar a reprodução;
- testar a construção com mensagens deliberadamente ruins e registrar os resultados como evidência.
Esta é a primeira construção guiada do componente central do LANTERN. O validador do Capítulo 3 confere se as mensagens são bem formadas e plausíveis; ele não consegue dizer se uma mensagem realmente veio de uma estação. Aqui você acrescenta a autenticação. O projeto é deliberadamente simples, para que cada linha possa ser entendida; um projeto de produção acrescentaria gestão de chaves e segurança de transporte (veja "O que esta construção não faz", abaixo).
15.1 Integridade e autenticidade
Um hash como o SHA-256 detecta mudanças acidentais, mas qualquer um pode calculá-lo, então um atacante que muda uma mensagem também pode recalcular o seu hash. Um MAC é calculado com uma chave secreta; apenas quem detém a chave consegue produzir um MAC válido. O HMAC, definido na RFC 2104, constrói um MAC a partir de uma função de hash. Se uma mensagem carrega um HMAC válido sob a chave de uma estação, o receptor sabe que ela foi produzida por quem detém a chave e não foi alterada em trânsito.
Um MAC não fornece confidencialidade (a mensagem continua legível), não prova que o sensor da estação está certo e não impede que um atacante reenvie uma mensagem capturada antes — uma reprodução.
15.2 Forma canônica
Um MAC é calculado sobre bytes. Se o remetente e o receptor serializarem a mesma mensagem de formas diferentes — ordem de chaves diferente, espaçamento diferente —, a verificação falha. As mensagens do LANTERN são serializadas como JSON com chaves ordenadas e sem espaços, e o MAC é calculado exatamente sobre esses bytes. Defina sempre a forma canônica de modo explícito.
Um relay que reserializa o JSON interpretado antes de conferir o MAC pode aceitar bytes que a estação nunca assinou.
O Laboratório 15 serializa com chaves ordenadas e sem espaços dos dois lados, de modo que o gateway da estação e o relay calculam o MAC sobre bytes idênticos; uma atualização de firmware do gateway que mudasse o espaçamento apareceria de imediato como uma rajada de falhas de MAC daquela estação.
15.3 Comparação em tempo constante
Comparar o MAC recebido com o esperado usando uma comparação comum de strings pode vazar, pelo tempo, quantos caracteres iniciais coincidiram. O hmac.compare_digest do Python compara num tempo independente de onde as entradas diferem (documentação do hmac do Python). Use-o em toda comparação de MAC.
O Laboratório 15 e o Laboratório 29 usam ambos hmac.compare_digest, nunca ==, e o relay que rejeitou 40 de 40 testes de falha de MAC no Capítulo 23 precisa fazer o mesmo.
Uma comparação comum permitiria que um atacante na rede da estação descobrisse, um caractere de cada vez, um MAC válido para uma leitura de cloro forjada.
15.4 Atualidade e ordem
Duas verificações baratas derrotam a reprodução. Uma janela de tempo rejeita as mensagens cujo carimbo de tempo esteja a mais de um número definido de segundos do relógio do receptor (aqui, 120 s); ela depende de os dois relógios estarem certos (Capítulo 17). Um número de sequência por estação, que cresce a cada mensagem, permite ao receptor rejeitar qualquer mensagem cujo número não seja maior que o último aceito. Os dois valores ficam dentro do corpo autenticado pelo MAC, então um atacante não consegue alterá-los sem invalidar o MAC.
O erro de relógio de 422 segundos da PS-09 mostra o custo: as suas mensagens genuínas falhariam na janela de 120 s.
15.5 Construção guiada
O laboratório abaixo implementa seal (o lado da estação) e Relay.receive (o lado do relay) e executa sete testes: duas mensagens boas, uma reprodução, um valor adulterado, uma mensagem selada com a chave errada, uma mensagem velha e uma mensagem truncada.
Laboratório 15 (salve como lab15.py, execute com python3 lab15.py):
# Lab 15 - guided build: an authenticated telemetry relay message with replay protection (offline, stdlib only)
# Synthetic key and messages. A real deployment keeps the key in a protected store, never in source code.
import hmac, hashlib, json
KEY = bytes.fromhex("6c616e7465726e2d6c61622d6b65792d6e6f742d7265616c2d30303031") # "lantern-lab-key-not-real-0001"
WINDOW = 120 # seconds
def seal(station, tag, value, ts, seq, key=KEY):
body = json.dumps({"st": station, "tag": tag, "v": value, "ts": ts, "seq": seq}, sort_keys=True, separators=(",", ":"))
mac = hmac.new(key, body.encode(), hashlib.sha256).hexdigest()
return body + "." + mac
class Relay:
def __init__(self, key): self.key, self.last = key, {}
def receive(self, wire, now):
try:
body, mac = wire.rsplit(".", 1)
except ValueError:
return "reject: malformed"
good = hmac.new(self.key, body.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(good, mac):
return "reject: bad MAC"
m = json.loads(body)
if abs(now - m["ts"]) > WINDOW:
return "reject: outside time window"
if m["seq"] <= self.last.get(m["st"], -1):
return "reject: replay"
self.last[m["st"]] = m["seq"]
return f"accept {m['st']} {m['tag']}={m['v']}"
r = Relay(KEY)
now = 1_790_000_000
a = seal("PS-04", "LEVEL_M", 3.42, now - 5, 1)
b = seal("PS-04", "LEVEL_M", 3.44, now - 1, 2)
tampered = a.replace('"v":3.42', '"v":9.42')
forged = seal("PS-04", "LEVEL_M", 1.00, now, 3, key=b"guessed-key")
old = seal("PS-07", "CL2_MGL", 0.6, now - 900, 1)
tests = [("first", a), ("second", b), ("replay of first", a), ("tampered value", tampered),
("wrong key", forged), ("stale", old), ("truncated", a[:20])]
for name, wire in tests:
print(f"{name:<16} {r.receive(wire, now)}")
print("MAC length (hex chars):", len(a.rsplit('.', 1)[1]))
Saída esperada (Python 3.13.5):
first accept PS-04 LEVEL_M=3.42
second accept PS-04 LEVEL_M=3.44
replay of first reject: replay
tampered value reject: bad MAC
wrong key reject: bad MAC
stale reject: outside time window
truncated reject: malformed
MAC length (hex chars): 64
Trabalhe nesta ordem: (1) leia seal e identifique a forma canônica; (2) leia receive e liste as verificações, em ordem; (3) preveja o resultado de cada teste; (4) execute; (5) para cada rejeição, nomeie a verificação que a pegou. Depois mude a ordem das verificações para que a verificação de reprodução e a sua atualização de estado rodem antes da verificação do MAC, e pense no que um atacante poderia fazer agora: enviar mensagens não autenticadas com números de sequência altos e bloquear uma estação. A ordem importa — autentique primeiro, depois confie no conteúdo.
15.6 O que esta construção não faz
A chave do laboratório está impressa no código-fonte porque é falsa; em produção, a chave fica num armazenamento protegido, legível apenas pela conta de serviço do relay, e cada estação tem a sua própria chave, para que uma chave roubada não permita a um atacante se passar por todas as estações. As chaves precisam de procedimentos de geração, distribuição, rotação e revogação (Capítulo 18). O transporte aqui não é cifrado; em produção, o trecho relay–historiador usa TLS 1.3 (RFC 8446), e se o trecho da estação pode usar TLS depende do que os dispositivos de campo suportam. Muitos dispositivos de campo mais antigos nem conseguem calcular HMACs; nesse caso, o MAC é aplicado por um pequeno gateway na estação, e o enlace gateway–dispositivo precisa ser protegido fisicamente.
15.7 Erros comuns e limites
- Usar um hash simples e chamá-lo de autenticação.
- Calcular o MAC sobre um objeto reserializado, em vez dos bytes recebidos.
- Comparar MACs com
==. - Atualizar o estado de reprodução antes da autenticação — ou antes que todas as outras verificações tenham passado.
- Uma única chave compartilhada por todas as estações.
- Chaves no código-fonte, em repositórios de configuração ou em tickets.
- Presumir que um MAC válido significa uma leitura correta.
Cada um tem uma consequência no LANTERN. Um hash simples permite que qualquer um que alcance o relay calcule um digest de aparência válida. Uma chave para todas as estações significa que uma chave retirada do gateway da PS-07, onde o arquivo da chave ficava num cartão removível, forja mensagens para todas as catorze estações. E um MAC válido numa leitura de cloro de 9,80 mg/L prova apenas que a PS-07 a enviou, e não que o analisador está certo.
Exercícios — capítulo 15
EX043 — Ordem das verificações. Objetivo: ver por que a autenticação vem primeiro. Entradas: Laboratório 15. Restrições: modifique uma cópia. Etapas: (1) mova a verificação de reprodução para antes da verificação do MAC, mantendo a atualização de estado onde está; (2) monte uma mensagem não autenticada para a PS-04 com seq 1000000 e um MAC errado; (3) envie-a, e depois uma mensagem genuína com seq 3; (4) relate o que acontece e por que a ordem original é mais segura. Evidência esperada: código, saída e explicação. Rubrica: 3 = mostra que, com o estado atualizado antes da autenticação, um atacante consegue bloquear as mensagens futuras de uma estação (ou, se a atualização de estado ficar depois da verificação do MAC, mostra que não há bloqueio e explica por que a posição da atualização é a verdadeira salvaguarda); a explicação se liga a "autenticar, depois confiar"; 2 = observação sem explicação; 1 = não executado; 0 = nada.
EX044 — Chaves por estação. Objetivo: estender o projeto. Entradas: Laboratório 15. Restrições: apenas a biblioteca padrão. Etapas: (1) dê a cada estação a sua própria chave num dicionário; (2) busque a chave pelo ID da estação — mas note que o ID da estação fica dentro do corpo; (3) explique como você pode ler com segurança o ID da estação antes de verificar o MAC, e o que você não pode fazer com ele antes da verificação. Evidência esperada: código e explicação. Rubrica: 3 = interpreta o corpo apenas para selecionar uma chave, verifica o MAC com essa chave antes de usar qualquer outro campo ou atualizar o estado, rejeita estações desconhecidas; explica que o ID não verificado é apenas um seletor de chave; 2 = funciona, explicação rasa; 1 = usa uma única chave; 0 = nada.
EX045 — Tamanho da janela. Objetivo: escolher a janela de atualidade. Entradas: os relógios das estações são mantidos dentro de ±2 s por NTP a partir do relay; a operadora às vezes atrasa mensagens em até 40 s; depois de uma interrupção da operadora, as estações enviam um acúmulo de 24 horas. Restrições: um projeto que trate os três. Etapas: (1) escolha uma janela para as mensagens ao vivo; (2) explique como os envios de acúmulo devem ser tratados sem abrir uma brecha de reprodução. Evidência esperada: meia página. Rubrica: 3 = janela ao vivo de cerca de 60–120 s, com raciocínio; acúmulo aceito por um caminho separado e explícito (por exemplo, um modo de acúmulo sinalizado que ainda exige MAC válido e números de sequência estritamente crescentes, com uma idade limitada e um alerta quando usado); 2 = janela certa, acúmulo inseguro ou vago; 1 = janela muito grande para tudo; 0 = nada.
Síntese do capítulo
Autentique as mensagens com HMAC sobre uma forma canônica definida, compare os MACs em tempo constante e derrote a reprodução com uma janela de tempo e números de sequência por estação levados dentro do corpo autenticado. Autentique primeiro, depois confie no conteúdo e registre-o. Um MAC prova a origem e a integridade, e não a confidencialidade nem a correção do sensor. As chaves precisam do seu próprio ciclo de vida, e cada estação deve ter a sua.
Continue estudando — capítulo 15 de 30
← Capítulo 14 · Índice de capítulos do curso · Capítulo 16 →
Siga para a próxima lição escrita.