Verificação segura de arquivos com Ruby e SHA384
Para verificar um download em Ruby, calcule o hash dos bytes dele com o digest SHA384 do OpenSSL e compare o resultado com um checksum em que você confia. O script abaixo aceita um ou mais pares de arquivo/checksum e retorna um status de saída diferente de zero se alguma verificação falhar, então você pode usá-lo em uma tarefa automatizada.
Comece com um checksum confiável
O SHA-384 pertence à família SHA-2 e produz um digest de 384 bits: 48 bytes, ou 96 caracteres hexadecimais. Use-o quando quem publica o arquivo ou o seu fluxo de trabalho existente fornece checksums SHA384; uma referência gerada com SHA256 não pode ser verificada usando SHA384.
Obtenha o checksum esperado de uma fonte confiável, como a página de release autenticada via HTTPS de quem publica o arquivo ou um manifesto de checksums assinado cuja assinatura você já verificou. Se alguém puder substituir tanto o arquivo quanto o checksum de referência dele, essa pessoa pode fazer a sua verificação passar. O hash detecta alterações em relação a essa referência; ele não torna um arquivo à prova de adulteração nem garante que seja seguro executá-lo. A documentação do OpenSSL do Ruby explica como os digests também fazem parte de assinaturas digitais.
Confira o Ruby e o OpenSSL
Você precisa do Ruby com a extensão OpenSSL. Os comandos aqui foram testados no Linux com Ruby 3.4.10,
Ruby/OpenSSL 3.3.3 e OpenSSL 3.6.4. Os exemplos de shell usam Bash; o sha384sum do GNU Coreutils é
opcional para a comparação independente mais abaixo. Se o Ruby não estiver instalado, siga o
guia oficial de instalação do Ruby para a sua plataforma.
Confira o interpretador, a biblioteca vinculada e a disponibilidade do SHA384 antes de continuar:
ruby -ropenssl -e 'puts RUBY_DESCRIPTION; puts "Ruby/OpenSSL #{OpenSSL::VERSION}"; puts OpenSSL::OPENSSL_LIBRARY_VERSION; puts "SHA384: #{OpenSSL::Digest.new("SHA384").digest_length} bytes"'
A última linha deve ser SHA384: 48 bytes. Se o carregamento do OpenSSL ou a criação do digest falhar,
corrija a sua instalação do Ruby antes de executar o verificador. O comando openssl no seu PATH pode
usar uma biblioteca diferente da do Ruby, então apenas a versão dele não comprova que esse
pré-requisito funciona.
Salve o verificador
Salve este script completo como verify_sha384.rb. Ele lê cada arquivo em blocos de 8 KiB e reutiliza o
buffer, então não carrega o arquivo inteiro na memória. O
IO#read do Ruby retorna nil no fim do arquivo
quando recebe um tamanho positivo, inclusive para um arquivo vazio. Cada bloco é passado para
OpenSSL::Digest#update.
require 'openssl'
def sha384_hash(path)
digest = OpenSSL::Digest.new('SHA384')
File.open(path, 'rb') do |file|
buffer = ''.b
digest.update(buffer) while file.read(8192, buffer)
end
digest.hexdigest
end
if ARGV.empty? || ARGV.length.odd?
warn 'Usage: ruby verify_sha384.rb FILE SHA384 [FILE SHA384 ...]'
exit 2
end
failed = false
ARGV.each_slice(2) do |path, expected|
unless /\A[0-9a-fA-F]{96}\z/.match?(expected)
warn "#{path}: ERROR (expected 96 hexadecimal characters)"
failed = true
next
end
begin
actual = sha384_hash(path)
if actual == expected.downcase
puts "#{path}: OK"
else
warn "#{path}: FAILED (SHA384 mismatch)"
failed = true
end
rescue SystemCallError, IOError => error
warn "#{path}: ERROR (#{error.message})"
failed = true
end
end
exit(failed ? 1 : 0)
Passe apenas o digest, sem nome de arquivo, prefixo sha384- ou espaços ao redor. Letras
hexadecimais maiúsculas são aceitas. O script compara checksums públicos com ==; neste fluxo de
trabalho, não existe um checksum secreto a ser protegido contra vazamento por tempo de execução. Para
autenticadores secretos, como HMACs, o Ruby oferece
métodos de comparação em tempo constante em vez de exigir um laço de
comparação escrito à mão.
Verifique um arquivo com resultado conhecido
Em um diretório de rascunho que contenha verify_sha384.rb, execute os comandos a seguir no Bash. Eles criam
example.txt contendo exatamente os três bytes abc, sem quebra de linha no final. A criação se
recusa a sobrescrever um arquivo existente; se ela falhar, a cadeia && para antes da
verificação. Use outro diretório de rascunho se quiser repetir a preparação.
ruby -e 'File.open("example.txt", "wbx") { |file| file.write("abc") }' &&
EXPECTED_SHA384='cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7' &&
ruby verify_sha384.rb example.txt "$EXPECTED_SHA384"
O resultado é:
example.txt: OK
O comando termina com status 0. Para o seu próprio download, substitua pelo caminho dele e
pelo checksum SHA384 confiável daquela release exata. Coloque entre aspas os caminhos que contêm
espaços. O verificador abre os arquivos somente para leitura e não cria nenhum artefato de
verificação.
Se você tiver o GNU Coreutils, calcule de forma independente o checksum do exemplo com
sha384sum:
sha384sum --binary -- example.txt
O primeiro campo deve ser igual a EXPECTED_SHA384. Calcular um hash de um arquivo que você acabou de
baixar é útil para comparação, mas isso por si só não fornece uma referência confiável para esse
download.
Faça a tarefa falhar quando o lote falhar
Continue usando a mesma sessão do Bash para que EXPECTED_SHA384 continue definido. Crie um arquivo vazio e
verifique os dois exemplos passando outro par de arquivo/checksum:
ruby -e 'File.open("empty.bin", "wbx") {}' &&
EMPTY_SHA384='38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b' &&
ruby verify_sha384.rb example.txt "$EXPECTED_SHA384" empty.bin "$EMPTY_SHA384"
Os dois arquivos devem informar OK, e o comando termina com 0. A criação de empty.bin
também se recusa a usar um destino existente. Agora acrescente uma quebra de linha ao primeiro
exemplo descartável e repita o lote:
ruby -e 'File.open("example.txt", "ab") { |file| file.write("\n") }' &&
ruby verify_sha384.rb example.txt "$EXPECTED_SHA384" empty.bin "$EMPTY_SHA384"
example.txt informa FAILED (SHA384 mismatch) na saída de erro padrão, enquanto empty.bin ainda informa
OK na saída padrão. O lote termina com 1: um sucesso posterior não apaga uma falha
anterior. O script verifica todos os pares fornecidos e não reescreve nenhum dos checksums de
referência.
Os códigos de saída são 0 quando todos os arquivos correspondem, 1 quando algum arquivo não
corresponde, tem um checksum esperado malformado ou não pode ser lido, e 2 quando a lista de
pares está ausente ou incompleta. Em uma tarefa, faça do verificador o último comando da etapa dele
ou propague explicitamente o status diferente de zero antes de executar os comandos seguintes. Apenas
imprimir uma mensagem de erro não faz uma tarefa de shell falhar.
Diagnostique uma verificação com falha
- Esperados 96 caracteres hexadecimais: copie apenas o digest SHA384. Um checksum SHA256, um
valor em Base64 ou a linha inteira de saída do
sha384sumnão são argumentos válidos. - SHA384 não corresponde: confira a release, o nome do arquivo, se o download está completo e o algoritmo esperado. Não substitua a referência pelo novo hash só para fazer a verificação passar.
- Erro de arquivo ou de permissão: confira o diretório de trabalho, o caminho e as permissões de
leitura da conta que executa a tarefa. Uma leitura com falha retorna
1mesmo que outros arquivos passem.
Preserve os bytes que você pretende verificar
O modo binário preserva os bytes fornecidos ao digest. A
documentação de modos de arquivo do Ruby descreve como 'b' desativa a
conversão de quebras de linha do Windows. Isso não torna idênticos um arquivo com LF e um arquivo com
CRLF: esses arquivos contêm bytes diferentes e devem ter checksums diferentes. Evite normalizar
quebras de linha, remover espaços em branco ou recodificar o texto antes desta verificação. Essas
etapas fariam você verificar um conteúdo transformado em vez do arquivo baixado.
Use arquivos regulares completos que permanecerão inalterados durante a verificação e o uso posterior. Esta pequena CLI não bloqueia arquivos nem impede que outro processo os substitua depois de uma verificação bem-sucedida. Mantenha protegida tanto a referência confiável quanto o arquivo que você verificou.
Para calcular hashes dentro de um fluxo de trabalho da Transloadit, consulte a documentação do /file/hash. A mesma exigência de uma referência confiável se aplica quando um checksum é gerado durante o processamento de arquivos.
