Documento Técnico · Hash & Assinatura · Público
Especificação de Geração de Hash
em Gravações Fato
Especificação detalhada do procedimento de cálculo, encadeamento e assinatura criptográfica dos hashes que asseguram a integridade de cada gravação. Acompanha o Documento Técnico de Segurança.
Parâmetros aplicáveis
Os parâmetros abaixo regem a geração de hash em todas as gravações finalizadas pela plataforma Fato a partir da versão 1.1.
| Função de hash | SHA-256 (FIPS 180-4 § 6.2) |
| Tamanho do hash | 256 bits — 32 bytes — 64 caracteres hexadecimais |
| Tamanho do bloco de vídeo | ≈ 1 segundo · segmento fMP4 · variável conforme bitrate |
| Encadeamento | hash_n = SHA-256(hash_{n−1} ‖ bloco_n) |
| Estado inicial | hash_{−1} = b"" (sequência vazia) |
| Hash final do arquivo | SHA-256 do conteúdo binário completo |
| Assinatura do manifesto | Ed25519 (RFC 8032) |
| Resistência a colisão | ≈ 2128 operações — impraticável com hardware atual [4] |
Justificativa da escolha
SHA-256 é função de hash criptográfica padronizada pelo NIST em FIPS 180-4 [1]. Transforma sequência binária de comprimento arbitrário em saída fixa de 256 bits. Três propriedades a tornam adequada à cadeia de custódia de evidências digitais:
| Determinismo | A mesma entrada produz invariavelmente a mesma saída; permite verificação repetida por terceiros independentes. |
| Sensibilidade | A alteração de um único bit na entrada modifica aproximadamente 50% dos bits da saída (efeito avalanche). |
| Unidirecionalidade | É computacionalmente inviável recuperar a entrada a partir do hash, ou produzir entrada distinta com idêntico hash. |
SHA-256 foi escolhida em detrimento de SHA-3 e BLAKE3 pela ampla aceitação em ferramentas de perícia digital nacionais e internacionais, pelo suporte universal em sistemas operacionais e linguagens de programação, e pela maturidade do algoritmo (publicação inicial em 2002, revisão em 2015).
Cálculo individual por segmento
O cliente envia o vídeo ao servidor em segmentos fMP4 de aproximadamente um segundo. Para cada segmento recebido, o servidor calcula o hash imediatamente — antes mesmo da confirmação da escrita em disco. Os hashes individuais são mantidos em memória durante a sessão e descartados após o encerramento, prevalecendo o hash encadeado (§ 4).
# Servidor: cálculo do hash de bloco bloco = receber_via_websocket() hash_bloco = sha256(bloco).hexdigest() gravar_em_disco(arquivo_temp, bloco) registrar(blocos_hashes, hash_bloco)
Estrutura cumulativa de hashes
O hash de cada bloco é calculado sobre a concatenação do hash do bloco anterior (estado da cadeia) e do conteúdo binário do bloco corrente. A construção é semelhante à de uma cadeia de blocos: a alteração de qualquer segmento exige o recálculo de todos os hashes subsequentes, e a nova cadeia diferirá da registrada no manifesto contra-assinado pelo servidor.
# Construção da cadeia (executada no servidor)
hash_chain = b"" # estado inicial vazio
for bloco_n in blocos_recebidos:
entrada = hash_chain + bloco_n # concatena estado + bloco
hash_chain = sha256(entrada).digest() # novo estado da cadeia
chain_hash_final = hash_chain.hex() # registrado no manifestoA construção encadeada permite verificação progressiva durante a captura (o servidor identifica blocos corrompidos imediatamente) e demonstra a ordem temporal dos blocos — garantia que o hash único do arquivo finalizado, isoladamente, não oferece.
Cálculo após finalização
Concomitantemente ao encadeamento, o servidor calcula também o SHA-256 simples do arquivo MP4 finalizado. Trata-se do valor de verificação mais simples para terceiros, dado que pode ser recalculado por ferramentas de linha de comando padrão.
$ shasum -a 256 evidencia_8f3c91.mp4 c2d4e1f8a3b9...0e2 evidencia_8f3c91.mp4 $ jq -r '.manifest | fromjson | .file_sha256' manifest.json c2d4e1f8a3b9...0e2 # Coincidência atesta integridade byte-a-byte do arquivo recebido.
Sobre o manifesto
O hash, isoladamente, atesta apenas que o arquivo não sofreu alteração desde determinado instante. A assinatura digital estabelece quem garantiu aquele instante e quando. O servidor assina o manifesto JSON — que contém todos os hashes — com sua chave ed25519 persistente, gerada uma única vez na primeira inicialização da plataforma.
# Servidor: composição e assinatura do manifesto
manifesto = {
"session_id": session_id,
"client_pubkey": pubkey_do_cliente,
"metadata_signature": metadata_sig_cliente,
"file_sha256": hash_arquivo_completo,
"events_log_sha256": hash_log_de_eventos,
"terminal_chunk_hash_server": hash_terminal_do_servidor,
"terminal_chunk_hash_client": hash_terminal_do_cliente,
"chunk_chain_verified": os_dois_conferem,
"tamper_detected": tamper_detected,
"tamper_events_sha256": hash_dos_eventos_de_adulteracao,
}
# JSON canônico: chaves ordenadas e separadores compactos. É esta string
# exata que é assinada, e é a sequência de bytes guardada em envelope
# ["manifest"] que o verificador confere — ele NÃO reserializa o JSON, porque
# reserializar pode produzir bytes diferentes dos que foram assinados.
manifesto_canonico = json.dumps(
manifesto, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
assinatura = chave_servidor.sign(manifesto_canonico.encode("utf-8"))
# Um único artefato: o envelope carrega a string assinada, a assinatura e a
# chave pública que o pacote declara — conferida contra a chave confiável
# informada em --server-pubkey, nunca aceita por si só.
envelope = {
"manifest": manifesto_canonico,
"signature": base64url(assinatura),
"server_public_key": base64url(chave_servidor.public_key),
}
gravar_em_s3("manifest.json", envelope)| Chave do servidor | Gerada uma vez na primeira inicialização. Persistida em data/server_key.seed com permissão restrita ao processo. |
| Chave pública | Publicada no portal. Pode ser fixada (pinning) por escritórios de advocacia para verificação prévia. |
| Serialização canônica | O JSON é serializado com chaves ordenadas alfabeticamente, de modo a tornar a verificação determinística em qualquer biblioteca conforme RFC 8259. |
Verificador independente em Python
A forma mais simples de verificar é a página /verify do portal: envie o pacote .zip da evidência e informe o SHA-256 do vídeo. Para verificação offline e independente da infraestrutura Fato, o projeto autônomo verifier executa as mesmas conferências sobre os artefatos extraídos do pacote — o vídeo, o manifest.json assinado e o log de eventos selado —, exigindo apenas a biblioteca cryptography.
O comando abaixo é a verificação real, não um exemplo. O verificador é um único arquivo Python auditável na íntegra, com uma única dependência de runtime: baixar verify.py (SHA-256 bf853e2ac29dff87ed40dd4523aa561e976a032f4077de2a7099a45299c2b38f). Requer Python 3.9 ou superior.
pip install 'cryptography>=42,<47'
python verify.py --manifest manifest.json \
--file <gravacao>.webm \
--events <id>.events.jsonl \
--server-pubkey <chave-publica-fato>Ele confere: a assinatura ed25519 do servidor sobre o manifesto canônico, contra a chave confiável informada em --server-pubkey — nunca contra a chave que o próprio pacote anuncia; o SHA-256 do vídeo; o SHA-256 do log de eventos selado; a assinatura de metadados do cliente, quando o quadro assinado for fornecido com --client-metadata; e a comparação entre os hashes terminais da cadeia de blocos atestados pelo cliente e pelo servidor, somada ao veredito selado pelo próprio servidor — o principal indicador de adulteração no envio.
Limite desta última conferência, declarado explicitamente: ela compara dois valores atestados dentro do manifesto assinado. O verificador não recalcula a cadeia a partir da gravação, porque os limites e o conteúdo de cada bloco não são registrados em lugar nenhum e não podem ser reconstruídos offline. É a conferência da assinatura do servidor que torna esses dois valores confiáveis.
Código de saída 0 significa verificação completa aprovada; 1, reprovada; 3, incompleta — algum elo não pôde ser conferido com os artefatos fornecidos, o que não equivale a aprovação.
Questões recorrentes
§ 8.1A re-codificação do vídeo preserva a verificabilidade?
Não. Qualquer alteração do conteúdo binário (re-encoding, corte, conversão de contêiner) altera o hash do arquivo. Para entrega em formato distinto, recomenda-se preservar o arquivo original Fato como prova e gerar a cópia adicional com hash próprio anexo.
§ 8.2A indisponibilidade do servidor inviabiliza a verificação?
Não. A verificação independe da infraestrutura Fato. Bastam o arquivo de vídeo, o manifesto, a assinatura do servidor e as duas chaves públicas. O servidor é necessário apenas para download dos artefatos.
§ 8.3O hash atesta a veracidade do conteúdo capturado?
Não. O hash atesta que o vídeo corresponde, byte a byte, ao que foi capturado pelo cliente naquele momento. A análise probatória da veracidade do conteúdo exibido permanece sob competência do operador do Direito.
§ 8.4Por que ed25519 em detrimento de RSA?
Ed25519 oferece nível de segurança equivalente a RSA-3072 com chaves de 32 bytes (contra 384 bytes em RSA) e desempenho substancialmente superior em assinatura e verificação. É o padrão recomendado pelo NIST FIPS 186-5 [3] e adotado em sistemas de cadeia de custódia de evidências digitais.
Bibliografia normativa
- NIST FIPS 180-4 — Secure Hash Standard (SHS). National Institute of Standards and Technology, agosto de 2015. nvlpubs.nist.gov/.../FIPS.180-4
- RFC 8032 — Josefsson & Liusvaara, Edwards-Curve Digital Signature Algorithm (EdDSA). IETF, janeiro de 2017. rfc-editor.org/rfc/rfc8032
- NIST FIPS 186-5 — Digital Signature Standard (DSS). National Institute of Standards and Technology, fevereiro de 2023. nvlpubs.nist.gov/.../FIPS.186-5
- NIST SP 800-107 Rev. 1 — Recommendation for Applications Using Approved Hash Algorithms. Agosto de 2012.
- RFC 8259 — Bray, The JavaScript Object Notation (JSON) Data Interchange Format. IETF, dezembro de 2017. rfc-editor.org/rfc/rfc8259