Blockchain File Integrity Checker in Python | FuzzuTech CTkinter GUI

 Demo :


Click Video πŸ‘‡πŸ‘‡πŸ‘‡































Features:
✔ Step-by-step project explanation
✔ Embedded YouTube Short link
✔ Downloadable source code
✔ Tags for SEO (same as above viral tags)


Code :


# -*- coding: utf-8 -*-

"""

FuzzuTech — Blockchain-based File Integrity Checker (CTk GUI)

Author: Fuzzu Developer

Description:

  - Compute SHA-256 for any file

  - Append an immutable-like record into a simple local blockchain (JSON)

  - Verify any file later against stored chain entries

  - Export/Import chain (JSON)

"""


import os, json, hashlib, time

from datetime import datetime

import tkinter as tk

from tkinter import filedialog, messagebox

import customtkinter as ctk


APP_NAME = "Blockchain File Integrity Checker — FuzzuTech"

CHAIN_PATH = os.path.join(os.path.dirname(__file__), "data", "chain.json")


# ----------------------------- Blockchain Core -----------------------------


def sha256_file(path, blocksize=65536):

    h = hashlib.sha256()

    with open(path, "rb") as f:

        for chunk in iter(lambda: f.read(blocksize), b""):

            h.update(chunk)

    return h.hexdigest()


def ensure_chain_file():

    os.makedirs(os.path.dirname(CHAIN_PATH), exist_ok=True)

    if not os.path.exists(CHAIN_PATH):

        with open(CHAIN_PATH, "w", encoding="utf-8") as f:

            json.dump({"chain": []}, f, indent=2)


def load_chain():

    ensure_chain_file()

    with open(CHAIN_PATH, "r", encoding="utf-8") as f:

        return json.load(f)


def save_chain(chain_obj):

    os.makedirs(os.path.dirname(CHAIN_PATH), exist_ok=True)

    with open(CHAIN_PATH, "w", encoding="utf-8") as f:

        json.dump(chain_obj, f, indent=2)


def last_block_hash(chain):

    if not chain:

        return "0"*64

    return chain[-1]["block_hash"]


def calc_block_hash(block):

    # Deterministic hash of block fields (except block_hash)

    data = f"{block['index']}|{block['timestamp']}|{block['file_name']}|{block['file_size']}|{block['file_hash']}|{block['prev_hash']}"

    return hashlib.sha256(data.encode("utf-8")).hexdigest()


def add_block(file_path):

    chain_obj = load_chain()

    chain = chain_obj.get("chain", [])

    file_name = os.path.basename(file_path)

    file_size = os.path.getsize(file_path)

    fhash = sha256_file(file_path)

    prev = last_block_hash(chain)

    block = {

        "index": len(chain),

        "timestamp": int(time.time()),

        "timestamp_iso": datetime.now().isoformat(timespec="seconds"),

        "file_name": file_name,

        "file_size": file_size,

        "file_hash": fhash,

        "prev_hash": prev,

    }

    block["block_hash"] = calc_block_hash(block)

    chain.append(block)

    chain_obj["chain"] = chain

    save_chain(chain_obj)

    return block


def verify_file(file_path):

    """Return tuple (status, detail). status in {'MATCH','MISMATCH','NOT_FOUND'}"""

    chain_obj = load_chain()

    chain = chain_obj.get("chain", [])

    if not chain:

        return ("NOT_FOUND", "Chain empty. Add file first.")

    fhash = sha256_file(file_path)

    file_name = os.path.basename(file_path)

    # try match by file_name + hash

    for blk in chain:

        if blk["file_hash"] == fhash and blk["file_name"] == file_name:

            # also validate the chain linkage for this block

            if blk["index"] == 0:

                link_ok = (blk["prev_hash"] == "0"*64)

            else:

                link_ok = (blk["prev_hash"] == chain[blk["index"]-1]["block_hash"])

            calc = calc_block_hash({k:v for k,v in blk.items() if k != "block_hash"})

            if link_ok and calc == blk["block_hash"]:

                return ("MATCH", f"Original file verified (block #{blk['index']}).")

            else:

                return ("MISMATCH", "Hash matched but block linkage or block hash invalid. Possible tampering.")

    return ("NOT_FOUND", "No matching record found. Likely new or modified file.")


