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 subprocess
import platform
from datetime import datetime, timedelta
from datetime import datetime
import sys
import psutil
# ===== Third-Party Imports =====
from flask import (
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 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())
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)
key = derive_key(password, salt)
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
return base64.urlsafe_b64encode(salt + nonce + ct).decode()
key = derive_key(password, salt)
ciphertext = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
return base64.b64encode(salt + nonce + ciphertext).decode()
def advanced_decrypt(token_b64: str, password: str) -> str:
"""Decrypt text using AES-GCM with password-derived key."""
def advanced_decrypt(data_b64: str, password: str) -> str:
"""Decrypt base64-encoded AES-GCM encrypted data."""
try:
data = base64.urlsafe_b64decode(token_b64.encode())
salt, nonce, ct = data[:16], data[16:28], data[28:]
data = base64.b64decode(data_b64)
salt, nonce, ciphertext = data[:16], data[16:28], data[28:]
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:
return "[Error] Invalid password or corrupted data!"
@@ -207,7 +208,7 @@ def handle_file_upload(request):
meta = {
'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()
}
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})
def handle_text_operation(request):
"""Process text encryption/decryption operations."""
data = request.get_json()
encryption_type = data.get("encryption-type", "basic")
operation = data.get("operation", "")
@@ -226,10 +226,27 @@ def handle_text_operation(request):
if encryption_type == "basic":
result = simple_encode(message) if operation == "encrypt" else simple_decode(message)
else:
result = advanced_encrypt(message, password) if operation == "encrypt" else advanced_decrypt(message, password)
return jsonify(result=html.escape(result))
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 =====
@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)
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(
io.BytesIO(decrypted),
as_attachment=True,
download_name=meta['original_name'],
download_name=original_name,
mimetype='application/octet-stream'
)
# 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['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
@@ -661,6 +685,96 @@ def robots_txt():
]
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 =====
@app.errorhandler(404)
def page_not_found(e):
@@ -695,3 +809,4 @@ if __name__ == "__main__":
else:
print("[INFO] Running in DEVELOPMENT mode with Flask server.")
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 =====
export function setupUI() {
// Set initial state of remove button to hidden
const removeBtn = document.getElementById("remove-file-btn");
if (removeBtn) {
removeBtn.style.display = "none";
}
initializeEventListeners();
}
// ===== Event Listeners =====
function initializeEventListeners() {
const elements = {
encryptionType: document.getElementById("encryption-type"),
@@ -55,10 +52,7 @@ function setupElementListeners(elements) {
elements.toggleSwitch.addEventListener("change", () => {
console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt");
});
// Add file input change listener
const fileInput = document.getElementById("file-input");
if (fileInput) {
fileInput.addEventListener("change", () => {
@@ -93,7 +87,6 @@ function setupShareLinkListeners(elements) {
}
}
// ===== UI State Management =====
function toggleEncryptionOptions() {
const type = document.getElementById("encryption-type").value.trim().toLowerCase();
const passwordInputWrapper = document.getElementById("password-input");
@@ -101,19 +94,11 @@ function toggleEncryptionOptions() {
const isAdvanced = type.includes("advanced");
if (passwordInputWrapper) {
if (isAdvanced) {
passwordInputWrapper.classList.remove("hidden");
} else {
passwordInputWrapper.classList.add("hidden");
}
passwordInputWrapper.classList.toggle("hidden", !isAdvanced);
}
if (fileSection) {
if (isAdvanced) {
fileSection.classList.remove("hidden");
} else {
fileSection.classList.add("hidden");
}
fileSection.classList.toggle("hidden", !isAdvanced);
}
updateToggleLabels();
@@ -150,7 +135,6 @@ function toggleInputMode() {
removeBtn.style.display = fileSelected ? "inline-block" : "none";
}
// ===== Form Handling =====
async function handleSubmit(event) {
event.preventDefault();
@@ -189,14 +173,18 @@ async function handleTextOperation(encryptionType, operation, password) {
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
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) {
alert("Error processing request: " + err.message);
}
}
// ===== Utility Functions =====
function removeFile() {
const fileInput = document.getElementById("file-input");
if (fileInput) fileInput.value = "";
@@ -214,7 +202,6 @@ function generateRandomPassword() {
const passwordField = document.getElementById("generated-password");
if (passwordField) {
passwordField.value = password;
// Check if we should start Pacman
checkForPacman();
}
}
@@ -225,33 +212,27 @@ function copyToClipboard(elementId, feedbackId) {
if (!el || !el.value) return;
// Create a temporary textarea element
const textarea = document.createElement('textarea');
textarea.value = el.value;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
// Select and copy the text
textarea.select();
textarea.setSelectionRange(0, 99999); // For mobile devices
textarea.setSelectionRange(0, 99999);
try {
// Try using the modern clipboard API first
navigator.clipboard.writeText(el.value).then(() => {
showFeedback(feedback);
}).catch(() => {
// Fallback to execCommand for older browsers
document.execCommand('copy');
showFeedback(feedback);
});
} catch (err) {
// Final fallback
document.execCommand('copy');
showFeedback(feedback);
}
// Clean up
document.body.removeChild(textarea);
}
@@ -298,6 +279,7 @@ function checkForPacman() {
window.exitGame();
}
}
function copyShareLink() {
const linkEl = document.getElementById("share-link");
const feedback = document.getElementById("shared-link-feedback");
@@ -346,8 +328,5 @@ function showCopyFeedback(feedbackEl) {
}, 3000);
}
function startPacman() { }
function exitGame() { }
function exitGame() { }