Blockchain Secure Login GUI in Python | CTk + PBKDF2 + Proof-of-Work – FuzzuTech

 Demo :


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































πŸ“‘ Features:

  • Embedded YouTube Short

  • Short description + code snippet highlights

  • SEO keywords: Python GUI, Blockchain Login, PBKDF2 Security, Proof-of-Work in Python

  • Internal links to previous Shorts


Code :


# secure_login_blockchain_ctk.py

# ------------------------------------------------------------

# Modern CTk GUI: Blockchain-based Secure Login System (Local)

# - Users stored with salted PBKDF2 hashes (SHA256)

# - "Blockchain" = append-only hash chain of events (genesis + register + login)

# - Simple Proof-of-Work (difficulty = 3 leading zeros)

# - Chain validation & export; clean, dark UI with tabs

# ------------------------------------------------------------


import os

import json

import time

import hmac

import json

import hashlib

import secrets

from datetime import datetime


import customtkinter as ctk

from tkinter import messagebox


APP_TITLE = "Blockchain Secure Login (CTk)"

DATA_DIR = os.path.join(os.path.dirname(__file__), "data")

USERS_PATH = os.path.join(DATA_DIR, "users.json")

CHAIN_PATH = os.path.join(DATA_DIR, "chain.json")


# --------------------------

# Utils: files & time

# --------------------------

def ensure_data_dir():

    os.makedirs(DATA_DIR, exist_ok=True)


def read_json(path, default):

    try:

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

            return json.load(f)

    except Exception:

        return default


def write_json(path, data):

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

        json.dump(data, f, indent=2, ensure_ascii=False)


def utc_iso():

    return datetime.utcnow().replace(microsecond=0).isoformat() + "Z"


# --------------------------

# Password hashing (PBKDF2)

# --------------------------

PBKDF2_ITERS = 120_000

SALT_BYTES = 16

KEY_LEN = 32


def hash_password(password: str, salt: bytes | None = None) -> dict:

    if salt is None:

        salt = secrets.token_bytes(SALT_BYTES)

    dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, PBKDF2_ITERS, dklen=KEY_LEN)

    return {

        "algo": "pbkdf2_sha256",

        "iterations": PBKDF2_ITERS,

        "salt_hex": salt.hex(),

        "hash_hex": dk.hex(),

    }


def verify_password(password: str, record: dict) -> bool:

    try:

        salt = bytes.fromhex(record["salt_hex"])

        iters = int(record["iterations"])

        expect = bytes.fromhex(record["hash_hex"])

        dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iters, dklen=len(expect))

        # constant-time compare

        return hmac.compare_digest(dk, expect)

    except Exception:

        return False


# --------------------------

# Simple Blockchain

# --------------------------

DIFFICULTY = 3  # number of leading zeros required in hex hash


def block_hash(block: dict) -> str:

    # Exclude 'hash' field when hashing

    copy = {k: block[k] for k in block if k != "hash"}

    content = json.dumps(copy, sort_keys=True, separators=(",", ":")).encode("utf-8")

    return hashlib.sha256(content).hexdigest()


def mine_block(block: dict, difficulty: int = DIFFICULTY) -> dict:

    prefix = "0" * difficulty

    nonce = 0

    while True:

        block["nonce"] = nonce

        hx = block_hash(block)

        if hx.startswith(prefix):

            block["hash"] = hx

            return block

        nonce += 1


def load_chain() -> list:

    chain = read_json(CHAIN_PATH, [])

    if not chain:

        # Create genesis block

        genesis = {

            "index": 0,

            "timestamp": utc_iso(),

            "event": {"type": "genesis", "msg": "Chain created"},

            "prev_hash": "0" * 64,

            "nonce": 0,

        }

        mined = mine_block(genesis)

        chain = [mined]

        write_json(CHAIN_PATH, chain)

    return chain


def add_event_to_chain(event: dict) -> dict:

    chain = load_chain()

    prev = chain[-1]

    block = {

        "index": len(chain),

        "timestamp": utc_iso(),

        "event": event,

        "prev_hash": prev["hash"],

        "nonce": 0,

    }

    mined = mine_block(block)

    chain.append(mined)

    write_json(CHAIN_PATH, chain)

    return mined


def validate_chain(chain: list | None = None) -> tuple[bool, str]:

    if chain is None:

        chain = load_chain()

    if not chain:

        return False, "Chain is empty."

    prefix = "0" * DIFFICULTY

    for i, blk in enumerate(chain):

        # Verify hash

        hx = block_hash(blk)

        if "hash" not in blk or hx != blk["hash"]:

            return False, f"Hash mismatch at index {i}"

        if not blk["hash"].startswith(prefix):

            return False, f"Proof-of-work broken at index {i}"

        if i == 0:

            # genesis

            if blk["prev_hash"] != "0" * 64 or blk["index"] != 0:

                return False, "Invalid genesis block."

        else:

            prev = chain[i - 1]

            if blk["prev_hash"] != prev["hash"]:

                return False, f"Broken link at index {i}"

            if blk["index"] != i:

                return False, f"Index mismatch at {i}"

    return True, f"OK · {len(chain)} blocks · difficulty {DIFFICULTY}"


