From c7241259f443c9520d97c79c2c27cf384f7e1c67 Mon Sep 17 00:00:00 2001 From: "brian m. carlson" Date: Mon, 11 Apr 2022 20:11:33 +0000 Subject: [PATCH] script: add a script to hash files Right now, we provide signed SHA-256 hashes for our releases. This is fine and sufficient, and also cryptographically secure. However, many distributors use other algorithms, and it would be convenient if we could provide easy access to those hashes as well. For example, NetBSD uses SHA-512 and BLAKE2s. Let's add a script to hash files with various algorithms and output them in the BSD format. The advantage of the BSD format over the traditional GNU format is that it includes the hash algorithm, which allows us to distinguish between hashes of the same length, such as SHA-256, SHA-512/256, and SHA3-256. It is generated by shasum, sha*sum, sha3sum, and b2sum with the --tag format, and all of these programs accept it for verification with no problems. Using the BSD format means that we need only provide one additional file with all the additional algorithms. There is therefore no need to add multiple new files, and if we desire to add additional algorithms in the future, that's easily done without modification. For aesthetics, we sort first by hash name and then by filename in the output. Unlike sorting with `sort`, this keeps the SHA-2 and SHA-3 algorithms separate instead of interspersing them, which aids in reading. Add some comments because the algorithm, while logical, is somewhat subtle. --- script/hash-files | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 script/hash-files diff --git a/script/hash-files b/script/hash-files new file mode 100755 index 00000000..42aedc27 --- /dev/null +++ b/script/hash-files @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require "openssl" + +# This maps the OpenSSL name to the name used in the output file. +# The order used is the order they should appear in the output file. +DIGESTS = { + 'BLAKE2b512' => 'BLAKE2b', + 'BLAKE2s256' => 'BLAKE2s', + 'SHA256' => 'SHA256', + 'SHA384' => 'SHA384', + 'SHA512' => 'SHA512', + 'SHA512-256' => 'SHA512/256', + 'SHA3-256' => 'SHA3-256', + 'SHA3-384' => 'SHA3-384', + 'SHA3-512' => 'SHA3-512', +} + +class Hasher + def initialize(file) + @file = file + @hashers = DIGESTS.map do |openssl, output| + [output, OpenSSL::Digest.new(openssl)] + end.to_h + end + + def update(s) + @hashers.values.each { |h| h.update(s) } + end + + def to_a + @hashers.map do |name, ctx| + "#{name} (#{@file}) = #{ctx.digest.unpack("H*")[0]}\n" + end.to_a + end +end + +results = [] +ARGV.each do |file| + f = File.open(file) + h = Hasher.new(file) + while chunk = f.read(65536) do + h.update(chunk) + end + results += h.to_a +end + +# Sort entries first by order of algorithm name in DIGESTS, then by filename, +# then print them. + +# Create a mapping of output name digest to order in the hash. +names = DIGESTS.values.each_with_index.to_a.to_h +results.sort_by do |s| + # Split into digest name and remainder. The remainder starts with the + # filename. + pair = s.split(' ', 2).to_a + # Order by the index of the digest and then the filename. + [names[pair[0]], pair[1]] +end.each { |l| puts l }