From 9b107af4f85e138d4697a81b0e2a5e035694b332 Mon Sep 17 00:00:00 2001 From: Ron Stoner Date: Mon, 24 Feb 2025 14:58:14 -0700 Subject: [PATCH 1/2] Code cleanup, comments, better error handling --- selfhash/__init__.py | 21 --------------- selfhash/selfhash.py | 64 +++++++++++++++++++++++++++++++++----------- setup.py | 15 ++++++++--- verify_all.py | 48 ++++++++++++++++++++++++--------- verify_auto.py | 6 ----- verify_self.py | 40 +++++++++++++++++++++------ 6 files changed, 127 insertions(+), 67 deletions(-) delete mode 100644 selfhash/__init__.py delete mode 100755 verify_auto.py diff --git a/selfhash/__init__.py b/selfhash/__init__.py deleted file mode 100644 index 87639fe..0000000 --- a/selfhash/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python -# Hash: 62cf98a0590f5ddea116edc071cc70815397ba71d2ff25e30a814ec5598ee3fe - -"""SelfHash __init__.py""" - -import inspect - -from .selfhash import SelfHash - - -def _get_caller_file(): - stack = inspect.stack() - caller_frame = stack[-1] - caller_file = caller_frame.filename - return caller_file - - -# Automatically hash the caller -_caller_file = _get_caller_file() -if _caller_file != '': - SelfHash().hash(_get_caller_file()) diff --git a/selfhash/selfhash.py b/selfhash/selfhash.py index e9dc7c3..80625bb 100644 --- a/selfhash/selfhash.py +++ b/selfhash/selfhash.py @@ -1,49 +1,80 @@ #!/usr/bin/python -# Hash: d758637770374a601330b7e5dc60215505a338702c6188b8563034c3e487ab2b +# Hash: f67323982e945bb945f9fc54ed0b7a8c0d1831b1475e6e89d3e270f9dbffd1dc +# Author: Ron Stoner +# Github: ronaldstoner +# Website: stoner.com -# No password is set for this hash as it is used to verify the selfhash module code itself and can be checked against the github repo +# No password is set for this hash as it is used to verify the selfhash module code itself +# This can be checked against the GitHub repository -"""SelfHash - Self hashing and verification python script""" +"""SelfHash - Self-hashing and verification Python script""" import hashlib import getpass import sys + class SelfHash: - """Class for SelfHash""" + """Class to handle the self-hashing and verification of Python source code.""" + def __init__(self, bypass_salt=False): - """Init function""" - self.file_data_hash = None - self.source_code_hash = None - self.known_hash = None - self.bypass_salt = bypass_salt + """ + Initializes the SelfHash object. + + :param bypass_salt: Flag to bypass the salt input (default is False). + """ + self.file_data_hash = None # Holds the hash of the file data without the hash line + self.source_code_hash = None # Holds the calculated hash of the source code + self.known_hash = None # Holds the known hash from the source code + self.bypass_salt = bypass_salt # Flag to bypass the salt input def hash(self, file): - """Function that hashes the source code""" + """ + Reads the file, calculates its hash, and verifies the integrity of the code by comparing + it with the known hash embedded in the source. + + :param file: The Python file to be hashed and verified. + """ fail = False + # Open the file and read its contents into a list of lines with open(file, 'r', encoding="utf-8") as source_file: file_data = source_file.readlines() + # Ensure the file has at least 2 lines (the second line will contain the hash) + if len(file_data) < 2: + print("Error: The file is too short. It should have at least 2 lines.") + fail = True + sys.exit(1) # Exit immediately if the file is too short + + # Extract the hash from the line and fail if not found try: hash_line_index = [i for i, line in enumerate(file_data) if line.strip().startswith("# Hash:")][0] except IndexError: print("The '# Hash:' line was not found in the file.") print("Please add '# Hash: INSERT_HASH_HERE' at \nthe top of your python file and try again.") fail = True + sys.exit(1) # Exit immediately if the '# Hash: ' is not found - if fail: - sys.exit(1) - + # Remove the hash line from the source file data to compute the rest of the code's hash self.file_data_hash = ''.join([line for i, line in enumerate(file_data) if i != hash_line_index]) + # Prompt for a salt if not bypassing it if not self.bypass_salt: salt = getpass.getpass(prompt='This python script is protected by SelfHash.\nPlease provide a salt for the hash calculation.\nIf you do not want to provide one, just press Enter: ') - self.file_data_hash += salt + if salt: # If a salt is provided, add it to the file data hash + self.file_data_hash += salt + # Calculate the SHA-256 hash of the source code (with salt if provided) self.source_code_hash = hashlib.sha256(self.file_data_hash.encode()).hexdigest() - + + # Compare the known hash to the calculated hash self.known_hash = file_data[hash_line_index].strip().split(' ')[-1] + self.known_hash = self.known_hash.strip() # Clean up extra spaces + + if len(self.known_hash) != 64: # Ensure it is a valid SHA256 hash + print("Invalid hash format found in the file.") + fail = True if self.known_hash in ("Hash:", "INSERT_HASH_HERE"): print("The hash of the source code is not set yet.\nPlease run the script once and then replace INSERT_HASH_HERE with the hash.") @@ -54,6 +85,7 @@ def hash(self, file): else: print("\033[91mFAIL\033[0m: The source code may have been tampered with or the salt/passphrase is incorrect.") fail = True - + + # Exit with error if there was any failure if fail: sys.exit(1) diff --git a/setup.py b/setup.py index 2b41c77..0a57c5e 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,8 @@ #!/usr/bin/python -# Hash: 193dfdda9e08fb513338a76bd17dacf8eb80e6a0fb8cef90bb838466e073f78f +# Hash: 8b74bf84000993da1f0fbb10e97197c7443b978b43edd1fa5f3957e729e49718 +# Author: Ron Stoner +# Github: ronaldstoner +# Website: stoner.com """SelfHash setup.py file package metadata""" @@ -7,16 +10,22 @@ setup( name='selfhash', - version='0.1.1', + version='0.1.2', packages=find_packages(), author='Ron Stoner', author_email='ron@stoner.com', description='A package to self hash and verify a python script', - long_description_content_type='text/markdown', + long_description_content_type='text/markdown; charset=UTF-8', long_description=open('README.md').read(), url='https://github.com/ronaldstoner/selfhash-python', classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'License :: OSI Approved :: MIT License', ], + keywords='self hash verify python script integrity checksum ', ) diff --git a/verify_all.py b/verify_all.py index 8e78557..07e0adc 100755 --- a/verify_all.py +++ b/verify_all.py @@ -1,28 +1,50 @@ #!/usr/bin/python -# Hash: ff09080b23d744ebcc446a3fdce8dc0da5ae1b127517a4d29c3be46219a012c4 +# Hash: 8a26874511080270877d2b3e5f94191fd9e5fd2dad5a191065906c8e66faa781 +# Author: Ron Stoner +# Github: ronaldstoner +# Website: stoner.com -"""Script to verify all python files in a directory recursively""" +"""Script to verify all Python files in a directory recursively""" import glob import selfhash def hash_and_verify_files(directory): - """Hash and verify all found .py files""" - # Use glob to find all .py files in the directory and its subdirectories - for filename in glob.iglob(directory + '**/*.py', recursive=True): - print(f"Processing {filename}...") + """Hash and verify all .py files in the given directory recursively.""" + + # Find all Python files in the directory and its subdirectories + py_files = glob.iglob(directory + '**/*.py', recursive=True) + total_files = sum(1 for _ in py_files) # Count the total number of .py files + print(f"\nTotal Python files found: {total_files}") + + # Re-run glob to get the file list again after counting + py_files = glob.iglob(directory + '**/*.py', recursive=True) - # Instantiate a new selfhash class for each file + for filename in py_files: + print(f"\nProcessing {filename}...") + + # Instantiate the SelfHash class for each file hasher = selfhash.SelfHash(bypass_salt=True) + + # Perform the hash verification for each file hasher.hash(filename) + # Check the hash against the known value and display appropriate messages if hasher.known_hash == "INSERT_HASH_HERE": - print(f"Generated Hash for {filename}: {hasher.source_code_hash}") - print("Please replace INSERT_HASH_HERE with this hash and run the script again.") + print(f"\033[93mWARNING\033[0m: The hash is not set for {filename}.") + print(f"Generated Hash: {hasher.source_code_hash}") + print("Please replace 'INSERT_HASH_HERE' with the generated hash and run the script again.") elif hasher.known_hash == hasher.source_code_hash: - print(f"\033[92mPASS\033[0m: The program {filename} is verified and true.") + print(f"Expected Hash: {hasher.known_hash}") + print(f"Actual Hash: {hasher.source_code_hash}") else: - print(f"\033[91mFAIL\033[0m: The source code of {filename} may have been tampered with.") + print(f"\033[91mFAIL\033[0m: The source code of {filename} may have been tampered with or the hash does not match.") + print(f"Expected Hash: {hasher.known_hash}") + print(f"Actual Hash: {hasher.source_code_hash}") + print("Please investigate this file.\n") -# Run the function starting from the current directory -hash_and_verify_files("./") + print("\nVerification process complete.") + +# Run the verification function starting from the current directory +if __name__ == "__main__": + hash_and_verify_files("./") diff --git a/verify_auto.py b/verify_auto.py deleted file mode 100755 index 7936546..0000000 --- a/verify_auto.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/python -# Hash: 0dc6f9324cdae350bbe56519f1e4cc26d709579744124e16070bf733d6128818 - -"""Script to verify the selfhash/selfhash.py module hash""" - -import selfhash # noqa: F401 diff --git a/verify_self.py b/verify_self.py index 1c789b7..4c4ecd5 100755 --- a/verify_self.py +++ b/verify_self.py @@ -1,14 +1,38 @@ -#!/usr/bin/python -# Hash: 48717260d6a313c4cc1b3d361a852a34bd24e8ba2786c2a08ecfdaf7f1e556c2 +#!/usr/bin/python3 +# Hash: b3bdd013f179bb06e9be80e8282c3d3dcaf34463f33257b88a96769daf51689c +# Author: Ron Stoner +# Github: ronaldstoner +# Website: stoner.com """Script to verify the selfhash/selfhash.py module hash""" import selfhash -# Load selfhash into a hasher and perform the verification check -hasher = selfhash.SelfHash(bypass_salt=True) -hasher.hash("selfhash/selfhash.py") # replace with the actual path to the selfhash.py file +def verify_selfhash(): + """Verifies the hash of the selfhash.py module.""" + + # Instantiate the SelfHash class with bypass_salt=True to skip salt prompt + hasher = selfhash.SelfHash(bypass_salt=True) + + # Perform the hash verification check on the selfhash.py module + print("\nVerifying the hash of the 'selfhash/selfhash.py' module...") + hasher.hash("selfhash/selfhash.py") # Replace with the actual path to selfhash.py if needed + + # Output the calculated hash and comparison results + print("Expected Hash: ", hasher.known_hash) + print("Actual Hash: ", hasher.source_code_hash) + if hasher.known_hash != hasher.source_code_hash: + print("\033[91mFAIL\033[0m: The selfhash.py module has been tampered with or the hash does not match.") + print("Expected Hash: ", hasher.known_hash) + print("Actual Hash: ", hasher.source_code_hash) + exit(1) + elif hasher.known_hash in ("INSERT_HASH_HERE", "Hash:"): + print("\033[93mWARNING\033[0m: The hash is not set yet. Please replace 'INSERT_HASH_HERE' with the calculated hash.") + print("Generated Hash: ", hasher.source_code_hash) + exit(1) + print("\nVerification complete.") + +# Run the verification function +if __name__ == "__main__": + verify_selfhash() -print(hasher.hash) -# This should only run if the hash matches and the program is 'verified' -print("The hash inside of selfhash/selfhash.py matches and looks correct.") From c254637685c1ced06cd87ff7f62c49893ec22fcb Mon Sep 17 00:00:00 2001 From: Ron Stoner Date: Mon, 24 Feb 2025 15:08:21 -0700 Subject: [PATCH 2/2] linter fixes --- selfhash/selfhash.py | 8 ++++---- verify_all.py | 10 +++++----- verify_self.py | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/selfhash/selfhash.py b/selfhash/selfhash.py index 80625bb..3639d92 100644 --- a/selfhash/selfhash.py +++ b/selfhash/selfhash.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# Hash: f67323982e945bb945f9fc54ed0b7a8c0d1831b1475e6e89d3e270f9dbffd1dc +# Hash: 7133c3ec57f1dbdd2b35a409abebd34ea0736fde377056706b32bf955ae7d313 # Author: Ron Stoner # Github: ronaldstoner # Website: stoner.com @@ -15,8 +15,8 @@ class SelfHash: + """Class to handle the self-hashing and verification of Python source code.""" - def __init__(self, bypass_salt=False): """ Initializes the SelfHash object. @@ -67,7 +67,7 @@ def hash(self, file): # Calculate the SHA-256 hash of the source code (with salt if provided) self.source_code_hash = hashlib.sha256(self.file_data_hash.encode()).hexdigest() - + # Compare the known hash to the calculated hash self.known_hash = file_data[hash_line_index].strip().split(' ')[-1] self.known_hash = self.known_hash.strip() # Clean up extra spaces @@ -85,7 +85,7 @@ def hash(self, file): else: print("\033[91mFAIL\033[0m: The source code may have been tampered with or the salt/passphrase is incorrect.") fail = True - + # Exit with error if there was any failure if fail: sys.exit(1) diff --git a/verify_all.py b/verify_all.py index 07e0adc..95766cd 100755 --- a/verify_all.py +++ b/verify_all.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# Hash: 8a26874511080270877d2b3e5f94191fd9e5fd2dad5a191065906c8e66faa781 +# Hash: 4d8077bf0fa508b5211a94521fdf436dc1316d3da6ea8d2570470b25ed2330db # Author: Ron Stoner # Github: ronaldstoner # Website: stoner.com @@ -11,21 +11,21 @@ def hash_and_verify_files(directory): """Hash and verify all .py files in the given directory recursively.""" - + # Find all Python files in the directory and its subdirectories py_files = glob.iglob(directory + '**/*.py', recursive=True) total_files = sum(1 for _ in py_files) # Count the total number of .py files print(f"\nTotal Python files found: {total_files}") - + # Re-run glob to get the file list again after counting py_files = glob.iglob(directory + '**/*.py', recursive=True) for filename in py_files: print(f"\nProcessing {filename}...") - + # Instantiate the SelfHash class for each file hasher = selfhash.SelfHash(bypass_salt=True) - + # Perform the hash verification for each file hasher.hash(filename) diff --git a/verify_self.py b/verify_self.py index 4c4ecd5..013f274 100755 --- a/verify_self.py +++ b/verify_self.py @@ -1,23 +1,24 @@ #!/usr/bin/python3 -# Hash: b3bdd013f179bb06e9be80e8282c3d3dcaf34463f33257b88a96769daf51689c +# Hash: 7171f49aa3395c9177ee86c621c5635ad5f62cc52a8564e5adb6b7f2e50b786b # Author: Ron Stoner # Github: ronaldstoner # Website: stoner.com """Script to verify the selfhash/selfhash.py module hash""" +import sys import selfhash def verify_selfhash(): """Verifies the hash of the selfhash.py module.""" - + # Instantiate the SelfHash class with bypass_salt=True to skip salt prompt hasher = selfhash.SelfHash(bypass_salt=True) - + # Perform the hash verification check on the selfhash.py module print("\nVerifying the hash of the 'selfhash/selfhash.py' module...") hasher.hash("selfhash/selfhash.py") # Replace with the actual path to selfhash.py if needed - + # Output the calculated hash and comparison results print("Expected Hash: ", hasher.known_hash) print("Actual Hash: ", hasher.source_code_hash) @@ -25,14 +26,13 @@ def verify_selfhash(): print("\033[91mFAIL\033[0m: The selfhash.py module has been tampered with or the hash does not match.") print("Expected Hash: ", hasher.known_hash) print("Actual Hash: ", hasher.source_code_hash) - exit(1) + sys.exit(1) elif hasher.known_hash in ("INSERT_HASH_HERE", "Hash:"): print("\033[93mWARNING\033[0m: The hash is not set yet. Please replace 'INSERT_HASH_HERE' with the calculated hash.") print("Generated Hash: ", hasher.source_code_hash) - exit(1) + sys.exit(1) print("\nVerification complete.") # Run the verification function if __name__ == "__main__": verify_selfhash() -