scripts/python/hashcheck.py
2024-03-20 11:28:46 -05:00

135 lines
4.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Given a hash checksum file, validate it's checksums
SPDX-License-Identifier: MIT
"""
import argparse
import hashlib
import os
import signal
import sys
__version__ = "0.0.1"
def parse_args():
"""Argument parsing routine"""
parser = argparse.ArgumentParser(description="hashcheck")
parser.add_argument(
"--version", action="version", version=__version__, help="Display the version"
)
parser.add_argument(
"-c", "--checksum", required=True, dest="checksum", help="Checksum file to read"
)
parser.add_argument(
"-t",
"--type",
required=False,
dest="type",
default="auto",
choices=["auto", "md5", "sha1", "sha224", "sha256", "sha384", "sha512"],
help=(
"Hash type: auto (default), md5, sha1, sha224, " "sha256, sha384, sha512"
),
)
return parser.parse_args()
def sigbye_handler(signal, frame):
"""Exit function triggered by caught signals"""
sys.exit(0)
def guess_type(hstring):
"""Given an input string, guess the crypto hash based on length"""
hexsize = {
32: "md5",
40: "sha1",
56: "sha224",
64: "sha256",
96: "sha384",
128: "sha512",
}
return hexsize.get(len(hstring.strip()), "NaN")
if __name__ == "__main__":
"""Main entry point for hashcheck"""
# register a clean shutdown for the usual signals
signal.signal(signal.SIGINT, sigbye_handler)
signal.signal(signal.SIGQUIT, sigbye_handler)
signal.signal(signal.SIGTERM, sigbye_handler)
# parse cmdline arguments
args = parse_args()
# for each line in the checksum, calculate the hash of the file
# and compare it against the digest it's supposed to be
try:
with open(args.checksum, "r") as csum:
# a hash file 99.999% of the time has a relative path of the
# file, so we'll attempt to cwd() there to help the user out
startdir = os.getcwd()
if os.path.dirname(args.checksum):
# if already in the directory, dirname() returns '' which is
# an exception result when used with chdir()
os.chdir(os.path.dirname(args.checksum))
# read our checksum and do the needful
for line in csum:
line = line.rstrip("\n")
# hash checksum = "abcdef file.name" where the second
# "space" is actually one of: *, ?, ^, or ' ' (space)
hparts = line.split(" ", 1)
if len(hparts) != 2:
print("Invalid line, skipping.")
continue
# hparts[0] = hash, hparts[1] = 1char type + name
# in auto mode, we can try and guess the type of hash used
# by the checksum length, which allows for mixed types of
# hashes in one file; otherwise, we have to use what was
# indicated on the cli
htype = args.type
if htype == "auto":
htype = guess_type(hparts[0])
if htype == "NaN":
print("Unable to guess hash type: {}".format(line))
continue
# attempt to instantiate the hashlib.foo()
try:
hasher = hashlib.new(htype)
except AttributeError as error:
print("Python hashlib does not support hash: {}".format(args.type))
continue
# calculate the hash of the file and compare
try:
# could be hashin a 4G iso file, calculate in chunks
with open(hparts[1][1:], "rb") as hfile:
for chunk in iter(lambda: hfile.read(4096), b""):
hasher.update(chunk)
# simply compare our two values
if hasher.hexdigest() == hparts[0]:
print("Pass: {}".format(line))
else:
print("Fail: {}".format(line))
except (IOError, OSError) as error:
# py2 = IOError, py3 = FileNotFound
print("Unable to open file: {}".format(hparts[1][1:]))
continue
# usually python exits and leaves the user where they began,
# trust but verify - doesn't hurt to make sure
os.chdir(startdir)
except (IOError, OSError) as error:
# py2 = IOError, py3 = FileNotFound
print("Unable to open checksum file: {}".format(args.checksum))
sys.exit(1)
sys.exit(0)