# HG changeset patch # User Franz Glasner # Date 1643202943 -3600 # Node ID 58d5a0b6e5b38ab48b8590161b783586f8faaac6 # Parent cc3d6fe7eda786183314c8566be8bc39ea813e33 Implement the OpenBSD variant (with --base64) to encode digests in base64, not hexadecimal diff -r cc3d6fe7eda7 -r 58d5a0b6e5b3 shasum.py --- a/shasum.py Wed Jan 19 09:23:06 2022 +0100 +++ b/shasum.py Wed Jan 26 14:15:43 2022 +0100 @@ -23,6 +23,8 @@ import argparse +import base64 +import binascii import hashlib import io try: @@ -49,6 +51,9 @@ "--algorithm", "-a", action="store", type=argv2algo, help="1 (default), 224, 256, 384, 512, 3-224, 3-256, 3-384, 3-512, blake2b, blake2s, md5") aparser.add_argument( + "--base64", action="store_true", + help="output checksums in base64 notation, not hexadecimal (OpenBSD).") + aparser.add_argument( "--binary", "-b", action="store_false", dest="text_mode", default=False, help="read in binary mode (default)") aparser.add_argument( @@ -103,7 +108,7 @@ def gen_opts(files=[], algorithm="SHA1", bsd=False, text_mode=False, - checklist=False, check=False, dest=None): + checklist=False, check=False, dest=None, base64=False): if text_mode: raise ValueError("text mode not supported") if checklist and check: @@ -115,7 +120,8 @@ checklist=checklist, check=check, text_mode=False, - dest=dest) + dest=dest, + base64=base64) return opts @@ -145,17 +151,45 @@ compute_digest_stream(opts.algorithm[0], source), None, opts.algorithm[1], - True) + True, + opts.base64) else: for fn in opts.files: out(opts.dest or sys.stdout, compute_digest_file(opts.algorithm[0], fn), fn, opts.algorithm[1], - True) + True, + opts.base64) return 0 +def compare_digests_equal(given_digest, expected_digest, algo): + """Compare a newly computed binary digest `given_digest` with a digest + string (hex or base64) in `expected_digest`. + + :param bytes given_digest: + :param str expected_digest: hexlified or base64 encoded digest + :param algo: The algorithm (factory) + :return: `True` if the digests are equal, `False` if not + :rtype: bool + + """ + if len(expected_digest) == algo().digest_size * 2: + # hex + try: + exd = binascii.unhexlify(expected_digest) + except TypeError: + return False + else: + # base64 + try: + exd = base64.b64decode(expected_digest) + except TypeError: + return False + return given_digest == exd + + def verify_digests_with_checklist(opts): dest = opts.dest or sys.stdout exit_code = 0 @@ -174,7 +208,7 @@ else: tag, algo, cl_filename, cl_digest = pl computed_digest = compute_digest_stream(algo, source) - if cl_digest.lower() == computed_digest.lower(): + if compare_digests_equal(computed_digest, cl_digest, algo): res = "OK" else: res = "FAILED" @@ -189,7 +223,7 @@ else: tag, algo, cl_filename, cl_digest = pl computed_digest = compute_digest_file(algo, fn) - if cl_digest.lower() == computed_digest.lower(): + if compare_digests_equal(computed_digest, cl_digest, algo): res = "OK" else: exit_code = 1 @@ -236,7 +270,7 @@ tag, algo, fn, digest = parts try: d = compute_digest_file(algo, fn) - if d.lower() == digest.lower(): + if compare_digests_equal(d, digest, algo): return ("ok", fn, tag) else: return ("failed", fn, tag) @@ -377,11 +411,15 @@ raise ValueError("unknown algorithm: {}".format(s)) -def out_bsd(dest, digest, filename, digestname, binary): +def out_bsd(dest, digest, filename, digestname, binary, use_base64): """BSD format output, also :command:`openssl dgst` and :command:`b2sum --tag" format output """ + if use_base64: + digest = base64.b64encode(digest).decode("ascii") + else: + digest = binascii.hexlify(digest).decode("ascii") if filename is None: print(digest, file=dest) else: @@ -391,10 +429,14 @@ file=dest) -def out_std(dest, digest, filename, digestname, binary): +def out_std(dest, digest, filename, digestname, binary, use_base64): """Coreutils format (:command:`shasum` et al.) """ + if use_base64: + digest = base64.b64encode(digest).decode("ascii") + else: + digest = binascii.hexlify(digest).decode("ascii") print("{} {}{}".format( digest, '*' if binary else ' ', @@ -454,7 +496,7 @@ mapsize = rest finally: os.close(fd) - return h.hexdigest() + return h.digest() def compute_digest_stream(hashobj, instream): @@ -473,7 +515,7 @@ if len(buf) == 0: break h.update(buf) - return h.hexdigest() + return h.digest() def normalize_filename(filename, strip_leading_dot_slash=False):