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
Post a Comment