changeset 51:58d5a0b6e5b3

Implement the OpenBSD variant (with --base64) to encode digests in base64, not hexadecimal
author Franz Glasner <f.glasner@feldmann-mg.com>
date Wed, 26 Jan 2022 14:15:43 +0100
parents cc3d6fe7eda7
children 5935055edea6
files shasum.py
diffstat 1 files changed, 53 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- 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):