Quantum-Safe File Encryptor – Python GUI (AES + Post-Quantum Encryption) | FuzzuTech

 Demo :


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































Features:

  • 🧠 PyQt6 GUI

  • πŸ” Hybrid AES + PQC Encryption

  • ⚙️ Encrypt/Decrypt with .enc & .meta key system

  • πŸ’Ύ Educational Demo for Python Developers


Code :


"""

quantum_safe_encryptor.py

Single-file Quantum-Safe File Encryptor (Demo)

- Modern PyQt6 GUI

- AES-256-GCM (via cryptography)

- Optional Post-Quantum KEM via liboqs Python bindings (if installed)

- Saves .enc file and .meta (pk::ct) when PQC is used, or .key when using symmetric key.


Requirements:

- Python 3.9+

- pip install PyQt6 cryptography

- Optional (for PQC): install liboqs C library and liboqs-python / pyoqs (platform-specific)

z

Run:

python quantum_safe_encryptor.py

"""


import sys, os, traceback

from pathlib import Path

from functools import partial


# --- Crypto imports ---

try:

    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    from cryptography.hazmat.primitives.kdf.hkdf import HKDF

    from cryptography.hazmat.primitives import hashes

except Exception as e:

    print("Missing cryptography package. Install with: pip install cryptography")

    raise


# Optional PQC

HAS_OQS = False

try:

    import oqs  # liboqs python bindings (if present)

    HAS_OQS = True

except Exception:

    # try alternate wrapper name

    try:

        import pyoqs as oqs

        HAS_OQS = True

    except Exception:

        oqs = None

        HAS_OQS = False


# --- PyQt6 imports ---

try:

    from PyQt6.QtWidgets import (

        QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QFileDialog,

        QProgressBar, QMessageBox, QHBoxLayout, QLineEdit, QInputDialog

    )

    from PyQt6.QtCore import Qt, QThread, pyqtSignal

    from PyQt6.QtGui import QFont, QIcon

except Exception:

    print("Missing PyQt6. Install with: pip install PyQt6")

    raise


# --- Utility crypto functions ---

import os


def generate_symmetric_key_bytes():

    """Generate 32-byte AES-256 key."""

    return AESGCM.generate_key(bit_length=256)


def aes_encrypt_bytes(key: bytes, data: bytes) -> bytes:

    aesgcm = AESGCM(key)

    nonce = os.urandom(12)  # recommended 12 bytes for GCM

    ct = aesgcm.encrypt(nonce, data, None)

    return nonce + ct  # store nonce prefixed


def aes_decrypt_bytes(key: bytes, blob: bytes) -> bytes:

    aesgcm = AESGCM(key)

    nonce = blob[:12]

    ct = blob[12:]

    return aesgcm.decrypt(nonce, ct, None)


# --- PQC helper (liboqs) ---

def pqc_encapsulate(algorithm="Kyber512"):

    """

    Returns (public_key_bytes, ciphertext_bytes, derived_aes_key_bytes).

    Requires oqs lib available.

    """

    if not HAS_OQS:

        raise RuntimeError("liboqs not available on this system.")

    # Using oqs.KEM API — wrappers differ; attempt a robust usage

    kem = oqs.KEM(algorithm)

    pk = kem.generate_keypair()

    ct, ss = kem.encapsulate(pk)

    kem.free()

    # Derive AES key from shared secret using HKDF-SHA256

    derived = HKDF(

        algorithm=hashes.SHA256(),

        length=32,

        salt=None,

        info=b"fuzzu-qsafe",

    ).derive(ss)

    return pk, ct, derived


def pqc_decapsulate(pk: bytes, ct: bytes, algorithm="Kyber512"):

    """

    Given stored public key + ciphertext, attempts decapsulation.

    Note: In real KEM workflow, decapsulation requires secret key. This demo uses

    a simplified approach: we assume the demo environment stores the secret (not safe).

    This function is a placeholder demonstrating API usage and will raise if

    wrapper lacks decapsulate functionality.

    """

    if not HAS_OQS:

        raise RuntimeError("liboqs not available on this system.")

    # Real usage requires secret key. Most Python wrappers provide separate API.

    # Here we attempt to create a KEM object and call decapsulate(ct) which may

    # not be correct in all bindings. If not available, raise a clear error.

    kem = oqs.KEM(algorithm)

    try:

        ss = kem.decapsulate(ct)

    except Exception as e:

        kem.free()

        raise RuntimeError("Decapsulation not supported in this wrapper demo. See README for proper PQC flow.") from e

    kem.free()

    derived = HKDF(

        algorithm=hashes.SHA256(),

        length=32,

        salt=None,

        info=b"fuzzu-qsafe",

    ).derive(ss)

    return derived


