File integrity check script (SHA-256 hashes)

I’ll give an example of a Python script that calculates the SHA-256 hashes of all files in a specified directory and sends an email with a list of files that have changed, been created, or been deleted, and also writes to the log file.

I ran the script on Ubuntu Server, so let’s make sure Python3 is installed:

python3 --version
apt install python3 python3-pip

I once needed to monitor the state of www files, so I created this script:

#!/usr/bin/env python3
import os
import hashlib
import json
import time
import subprocess

WATCH_DIR = "/var/www"
HASH_DB = "/root/www_hashes.json"
LOG_FILE = "/root/www_changes.log"
EMAIL_TO = "root"
EMAIL_SUBJECT = "FILE MODIFIED"
EXCLUDE_FILES = [
    "/var/www/logs/1.log",
    "/var/www/logs/2.log",
]

def file_hash(path):
    sha = hashlib.sha256()
    try:
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                sha.update(chunk)
        return sha.hexdigest()
    except Exception:
        return None


def load_db():
    if os.path.exists(HASH_DB):
        with open(HASH_DB, "r") as f:
            return json.load(f)
    return {}


def save_db(db):
    with open(HASH_DB, "w") as f:
        json.dump(db, f, indent=2)


def scan():
    hashes = {}
    for root, dirs, files in os.walk(WATCH_DIR):
        for name in files:
            path = os.path.join(root, name)
            if path in EXCLUDE_FILES:
                continue
            h = file_hash(path)
            if h:
                hashes[path] = h
    return hashes


def log_changes(changes):
    if not changes:
        return
    timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
    with open(LOG_FILE, "a") as f:
        f.write(f"\n[{timestamp}] Changes:\n")
        for line in changes:
            f.write(line + "\n")


def send_email(changes):
    if not changes:
        return
    body = "\n".join(changes)
    try:
        proc = subprocess.Popen(
            ["/usr/sbin/sendmail", EMAIL_TO],
            stdin=subprocess.PIPE
        )
        proc.communicate(input=f"Subject: {EMAIL_SUBJECT}\n\n{body}".encode("utf-8"))
        print("[EMAIL] notification sent")
    except Exception as e:
        print("[EMAIL ERROR]:", e)


def main():
    old_hashes = load_db()
    new_hashes = scan()
    changes = []

    for path, new_h in new_hashes.items():
        old_h = old_hashes.get(path)
        if old_h and old_h != new_h:
            changes.append(f"File modified: {path}")

    for path in old_hashes:
        if path not in new_hashes:
            changes.append(f"File deleted: {path}")

    for path in new_hashes:
        if path not in old_hashes:
            changes.append(f"New file: {path}")

    if changes:
        log_changes(changes)
        send_email(changes)

    save_db(new_hashes)


if __name__ == "__main__":
    main()

Let’s make the file executable and run it. On NVMe RAIDs, the calculation is done very quickly. The larger the file size, the longer it takes. If there are tens of thousands of small files, it will take seconds:

chmod +x ixnfo.com.py
./ixnfo.com.py

All that’s left is to add it to cron, for example, every day at 7 am:

02 7 * * * root /dir/ixnfo.com.py > /dev/null 2>&1

See also my articles:
Using and configuring CRON
File Integrity Check Script (size+mtime)
Scripts

Leave a comment

Leave a Reply