135 lines
4.6 KiB
Python
Executable file
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)
|