# --- Worker threads for file IO so UI doesn't freeze ---

class EncryptWorker(QThread):

    finished = pyqtSignal(str)

    error = pyqtSignal(str)


    def __init__(self, in_path, out_path, use_pqc):

        super().__init__()

        self.in_path = in_path

        self.out_path = out_path

        self.use_pqc = use_pqc


    def run(self):

        try:

            with open(self.in_path, "rb") as f:

                data = f.read()

            if self.use_pqc and HAS_OQS:

                pk, ct, symkey = pqc_encapsulate()

                ciphertext_blob = aes_encrypt_bytes(symkey, data)

                # Save encrypted file

                with open(self.out_path, "wb") as outf:

                    outf.write(ciphertext_blob)

                # Save .meta with pk::ct for demo (NOT secure format)

                meta_path = self.out_path + ".meta"

                with open(meta_path, "wb") as mf:

                    mf.write(pk + b"::" + ct)

                self.finished.emit(f"Encrypted (PQC) -> {os.path.basename(self.out_path)} (meta saved)")

            else:

                symkey = generate_symmetric_key_bytes()

                ciphertext_blob = aes_encrypt_bytes(symkey, data)

                with open(self.out_path, "wb") as outf:

                    outf.write(ciphertext_blob)

                # Save key file and instruct user to keep it safe

                key_path = self.out_path + ".key"

                with open(key_path, "wb") as kf:

                    kf.write(symkey)

                self.finished.emit(f"Encrypted -> {os.path.basename(self.out_path)} (key saved: {os.path.basename(key_path)})")

        except Exception as e:

            tb = traceback.format_exc()

            self.error.emit(f"Encryption failed: {e}\n{tb}")


class DecryptWorker(QThread):

    finished = pyqtSignal(str)

    error = pyqtSignal(str)


    def __init__(self, in_path, out_path, key_file=None):

        super().__init__()

        self.in_path = in_path

        self.out_path = out_path

        self.key_file = key_file


    def run(self):

        try:

            with open(self.in_path, "rb") as f:

                blob = f.read()

            # Attempt read meta

            meta_path = self.in_path + ".meta"

            if os.path.exists(meta_path) and HAS_OQS:

                with open(meta_path, "rb") as mf:

                    data = mf.read()

                # naive split: pk::ct

                if b"::" in data:

                    pk, ct = data.split(b"::", 1)

                    try:

                        symkey = pqc_decapsulate(pk, ct)

                    except Exception as e:

                        raise RuntimeError("PQC decapsulation failed: " + str(e))

                else:

                    raise RuntimeError("Meta file malformed.")

            else:

                # require key file to be selected

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

                    raise RuntimeError("Key file required for decryption (no meta found).")

                with open(self.key_file, "rb") as kf:

                    symkey = kf.read()

            plaintext = aes_decrypt_bytes(symkey, blob)

            with open(self.out_path, "wb") as outf:

                outf.write(plaintext)

            self.finished.emit(f"Decrypted -> {os.path.basename(self.out_path)}")

        except Exception as e:

            tb = traceback.format_exc()

            self.error.emit(f"Decryption failed: {e}\n{tb}")


# --- Main GUI ---