# --------------------------

# Users store

# --------------------------

def load_users() -> dict:

    return read_json(USERS_PATH, {})


def save_users(users: dict):

    write_json(USERS_PATH, users)


def user_exists(username: str) -> bool:

    users = load_users()

    return username.lower() in users


def create_user(username: str, password: str) -> bool:

    u = username.lower().strip()

    if not u or not password:

        return False

    users = load_users()

    if u in users:

        return False

    hpw = hash_password(password)

    users[u] = {

        "username": u,

        "password": hpw,

        "created_at": utc_iso(),

    }

    save_users(users)

    # add event to chain

    add_event_to_chain({"type": "register", "username": u})

    return True


def authenticate(username: str, password: str) -> bool:

    u = username.lower().strip()

    users = load_users()

    if u not in users:

        # Also log failed attempt to chain for auditability (no password stored)

        add_event_to_chain({"type": "login_fail", "username": u})

        return False

    ok = verify_password(password, users[u]["password"])

    add_event_to_chain({"type": "login_ok" if ok else "login_fail", "username": u})

    return ok


# --------------------------

# GUI: CustomTkinter App

# --------------------------

class App(ctk.CTk):

    def __init__(self):

        super().__init__()

        self.title(APP_TITLE)

        self.geometry("920x620")

        self.minsize(860, 560)

        ctk.set_appearance_mode("dark")

        ctk.set_default_color_theme("blue")  # subtle accent


        self.grid_rowconfigure(1, weight=1)

        self.grid_columnconfigure(0, weight=1)


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

        self.header.grid(row=0, column=0, sticky="ew")

        self.header.grid_columnconfigure(2, weight=1)


        self.logo = ctk.CTkLabel(self.header, text="πŸ”", font=("Segoe UI Emoji", 24))

        self.logo.grid(row=0, column=0, padx=(14, 6), pady=10)

        self.title_lbl = ctk.CTkLabel(self.header, text=APP_TITLE, font=("Segoe UI", 20, "bold"))

        self.title_lbl.grid(row=0, column=1, padx=(0, 12))


        self.status_lbl = ctk.CTkLabel(self.header, text="Chain: validating…", text_color=("gray70", "gray70"))

        self.status_lbl.grid(row=0, column=3, padx=14)


        self.body = ctk.CTkFrame(self, fg_color="transparent")

        self.body.grid(row=1, column=0, sticky="nsew", padx=16, pady=16)

        self.body.grid_rowconfigure(0, weight=1)

        self.body.grid_columnconfigure(0, weight=1)


        self.tabs = ctk.CTkTabview(self.body, segmented_button_selected_color=None)

        self.tabs.grid(row=0, column=0, sticky="nsew")

        self.tab_login = self.tabs.add("Login")

        self.tab_register = self.tabs.add("Register")

        self.tab_chain = self.tabs.add("Blockchain")

        self.tab_settings = self.tabs.add("Settings")


        self.build_login_tab()

        self.build_register_tab()

        self.build_chain_tab()

        self.build_settings_tab()


        self.after(150, self.refresh_chain_status)


    # ------------- Login tab

    def build_login_tab(self):

        p = self.tab_login

        for i in range(8):

            p.grid_rowconfigure(i, weight=0)

        p.grid_rowconfigure(9, weight=1)

        p.grid_columnconfigure(0, weight=1)

        p.grid_columnconfigure(1, weight=1)


        card = ctk.CTkFrame(p, corner_radius=20)

        card.grid(row=0, column=0, columnspan=2, padx=40, pady=40, sticky="n")

        card.grid_columnconfigure(0, weight=1)


        heading = ctk.CTkLabel(card, text="Welcome back", font=("Segoe UI", 22, "bold"))

        heading.grid(row=0, column=0, padx=20, pady=(20, 6))


        sub = ctk.CTkLabel(card, text="Sign in securely • Events are chained on the ledger", text_color=("gray70", "gray70"))

        sub.grid(row=1, column=0, padx=20, pady=(0, 16))


        self.login_user = ctk.CTkEntry(card, placeholder_text="Username", width=360)

        self.login_user.grid(row=2, column=0, padx=20, pady=8)


        self.login_pass = ctk.CTkEntry(card, placeholder_text="Password", show="•", width=360)

        self.login_pass.grid(row=3, column=0, padx=20, pady=8)


        self.show_pw_var = ctk.BooleanVar(value=False)

        show_pw = ctk.CTkCheckBox(card, text="Show password", variable=self.show_pw_var, command=self.toggle_login_pw)

        show_pw.grid(row=4, column=0, padx=20, pady=(0, 12))


        btn = ctk.CTkButton(card, text="Sign In", height=40, command=self.on_login)

        btn.grid(row=5, column=0, padx=20, pady=(8, 20), sticky="nsew")


        self.login_msg = ctk.CTkLabel(card, text="", text_color=("gray80", "gray80"))

        self.login_msg.grid(row=6, column=0, padx=20, pady=(0, 20))


    def toggle_login_pw(self):

        self.login_pass.configure(show="" if self.show_pw_var.get() else "•")


    def on_login(self):

        u = self.login_user.get().strip()

        p = self.login_pass.get()

        if not u or not p:

            self.toast("Please enter username and password.", kind="warn")

            return

        ok = authenticate(u, p)

        if ok:

            self.toast(f"Welcome, {u}!", kind="ok")

            self.login_msg.configure(text=f"✅ Authenticated as {u}")

        else:

            self.toast("Invalid credentials.", kind="error")

            self.login_msg.configure(text="❌ Login failed")

        self.refresh_chain_status()


    # ------------- Register tab

    def build_register_tab(self):

        p = self.tab_register

        p.grid_columnconfigure(0, weight=1)

        p.grid_rowconfigure(9, weight=1)


        card = ctk.CTkFrame(p, corner_radius=20)

        card.grid(row=0, column=0, padx=40, pady=40, sticky="n")

        card.grid_columnconfigure(0, weight=1)


        heading = ctk.CTkLabel(card, text="Create a secure account", font=("Segoe UI", 22, "bold"))

        heading.grid(row=0, column=0, padx=20, pady=(20, 6))


        self.reg_user = ctk.CTkEntry(card, placeholder_text="Username (unique)", width=360)

        self.reg_user.grid(row=1, column=0, padx=20, pady=8)


        self.reg_pass = ctk.CTkEntry(card, placeholder_text="Password", show="•", width=360)

        self.reg_pass.grid(row=2, column=0, padx=20, pady=8)


        self.reg_pass2 = ctk.CTkEntry(card, placeholder_text="Confirm password", show="•", width=360)

        self.reg_pass2.grid(row=3, column=0, padx=20, pady=8)


        self.reg_show_var = ctk.BooleanVar(value=False)

        reg_show = ctk.CTkCheckBox(card, text="Show passwords", variable=self.reg_show_var, command=self.toggle_reg_pw)

        reg_show.grid(row=4, column=0, padx=20, pady=(0, 12))


        self.policy_lbl = ctk.CTkLabel(card, text="Tip: use 10+ chars, mix of letters, numbers, symbols.", text_color=("gray70", "gray70"))

        self.policy_lbl.grid(row=5, column=0, padx=20, pady=(0, 8))


        btn = ctk.CTkButton(card, text="Create Account", height=40, command=self.on_register)

        btn.grid(row=6, column=0, padx=20, pady=(8, 20), sticky="nsew")


        self.reg_msg = ctk.CTkLabel(card, text="", text_color=("gray80", "gray80"))

        self.reg_msg.grid(row=7, column=0, padx=20, pady=(0, 20))


    def toggle_reg_pw(self):

        show = "" if self.reg_show_var.get() else "•"

        self.reg_pass.configure(show=show)

        self.reg_pass2.configure(show=show)


    def on_register(self):

        u = self.reg_user.get().strip()

        p1 = self.reg_pass.get()

        p2 = self.reg_pass2.get()

        if not u or not p1 or not p2:

            self.toast("Fill all fields.", kind="warn")

            return

        if p1 != p2:

            self.toast("Passwords do not match.", kind="error")

            self.reg_msg.configure(text="❌ Passwords do not match")

            return

        if len(p1) < 8:

            self.toast("Use at least 8 characters.", kind="warn")

            return

        if user_exists(u):

            self.toast("Username already exists.", kind="error")

            self.reg_msg.configure(text="❌ Username taken")

            return

        ok = create_user(u, p1)

        if ok:

            self.toast("Account created! You can login now.", kind="ok")

            self.reg_msg.configure(text="✅ Account created")

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

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

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

            self.refresh_chain_status()

        else:

            self.toast("Failed to create user.", kind="error")

            self.reg_msg.configure(text="❌ Failed to create user")


    # ------------- Chain tab

    def build_chain_tab(self):

        p = self.tab_chain

        for i in range(8):

            p.grid_rowconfigure(i, weight=0)

        p.grid_rowconfigure(8, weight=1)

        p.grid_columnconfigure(0, weight=1)

        p.grid_columnconfigure(1, weight=1)


        top = ctk.CTkFrame(p)

        top.grid(row=0, column=0, columnspan=2, sticky="ew", padx=20, pady=(20, 10))

        top.grid_columnconfigure(1, weight=1)


        self.chain_status = ctk.CTkLabel(top, text="Status: —")

        self.chain_status.grid(row=0, column=0, padx=(10, 10), pady=10, sticky="w")


        verify_btn = ctk.CTkButton(top, text="Verify Chain", command=self.on_verify_chain, width=150)

        verify_btn.grid(row=0, column=2, padx=10, pady=10)


        export_btn = ctk.CTkButton(top, text="Export chain.json", command=self.on_export_chain, width=150)

        export_btn.grid(row=0, column=3, padx=10, pady=10)


        self.chain_list = ctk.CTkTextbox(p, height=360)

        self.chain_list.grid(row=1, column=0, columnspan=2, sticky="nsew", padx=20, pady=(0, 20))


        self.refresh_chain_view()


    def on_verify_chain(self):

        ok, msg = validate_chain()

        self.chain_status.configure(text=f"Status: {'✅' if ok else '❌'} {msg}")

        self.toast(("Chain OK" if ok else "Chain invalid") + f": {msg}", kind=("ok" if ok else "error"))


    def on_export_chain(self):

        # Already exported continuously; just show location

        self.toast(f"Exported at {CHAIN_PATH}", kind="ok")


    def refresh_chain_view(self):

        chain = load_chain()

        self.chain_list.configure(state="normal")

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

        for blk in chain:

            pretty = json.dumps(blk, indent=2)

            self.chain_list.insert("end", pretty + "\n")

        self.chain_list.configure(state="disabled")


    # ------------- Settings tab

    def build_settings_tab(self):

        p = self.tab_settings

        p.grid_columnconfigure(0, weight=1)

        p.grid_rowconfigure(5, weight=1)


        theme_lbl = ctk.CTkLabel(p, text="Appearance", font=("Segoe UI", 16, "bold"))

        theme_lbl.grid(row=0, column=0, padx=20, pady=(24, 6), sticky="w")


        theme_row = ctk.CTkFrame(p)

        theme_row.grid(row=1, column=0, padx=20, pady=6, sticky="w")

        ctk.CTkLabel(theme_row, text="Theme:").grid(row=0, column=0, padx=(10,8), pady=10)

        self.theme_var = ctk.StringVar(value="Dark")

        theme_opt = ctk.CTkOptionMenu(theme_row, values=["Dark", "Light", "System"], variable=self.theme_var, command=self.on_theme)

        theme_opt.grid(row=0, column=1, padx=8, pady=10)


        diff_lbl = ctk.CTkLabel(p, text="Security", font=("Segoe UI", 16, "bold"))

        diff_lbl.grid(row=2, column=0, padx=20, pady=(16, 6), sticky="w")


        info = ctk.CTkLabel(p, text="Password hashing: PBKDF2-SHA256 · Chain PoW difficulty: 3", text_color=("gray70","gray70"))

        info.grid(row=3, column=0, padx=20, pady=(0, 12), sticky="w")


        refresh_btn = ctk.CTkButton(p, text="Reload Chain View", command=self.refresh_chain_view)

        refresh_btn.grid(row=4, column=0, padx=20, pady=(6, 6), sticky="w")


    def on_theme(self, choice: str):

        choice = choice.lower()

        if choice == "dark":

            ctk.set_appearance_mode("dark")

        elif choice == "light":

            ctk.set_appearance_mode("light")

        else:

            ctk.set_appearance_mode("system")


    # ------------- Helpers

    def toast(self, msg: str, kind: str = "info"):

        # Simple inline notification

        colors = {

            "info": ("#4b5563", "#1f2937"),

            "ok": ("#16a34a", "#064e3b"),

            "warn": ("#f59e0b", "#7c2d12"),

            "error": ("#ef4444", "#7f1d1d"),

        }

        fg, bg = colors.get(kind, colors["info"])

        if hasattr(self, "_toast"):

            self._toast.destroy()

        self._toast = ctk.CTkLabel(self.header, text=msg, fg_color=bg, text_color="white", corner_radius=12, padx=12, pady=8)

        self._toast.grid(row=0, column=4, padx=10)

        # Auto-hide

        self.after(2600, lambda: (self._toast.destroy() if hasattr(self, "_toast") else None))


    def refresh_chain_status(self):

        ok, msg = validate_chain()

        self.status_lbl.configure(text=f"Chain: {'✅' if ok else '❌'} {msg}")

        self.chain_status.configure(text=f"Status: {'✅' if ok else '❌'} {msg}")

        self.refresh_chain_view()


# --------------------------

# Boot

# --------------------------

def main():

    ensure_data_dir()

    # ensure users/chain exist

    _ = load_chain()

    _ = load_users()

    app = App()

    app.mainloop()


if __name__ == "__main__":

    main()

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