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