class MainWindow(QWidget):

    def __init__(self):

        super().__init__()

        self.setWindowTitle("Quantum-Safe File Encryptor — FuzzuTech")

        self.setGeometry(180, 120, 920, 520)

        self.setWindowIcon(QIcon())  # placeholder

        self.setup_ui()


    def setup_ui(self):

        # Styling

        self.setStyleSheet("""

            QWidget { background: qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 #071230, stop:1 #0f1724); color: #e6eef8; }

            QPushButton { background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 #ff7a7a, stop:1 #ff6b6b);

                          border-radius:12px; padding:10px; font-weight:700; }

            QPushButton#secondary { background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 #6b9bff, stop:1 #5fd3ff); }

            QLabel#title { font-size:22px; font-weight:800; }

            QLabel#info { color:#cfe8ff; }

            QProgressBar { border-radius: 10px; height: 14px; background: rgba(255,255,255,0.08); }

            QProgressBar::chunk { border-radius: 10px; background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 #f6d365, stop:1 #fda085); }

            QLineEdit { padding:8px; border-radius:8px; background: rgba(255,255,255,0.03); color:#fff; }

        """)

        layout = QVBoxLayout()

        layout.setSpacing(12)


        title = QLabel("Quantum-Safe File Encryptor")

        title.setObjectName("title")

        title.setAlignment(Qt.AlignmentFlag.AlignCenter)

        layout.addWidget(title)


        info = QLabel("AES-256-GCM + optional Post-Quantum KEM (if liboqs installed). For demo only.")

        info.setObjectName("info")

        info.setAlignment(Qt.AlignmentFlag.AlignCenter)

        layout.addWidget(info)


        # Buttons row

        row = QHBoxLayout()

        self.btn_encrypt = QPushButton("Encrypt File")

        self.btn_encrypt.clicked.connect(self.encrypt_file)

        row.addWidget(self.btn_encrypt)


        self.btn_decrypt = QPushButton("Decrypt File")

        self.btn_decrypt.clicked.connect(self.decrypt_file)

        row.addWidget(self.btn_decrypt)


        self.btn_show_info = QPushButton("Status")

        self.btn_show_info.setObjectName("secondary")

        self.btn_show_info.clicked.connect(self.show_status)

        row.addWidget(self.btn_show_info)


        layout.addLayout(row)


        # Key path input (for decryption fallback)

        key_row = QHBoxLayout()

        self.key_input = QLineEdit()

        self.key_input.setPlaceholderText("Optional: select .key file for decryption (or use .meta alongside .enc)")

        key_row.addWidget(self.key_input)

        btn_browse_key = QPushButton("Browse Key")

        btn_browse_key.clicked.connect(self.browse_keyfile)

        btn_browse_key.setObjectName("secondary")

        key_row.addWidget(btn_browse_key)

        layout.addLayout(key_row)


        # Progress & log

        self.progress = QProgressBar()

        self.progress.setValue(0)

        layout.addWidget(self.progress)


        self.status_label = QLabel("Ready")

        self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        layout.addWidget(self.status_label)


        # Footer buttons

        footer = QHBoxLayout()

        self.btn_about = QPushButton("About")

        self.btn_about.clicked.connect(self.show_about)

        footer.addWidget(self.btn_about)


        self.btn_open_folder = QPushButton("Open Output Folder")

        self.btn_open_folder.clicked.connect(self.open_output_folder)

        footer.addWidget(self.btn_open_folder)


        layout.addLayout(footer)

        self.setLayout(layout)


    def browse_keyfile(self):

        p, _ = QFileDialog.getOpenFileName(self, "Select key file", filter="Key files (*.key);;All files (*)")

        if p:

            self.key_input.setText(p)


    def set_status(self, text):

        self.status_label.setText(text)


    def show_status(self):

        txt = "liboqs available: {}\ncryptography: available\nPyQt6: available\n".format(HAS_OQS)

        QMessageBox.information(self, "Status", txt)


    def show_about(self):

        about_txt = ("Quantum-Safe File Encryptor\n"

                     "Demo: AES-256-GCM + optional liboqs KEM\n"

                     "Author: FuzzuTech (demo)\n\n"

                     "This is a demo app. Do NOT assume secure key storage; this example\n"

                     "is intended for learning and demo/shorts only.")

        QMessageBox.information(self, "About", about_txt)


    def open_output_folder(self):

        # Opens current working directory in file manager

        cwd = os.getcwd()

        try:

            if sys.platform.startswith("win"):

                os.startfile(cwd)

            elif sys.platform == "darwin":

                os.system(f'open "{cwd}"')

            else:

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

        except Exception:

            QMessageBox.information(self, "Open Folder", f"Output folder: {cwd}")


    def encrypt_file(self):

        in_path, _ = QFileDialog.getOpenFileName(self, "Select file to encrypt")

        if not in_path:

            return

        out_default = os.path.basename(in_path) + ".enc"

        out_path, _ = QFileDialog.getSaveFileName(self, "Save encrypted file as", out_default)

        if not out_path:

            return

        # Ask whether to use PQC if available

        use_pqc = False

        if HAS_OQS:

            resp = QMessageBox.question(self, "Use Post-Quantum KEM?",

                                        "liboqs is available. Do you want to use Post-Quantum KEM (Kyber) to wrap keys?\n\n"

                                        "Yes: hybrid PQC (pk + ct) saved to .meta (demo format).\n"

                                        "No: pure AES-GCM with .key file saved.",

                                        QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,

                                        QMessageBox.StandardButton.Yes)

            use_pqc = (resp == QMessageBox.StandardButton.Yes)

        else:

            # inform user symmetric fallback

            QMessageBox.information(self, "PQC Not Available",

                                    "liboqs not detected. Using AES-256-GCM and exporting a .key file for decryption.")


        # Kick off worker

        self.progress.setValue(10)

        self.set_status("Encrypting...")

        self.encrypt_worker = EncryptWorker(in_path, out_path, use_pqc)

        self.encrypt_worker.finished.connect(self.on_encrypt_finished)

        self.encrypt_worker.error.connect(self.on_worker_error)

        self.encrypt_worker.start()


    def on_encrypt_finished(self, msg):

        self.progress.setValue(100)

        self.set_status(msg)

        QMessageBox.information(self, "Done", msg)

        self.progress.setValue(0)


    def on_worker_error(self, errmsg):

        self.progress.setValue(0)

        self.set_status("Error")

        QMessageBox.critical(self, "Error", errmsg)


    def decrypt_file(self):

        in_path, _ = QFileDialog.getOpenFileName(self, "Select encrypted file (.enc)")

        if not in_path:

            return

        out_default = os.path.basename(in_path) + ".dec"

        out_path, _ = QFileDialog.getSaveFileName(self, "Save decrypted file as", out_default)

        if not out_path:

            return

        key_file = self.key_input.text().strip() or None

        if not key_file:

            # try to find meta

            meta_path = in_path + ".meta"

            if not (os.path.exists(meta_path) and HAS_OQS):

                # ask user to select key file

                reply = QMessageBox.question(self, "Key Required",

                                             "No .meta found or PQC not available. Do you want to select a .key file now?",

                                             QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,

                                             QMessageBox.StandardButton.Yes)

                if reply == QMessageBox.StandardButton.Yes:

                    p, _ = QFileDialog.getOpenFileName(self, "Select key file", filter="Key files (*.key);;All files (*)")

                    if not p:

                        QMessageBox.warning(self, "Aborted", "Decryption aborted: key required.")

                        return

                    key_file = p

                else:

                    QMessageBox.warning(self, "Aborted", "Decryption aborted: key required.")

                    return


        self.progress.setValue(10)

        self.set_status("Decrypting...")

        self.decrypt_worker = DecryptWorker(in_path, out_path, key_file)

        self.decrypt_worker.finished.connect(self.on_decrypt_finished)

        self.decrypt_worker.error.connect(self.on_worker_error)

        self.decrypt_worker.start()


    def on_decrypt_finished(self, msg):

        self.progress.setValue(100)

        self.set_status(msg)

        QMessageBox.information(self, "Done", msg)

        self.progress.setValue(0)


# --- Main entry ---

def main():

    app = QApplication(sys.argv)

    w = MainWindow()

    w.show()

    sys.exit(app.exec())


if __name__ == "__main__":

    main()


Comments

Popular posts from this blog

Is This News Real or Fake? πŸ€– AI Exposes the Truth | FuzzuTech Python App Demo

🚨 Python Intrusion Detection System (IDS) – Real-Time ML + Tkinter GUI Project | FuzzuTech

Educational File Encryptor GUI (Python AES Project) | FuzzuTech