Skip to content

Commit

Permalink
0.1.2 - Merge pull request #8 from ronaldstoner/0.1.2
Browse files Browse the repository at this point in the history
Code cleanup, comments, better error handling
  • Loading branch information
ronaldstoner authored Feb 24, 2025
2 parents c124e9d + c254637 commit d1e9e17
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 65 deletions.
21 changes: 0 additions & 21 deletions selfhash/__init__.py

This file was deleted.

60 changes: 46 additions & 14 deletions selfhash/selfhash.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,80 @@
#!/usr/bin/python
# Hash: d758637770374a601330b7e5dc60215505a338702c6188b8563034c3e487ab2b
# Hash: 7133c3ec57f1dbdd2b35a409abebd34ea0736fde377056706b32bf955ae7d313
# 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.")
Expand All @@ -55,5 +86,6 @@ def hash(self, file):
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)
15 changes: 12 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
#!/usr/bin/python
# Hash: 193dfdda9e08fb513338a76bd17dacf8eb80e6a0fb8cef90bb838466e073f78f
# Hash: 8b74bf84000993da1f0fbb10e97197c7443b978b43edd1fa5f3957e729e49718
# Author: Ron Stoner
# Github: ronaldstoner
# Website: stoner.com

"""SelfHash setup.py file package metadata"""

from setuptools import setup, find_packages

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 ',
)
48 changes: 35 additions & 13 deletions verify_all.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
#!/usr/bin/python
# Hash: ff09080b23d744ebcc446a3fdce8dc0da5ae1b127517a4d29c3be46219a012c4
# Hash: 4d8077bf0fa508b5211a94521fdf436dc1316d3da6ea8d2570470b25ed2330db
# 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."""

# Instantiate a new selfhash class for each file
# 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)

# 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")

print("\nVerification process complete.")

# Run the function starting from the current directory
hash_and_verify_files("./")
# Run the verification function starting from the current directory
if __name__ == "__main__":
hash_and_verify_files("./")
6 changes: 0 additions & 6 deletions verify_auto.py

This file was deleted.

40 changes: 32 additions & 8 deletions verify_self.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
#!/usr/bin/python
# Hash: 48717260d6a313c4cc1b3d361a852a34bd24e8ba2786c2a08ecfdaf7f1e556c2
#!/usr/bin/python3
# Hash: 7171f49aa3395c9177ee86c621c5635ad5f62cc52a8564e5adb6b7f2e50b786b
# Author: Ron Stoner
# Github: ronaldstoner
# Website: stoner.com

"""Script to verify the selfhash/selfhash.py module hash"""

import sys
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."""

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.")
# 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)
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)
sys.exit(1)
print("\nVerification complete.")

# Run the verification function
if __name__ == "__main__":
verify_selfhash()

0 comments on commit d1e9e17

Please sign in to comment.