This commit is contained in:
Tyler
2025-05-17 02:23:03 -10:00
committed by GitHub
parent 0b39998364
commit 03079263ec
3 changed files with 147 additions and 53 deletions
+133 -18
View File
@@ -8,14 +8,14 @@ import hashlib
import secrets import secrets
import subprocess import subprocess
import platform import platform
from datetime import datetime, timedelta from datetime import datetime
import sys import sys
import psutil import psutil
# ===== Third-Party Imports ===== # ===== Third-Party Imports =====
from flask import ( from flask import (
Flask, render_template, request, jsonify, session, Flask, render_template, request, jsonify, session,
redirect, url_for, flash, send_file redirect, url_for, flash, send_file, make_response
) )
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
@@ -83,20 +83,21 @@ def simple_decode(text: str) -> str:
return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower()) return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower())
def advanced_encrypt(plaintext: str, password: str) -> str: def advanced_encrypt(plaintext: str, password: str) -> str:
"""Encrypt text using AES-GCM with password-derived key.""" """Encrypt plaintext with AES-GCM and return base64-encoded result."""
salt = os.urandom(16) salt = os.urandom(16)
key = derive_key(password, salt)
nonce = os.urandom(12) nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, plaintext.encode(), None) key = derive_key(password, salt)
return base64.urlsafe_b64encode(salt + nonce + ct).decode() ciphertext = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
return base64.b64encode(salt + nonce + ciphertext).decode()
def advanced_decrypt(token_b64: str, password: str) -> str: def advanced_decrypt(data_b64: str, password: str) -> str:
"""Decrypt text using AES-GCM with password-derived key.""" """Decrypt base64-encoded AES-GCM encrypted data."""
try: try:
data = base64.urlsafe_b64decode(token_b64.encode()) data = base64.b64decode(data_b64)
salt, nonce, ct = data[:16], data[16:28], data[28:] salt, nonce, ciphertext = data[:16], data[16:28], data[28:]
key = derive_key(password, salt) key = derive_key(password, salt)
return AESGCM(key).decrypt(nonce, ct, None).decode() plaintext = AESGCM(key).decrypt(nonce, ciphertext, None)
return plaintext.decode()
except Exception: except Exception:
return "[Error] Invalid password or corrupted data!" return "[Error] Invalid password or corrupted data!"
@@ -207,7 +208,7 @@ def handle_file_upload(request):
meta = { meta = {
'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(), 'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(),
'original_name': filename, 'original_name': encrypt_filename(filename, enc_password),
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
} }
with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f: with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f:
@@ -217,7 +218,6 @@ def handle_file_upload(request):
return jsonify({"success": True, "pickup_url": pickup_url}) return jsonify({"success": True, "pickup_url": pickup_url})
def handle_text_operation(request): def handle_text_operation(request):
"""Process text encryption/decryption operations."""
data = request.get_json() data = request.get_json()
encryption_type = data.get("encryption-type", "basic") encryption_type = data.get("encryption-type", "basic")
operation = data.get("operation", "") operation = data.get("operation", "")
@@ -226,10 +226,27 @@ def handle_text_operation(request):
if encryption_type == "basic": if encryption_type == "basic":
result = simple_encode(message) if operation == "encrypt" else simple_decode(message) result = simple_encode(message) if operation == "encrypt" else simple_decode(message)
else: return jsonify(result=html.escape(result))
result = advanced_encrypt(message, password) if operation == "encrypt" else advanced_decrypt(message, password)
return jsonify(result=html.escape(result)) if operation == "encrypt":
encrypted = advanced_encrypt(message, password)
return jsonify(result=encrypted)
else:
decrypted = advanced_decrypt(message, password)
return jsonify(result=html.escape(decrypted))
def encrypt_filename(filename: str, password: str) -> str:
salt = os.urandom(16)
key = derive_key(password, salt)
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, filename.encode(), None)
return base64.urlsafe_b64encode(salt + nonce + ct).decode()
def decrypt_filename(enc_filename_b64: str, password: str) -> str:
raw = base64.urlsafe_b64decode(enc_filename_b64)
salt, nonce, ct = raw[:16], raw[16:28], raw[28:]
key = derive_key(password, salt)
return AESGCM(key).decrypt(nonce, ct, None).decode()
# ===== File Pickup Route ===== # ===== File Pickup Route =====
@app.route("/pickup/<file_id>", methods=["GET", "POST"]) @app.route("/pickup/<file_id>", methods=["GET", "POST"])
@@ -278,15 +295,22 @@ def handle_file_pickup(request, meta_path, enc_path, file_id):
os.remove(enc_path) os.remove(enc_path)
log_admin_event(f"File {file_id} downloaded and deleted.") log_admin_event(f"File {file_id} downloaded and deleted.")
try:
original_name = decrypt_filename(meta['original_name'], enc_password)
except Exception:
original_name = "retrieved_file"
response = send_file( response = send_file(
io.BytesIO(decrypted), io.BytesIO(decrypted),
as_attachment=True, as_attachment=True,
download_name=meta['original_name'],
download_name=original_name,
mimetype='application/octet-stream' mimetype='application/octet-stream'
) )
# Add headers for better mobile compatibility # Add headers for better mobile compatibility
response.headers['Content-Disposition'] = f'attachment; filename="{meta["original_name"]}"'
response.headers['Content-Disposition'] = f'attachment; filename="{original_name}"'
response.headers['Content-Type'] = 'application/octet-stream' response.headers['Content-Type'] = 'application/octet-stream'
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache' response.headers['Pragma'] = 'no-cache'
@@ -661,6 +685,96 @@ def robots_txt():
] ]
return "\n".join(lines), 200, {"Content-Type": "text/plain"} return "\n".join(lines), 200, {"Content-Type": "text/plain"}
# ===== API Endpoints =====
@app.route("/api/encrypt", methods=["POST"])
def api_encrypt():
try:
req = request.get_json()
password = req.get("password")
data_b64 = req.get("data")
if not password or not data_b64:
return jsonify({"error": "Missing data or password"}), 400
file_data = base64.b64decode(data_b64)
salt = os.urandom(16)
key = derive_key(password, salt)
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, file_data, None)
encrypted_binary = salt + nonce + ct
# Return base64 string of the binary
encrypted_b64 = base64.b64encode(encrypted_binary).decode()
return jsonify({"result": encrypted_b64}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/decrypt", methods=["POST"])
def api_decrypt():
try:
req = request.get_json()
password = req.get("password")
encrypted_b64 = req.get("data")
if not password or not encrypted_b64:
return jsonify({"error": "Missing data or password"}), 400
encrypted_binary = base64.b64decode(encrypted_b64)
salt, nonce, ct = encrypted_binary[:16], encrypted_binary[16:28], encrypted_binary[28:]
key = derive_key(password, salt)
decrypted = AESGCM(key).decrypt(nonce, ct, None)
return jsonify({"result": base64.b64encode(decrypted).decode()}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/pacshare", methods=["POST"])
def api_pacshare():
try:
enc_password = request.form.get("enc_password")
pickup_password = request.form.get("pickup_password")
file = request.files.get("file")
if not file or not enc_password or not pickup_password:
return jsonify({"error": "Missing file or fields"}), 400
file_data = file.read()
filename = secure_filename(file.filename)
salt = os.urandom(16)
key = derive_key(enc_password, salt)
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, file_data, None)
encrypted = salt + nonce + ct
file_id = secrets.token_urlsafe(24)
enc_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.enc")
meta_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.json")
with open(enc_path, "wb") as f:
f.write(encrypted)
encrypted_filename = encrypt_filename(filename, enc_password)
meta = {
'pickup_password': base64.urlsafe_b64encode(
hashlib.sha256(pickup_password.encode()).digest()
).decode(),
'original_name': encrypted_filename,
'timestamp': datetime.now().isoformat()
}
with open(meta_path, "w") as f:
json.dump(meta, f)
pickup_url = request.host_url.rstrip('/') + url_for('pickup_file', file_id=file_id)
return jsonify({"pickup_url": pickup_url})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ===== Error Handlers ===== # ===== Error Handlers =====
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
@@ -695,3 +809,4 @@ if __name__ == "__main__":
else: else:
print("[INFO] Running in DEVELOPMENT mode with Flask server.") print("[INFO] Running in DEVELOPMENT mode with Flask server.")
app.run(debug=True, host="0.0.0.0", port=5000) app.run(debug=True, host="0.0.0.0", port=5000)
Binary file not shown.
+14 -35
View File
@@ -7,16 +7,13 @@ import { encryptFile, decryptFile } from './fileops.js';
// ===== UI Initialization ===== // ===== UI Initialization =====
export function setupUI() { export function setupUI() {
// Set initial state of remove button to hidden
const removeBtn = document.getElementById("remove-file-btn"); const removeBtn = document.getElementById("remove-file-btn");
if (removeBtn) { if (removeBtn) {
removeBtn.style.display = "none"; removeBtn.style.display = "none";
} }
initializeEventListeners(); initializeEventListeners();
} }
// ===== Event Listeners =====
function initializeEventListeners() { function initializeEventListeners() {
const elements = { const elements = {
encryptionType: document.getElementById("encryption-type"), encryptionType: document.getElementById("encryption-type"),
@@ -55,10 +52,7 @@ function setupElementListeners(elements) {
elements.toggleSwitch.addEventListener("change", () => { elements.toggleSwitch.addEventListener("change", () => {
console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt"); console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt");
}); });
// Add file input change listener
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
if (fileInput) { if (fileInput) {
fileInput.addEventListener("change", () => { fileInput.addEventListener("change", () => {
@@ -93,7 +87,6 @@ function setupShareLinkListeners(elements) {
} }
} }
// ===== UI State Management =====
function toggleEncryptionOptions() { function toggleEncryptionOptions() {
const type = document.getElementById("encryption-type").value.trim().toLowerCase(); const type = document.getElementById("encryption-type").value.trim().toLowerCase();
const passwordInputWrapper = document.getElementById("password-input"); const passwordInputWrapper = document.getElementById("password-input");
@@ -101,19 +94,11 @@ function toggleEncryptionOptions() {
const isAdvanced = type.includes("advanced"); const isAdvanced = type.includes("advanced");
if (passwordInputWrapper) { if (passwordInputWrapper) {
if (isAdvanced) { passwordInputWrapper.classList.toggle("hidden", !isAdvanced);
passwordInputWrapper.classList.remove("hidden");
} else {
passwordInputWrapper.classList.add("hidden");
}
} }
if (fileSection) { if (fileSection) {
if (isAdvanced) { fileSection.classList.toggle("hidden", !isAdvanced);
fileSection.classList.remove("hidden");
} else {
fileSection.classList.add("hidden");
}
} }
updateToggleLabels(); updateToggleLabels();
@@ -150,7 +135,6 @@ function toggleInputMode() {
removeBtn.style.display = fileSelected ? "inline-block" : "none"; removeBtn.style.display = fileSelected ? "inline-block" : "none";
} }
// ===== Form Handling =====
async function handleSubmit(event) { async function handleSubmit(event) {
event.preventDefault(); event.preventDefault();
@@ -189,14 +173,18 @@ async function handleTextOperation(encryptionType, operation, password) {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
const data = await response.json(); const data = await response.json();
document.getElementById("output-text").value = data.result;
const outputField = document.getElementById("output-text");
if (outputField) {
outputField.value = data.result || "[Error] No response received.";
}
} catch (err) { } catch (err) {
alert("Error processing request: " + err.message); alert("Error processing request: " + err.message);
} }
} }
// ===== Utility Functions =====
function removeFile() { function removeFile() {
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
if (fileInput) fileInput.value = ""; if (fileInput) fileInput.value = "";
@@ -214,7 +202,6 @@ function generateRandomPassword() {
const passwordField = document.getElementById("generated-password"); const passwordField = document.getElementById("generated-password");
if (passwordField) { if (passwordField) {
passwordField.value = password; passwordField.value = password;
// Check if we should start Pacman
checkForPacman(); checkForPacman();
} }
} }
@@ -225,33 +212,27 @@ function copyToClipboard(elementId, feedbackId) {
if (!el || !el.value) return; if (!el || !el.value) return;
// Create a temporary textarea element
const textarea = document.createElement('textarea'); const textarea = document.createElement('textarea');
textarea.value = el.value; textarea.value = el.value;
textarea.style.position = 'fixed'; textarea.style.position = 'fixed';
textarea.style.opacity = '0'; textarea.style.opacity = '0';
document.body.appendChild(textarea); document.body.appendChild(textarea);
// Select and copy the text
textarea.select(); textarea.select();
textarea.setSelectionRange(0, 99999); // For mobile devices textarea.setSelectionRange(0, 99999);
try { try {
// Try using the modern clipboard API first
navigator.clipboard.writeText(el.value).then(() => { navigator.clipboard.writeText(el.value).then(() => {
showFeedback(feedback); showFeedback(feedback);
}).catch(() => { }).catch(() => {
// Fallback to execCommand for older browsers
document.execCommand('copy'); document.execCommand('copy');
showFeedback(feedback); showFeedback(feedback);
}); });
} catch (err) { } catch (err) {
// Final fallback
document.execCommand('copy'); document.execCommand('copy');
showFeedback(feedback); showFeedback(feedback);
} }
// Clean up
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
@@ -298,6 +279,7 @@ function checkForPacman() {
window.exitGame(); window.exitGame();
} }
} }
function copyShareLink() { function copyShareLink() {
const linkEl = document.getElementById("share-link"); const linkEl = document.getElementById("share-link");
const feedback = document.getElementById("shared-link-feedback"); const feedback = document.getElementById("shared-link-feedback");
@@ -346,8 +328,5 @@ function showCopyFeedback(feedbackEl) {
}, 3000); }, 3000);
} }
function startPacman() { } function startPacman() { }
function exitGame() { } function exitGame() { }