def export_chain(export_path):

    chain_obj = load_chain()

    with open(export_path, "w", encoding="utf-8") as f:

        json.dump(chain_obj, f, indent=2)


def import_chain(import_path):

    with open(import_path, "r", encoding="utf-8") as f:

        chain_obj = json.load(f)

    # minimal sanity: must have list under "chain"

    if not isinstance(chain_obj, dict) or not isinstance(chain_obj.get("chain", None), list):

        raise ValueError("Invalid chain file.")

    save_chain(chain_obj)


# ----------------------------- UI (CTk) ------------------------------------


class App(ctk.CTk):

    def __init__(self):

        super().__init__()

        ctk.set_appearance_mode("dark")

        ctk.set_default_color_theme("blue")

        self.title(APP_NAME)

        self.geometry("980x640")

        self.minsize(900, 600)


        # State

        self.selected_file = None


        # Layout

        self._build_header()

        self._build_body()

        self._build_footer()


        ensure_chain_file()

        self.refresh_chain_count()


    def _build_header(self):

        header = ctk.CTkFrame(self, corner_radius=16)

        header.pack(fill="x", padx=16, pady=(16, 8))

        title = ctk.CTkLabel(header, text="Blockchain File Integrity Checker", font=ctk.CTkFont(size=24, weight="bold"))

        title.pack(side="left", padx=12, pady=12)

        self.count_label = ctk.CTkLabel(header, text="Blocks: 0", font=ctk.CTkFont(size=16))

        self.count_label.pack(side="right", padx=12, pady=12)


    def _build_body(self):

        body = ctk.CTkFrame(self, corner_radius=16)

        body.pack(fill="both", expand=True, padx=16, pady=8)


        left = ctk.CTkFrame(body, corner_radius=16)

        left.pack(side="left", fill="both", expand=True, padx=(16,8), pady=16)


        right = ctk.CTkFrame(body, corner_radius=16)

        right.pack(side="left", fill="both", expand=True, padx=(8,16), pady=16)


        # Left: Actions

        lbl = ctk.CTkLabel(left, text="Select File", font=ctk.CTkFont(size=18, weight="bold"))

        lbl.pack(anchor="w", padx=12, pady=(12,6))


        self.file_entry = ctk.CTkEntry(left, placeholder_text="No file selected", width=520)

        self.file_entry.pack(anchor="w", padx=12, pady=6)


        row = ctk.CTkFrame(left)

        row.pack(anchor="w", padx=12, pady=6)

        ctk.CTkButton(row, text="Browse", command=self.pick_file).pack(side="left", padx=(0,8))

        ctk.CTkButton(row, text="Hash & Add to Chain", command=self.do_add).pack(side="left", padx=8)

        ctk.CTkButton(row, text="Verify File", fg_color="#2fa572", command=self.do_verify).pack(side="left", padx=8)


        # Export/Import

        ei = ctk.CTkFrame(left)

        ei.pack(fill="x", padx=12, pady=(16,8))

        ctk.CTkButton(ei, text="Export Chain (JSON)", command=self.do_export).pack(side="left", padx=6, pady=6)

        ctk.CTkButton(ei, text="Import Chain (JSON)", command=self.do_import).pack(side="left", padx=6, pady=6)

        ctk.CTkButton(ei, text="Open Chain Folder", command=self.open_chain_folder).pack(side="left", padx=6, pady=6)


        # Log

        self.log = ctk.CTkTextbox(left, height=300)

        self.log.pack(fill="both", expand=True, padx=12, pady=(12,12))

        self.log.insert("end", "Ready.\n")


        # Right: Chain info

        rtitle = ctk.CTkLabel(right, text="Chain Overview", font=ctk.CTkFont(size=18, weight="bold"))

        rtitle.pack(anchor="w", padx=12, pady=(12,6))


        self.chain_text = ctk.CTkTextbox(right, height=460)

        self.chain_text.pack(fill="both", expand=True, padx=12, pady=(6,12))

        self.refresh_chain_text()


    def _build_footer(self):

        footer = ctk.CTkFrame(self, corner_radius=16)

        footer.pack(fill="x", padx=16, pady=(8,16))

        ctk.CTkLabel(footer, text="Tip: Export your chain.json regularly for backup/share.", font=ctk.CTkFont(size=14)).pack(side="left", padx=12, pady=8)

        ctk.CTkButton(footer, text="Theme: Light/Dark", command=self.toggle_theme).pack(side="right", padx=12, pady=8)


    # ------------------------- Actions -------------------------


    def pick_file(self):

        path = filedialog.askopenfilename(title="Select a file")

        if path:

            self.selected_file = path

            self.file_entry.delete(0, "end")

            self.file_entry.insert(0, path)


    def do_add(self):

        if not self.selected_file or not os.path.exists(self.selected_file):

            messagebox.showwarning("No File", "Please choose a valid file.")

            return

        blk = add_block(self.selected_file)

        self.log.insert("end", f"✅ Added: {blk['file_name']} | hash={blk['file_hash']}\n")

        self.log.see("end")

        self.refresh_chain_text()

        self.refresh_chain_count()

        messagebox.showinfo("Added", f"Block #{blk['index']} added.")


    def do_verify(self):

        if not self.selected_file or not os.path.exists(self.selected_file):

            messagebox.showwarning("No File", "Please choose a valid file.")

            return

        status, detail = verify_file(self.selected_file)

        prefix = {"MATCH":"✅ Match", "MISMATCH":"⚠️ Mismatch", "NOT_FOUND":"❓ Not Found"}[status]

        self.log.insert("end", f"{prefix}: {detail}\n")

        self.log.see("end")

        if status == "MATCH":

            messagebox.showinfo("Verify", detail)

        elif status == "MISMATCH":

            messagebox.showerror("Verify", detail)

        else:

            messagebox.showwarning("Verify", detail)


    def do_export(self):

        export_path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON","*.json")], title="Export chain to")

        if export_path:

            export_chain(export_path)

            messagebox.showinfo("Export", f"Exported to {export_path}")


    def do_import(self):

        import_path = filedialog.askopenfilename(filetypes=[("JSON","*.json")], title="Import chain from")

        if import_path:

            try:

                import_chain(import_path)

                self.refresh_chain_text()

                self.refresh_chain_count()

                messagebox.showinfo("Import", "Chain imported successfully.")

            except Exception as e:

                messagebox.showerror("Import Error", str(e))


    def open_chain_folder(self):

        folder = os.path.dirname(CHAIN_PATH)

        os.makedirs(folder, exist_ok=True)

        try:

            if os.name == "nt":

                os.startfile(folder)

            elif os.name == "posix":

                os.system(f'xdg-open "{folder}"')

            else:

                messagebox.showinfo("Folder", folder)

        except Exception:

            messagebox.showinfo("Folder", folder)


    def toggle_theme(self):

        current = ctk.get_appearance_mode()

        ctk.set_appearance_mode("light" if current == "Dark" else "dark")


    def refresh_chain_count(self):

        chain = load_chain().get("chain", [])

        self.count_label.configure(text=f"Blocks: {len(chain)}")


    def refresh_chain_text(self):

        chain = load_chain().get("chain", [])

        lines = []

        for b in chain[-50:][::-1]:  # show last 50 latest first

            lines.append(

                f"#{b['index']}  {b['file_name']}  ({b['file_size']} bytes)\n"

                f"  time: {b['timestamp_iso']} | file_hash: {b['file_hash']}\n"

                f"  prev: {b['prev_hash']}\n"

                f"  this: {b['block_hash']}\n"

            )

        txt = "\n".join(lines) if lines else "No blocks yet. Add your first file!"

        self.chain_text.delete("1.0", "end")

        self.chain_text.insert("end", txt)


if __name__ == "__main__":

    app = App()

    app.mainloop()

Comments

Popular posts from this blog

πŸš€ Simple Login & Registration System in Python Tkinter πŸ“±

πŸ“‘ Fuzzu Packet Sniffer – Python GUI for Real-Time IP Monitoring | Tkinter + Scapy

πŸ”₯ Advanced MP3 Music Player in Python | CustomTkinter + Pygame | Free Source Code