From 973aa0f20f3880c40122800232a89d7b566d5717 Mon Sep 17 00:00:00 2001 From: Tyler <68524461+TySP-Dev@users.noreply.github.com> Date: Sat, 17 May 2025 14:49:12 -1000 Subject: [PATCH] More api fixes --- app.py | 121 +++++++++++++++++++++++++--------------- requirements.txt | 1 + static/js/encryption.js | 23 ++++++-- static/js/fileops.js | 65 +++++++++++++++++---- 4 files changed, 150 insertions(+), 60 deletions(-) diff --git a/app.py b/app.py index 97f972a..b74e689 100644 --- a/app.py +++ b/app.py @@ -690,62 +690,93 @@ def robots_txt(): # ===== API Endpoints ===== @app.route("/api/encrypt", methods=["POST"]) -def api_encrypt_file(): - if "file" not in request.files or "password" not in request.form: - return jsonify({"error": "Missing file or password"}), 400 - - uploaded_file = request.files["file"] - password = request.form["enc_password"] - +def api_encrypt(): try: - file_data = uploaded_file.read() - 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 + # Text encryption + if request.is_json: + data = request.get_json() + message = data.get("message", "") + password = data.get("password", "") + if not message or not password: + return jsonify({"error": "Missing message or password"}), 400 - # Create proper output filename - original_filename = uploaded_file.filename - output_filename = f"{original_filename}.encrypted" + salt = os.urandom(16) + nonce = os.urandom(12) + key = derive_key(password, salt) + ciphertext = AESGCM(key).encrypt(nonce, message.encode(), None) + encrypted_combined = salt + nonce + ciphertext + encrypted_b64 = base64.b64encode(encrypted_combined).decode() - return send_file( - BytesIO(encrypted_binary), - as_attachment=True, - download_name=output_filename, - mimetype="application/octet-stream" - ) + return jsonify({"result": encrypted_b64}) + + # File encryption + if "file" in request.files and "enc_password" in request.form: + uploaded_file = request.files["file"] + password = request.form["enc_password"] + + file_data = uploaded_file.read() + salt = os.urandom(16) + nonce = os.urandom(12) + key = derive_key(password, salt) + ct = AESGCM(key).encrypt(nonce, file_data, None) + encrypted_binary = salt + nonce + ct + + output_filename = f"{uploaded_file.filename}.encrypted" + + return send_file( + BytesIO(encrypted_binary), + as_attachment=True, + download_name=output_filename, + mimetype="application/octet-stream" + ) + + return jsonify({"error": "Missing or invalid input"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/decrypt", methods=["POST"]) -def api_decrypt_file(): - if "file" not in request.files or "password" not in request.form: - return jsonify({"error": "Missing file or password"}), 400 - - uploaded_file = request.files["file"] - password = request.form["enc_password"] - +def api_decrypt(): try: - encrypted_data = uploaded_file.read() - salt, nonce, ct = encrypted_data[:16], encrypted_data[16:28], encrypted_data[28:] - key = derive_key(password, salt) - decrypted = AESGCM(key).decrypt(nonce, ct, None) + # Text decryption + if request.is_json: + data = request.get_json() + encrypted_b64 = data.get("message", "") + password = data.get("password", "") + if not encrypted_b64 or not password: + return jsonify({"error": "Missing message or password"}), 400 - # Strip `.encrypted` from filename - original_filename = uploaded_file.filename - if original_filename.endswith(".encrypted"): - original_filename = original_filename[:-10] - else: - original_filename = f"decrypted_{original_filename}" + raw = base64.b64decode(encrypted_b64) + salt, nonce, ct = raw[:16], raw[16:28], raw[28:] + key = derive_key(password, salt) + plaintext = AESGCM(key).decrypt(nonce, ct, None) - return send_file( - BytesIO(decrypted), - as_attachment=True, - download_name=original_filename, - mimetype="application/octet-stream" - ) + return jsonify({"result": plaintext.decode()}) + + # File decryption + if "file" in request.files and "enc_password" in request.form: + uploaded_file = request.files["file"] + password = request.form["enc_password"] + + encrypted_data = uploaded_file.read() + salt, nonce, ct = encrypted_data[:16], encrypted_data[16:28], encrypted_data[28:] + key = derive_key(password, salt) + decrypted = AESGCM(key).decrypt(nonce, ct, None) + + filename = uploaded_file.filename + if filename.endswith(".encrypted"): + filename = filename[:-10] + else: + filename = f"decrypted_{filename}" + + return send_file( + BytesIO(decrypted), + as_attachment=True, + download_name=filename, + mimetype="application/octet-stream" + ) + + return jsonify({"error": "Missing or invalid input"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 diff --git a/requirements.txt b/requirements.txt index d213d4a..6c080c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ ### **requirements.txt** flask==3.0.3 +flask-cors cryptography==42.0.5 waitress==2.1.2 werkzeug==3.0.1 diff --git a/static/js/encryption.js b/static/js/encryption.js index 82633c9..23862e0 100644 --- a/static/js/encryption.js +++ b/static/js/encryption.js @@ -10,6 +10,23 @@ const IV_LENGTH = 12; const PBKDF2_ITERATIONS = 200_000; const KEY_LENGTH = 256; +// ===== Binary-safe Base64 Helpers ===== +function base64Encode(buffer) { + const binary = Array.from(new Uint8Array(buffer)) + .map(byte => String.fromCharCode(byte)) + .join(''); + return btoa(binary); +} + +function base64Decode(b64str) { + const binary = atob(b64str); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} + // ===== Key Derivation ===== /** * Derives an AES-GCM key from a password using PBKDF2. @@ -66,7 +83,7 @@ export async function encryptAdvanced(message, password) { output.set(iv, salt.length); output.set(new Uint8Array(ciphertext), salt.length + iv.length); - return btoa(String.fromCharCode(...output)); + return base64Encode(output.buffer); } // ===== Decryption ===== @@ -77,9 +94,7 @@ export async function encryptAdvanced(message, password) { * @returns {Promise} - Decrypted plaintext. */ export async function decryptAdvanced(encryptedData, password) { - const encrypted = new Uint8Array( - atob(encryptedData).split('').map(c => c.charCodeAt(0)) - ); + const encrypted = base64Decode(encryptedData); const salt = encrypted.slice(0, SALT_LENGTH); const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); diff --git a/static/js/fileops.js b/static/js/fileops.js index 3743710..449f803 100644 --- a/static/js/fileops.js +++ b/static/js/fileops.js @@ -1,19 +1,43 @@ +import { deriveKey } from "./encryption.js"; // assuming shared deriveKey() + +const SALT_LENGTH = 16; +const IV_LENGTH = 12; +const KEY_LENGTH = 256; + /** - * File operations module. - * Handles file encryption and decryption operations. + * Encrypts a full file and downloads the encrypted version. */ - -// ===== Constants ===== -const CHUNK_SIZE = 1024 * 1024; // 1MB chunks - -// ===== Public Interface ===== export async function encryptFile(fileInput, password) { const file = fileInput.files[0]; if (!file) return; try { - const encryptedChunks = await processFile(file, password, true); - downloadEncryptedFile(encryptedChunks, file.name); + const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH)); + const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); + const key = await deriveKey(password, salt); + const fileBuffer = new Uint8Array(await file.arrayBuffer()); + + const ciphertext = await crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + key, + fileBuffer + ); + + const ctBytes = new Uint8Array(ciphertext); + const result = new Uint8Array(salt.length + iv.length + ctBytes.length); + result.set(salt); + result.set(iv, salt.length); + result.set(ctBytes, salt.length + iv.length); + + const blob = new Blob([result], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = file.name + ".encrypted"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); } catch (error) { alert("Error encrypting file: " + error.message); } @@ -24,8 +48,27 @@ export async function decryptFile(fileInput, password) { if (!file) return; try { - const decryptedChunks = await processFile(file, password, false); - downloadDecryptedFile(decryptedChunks, file.name); + const data = new Uint8Array(await file.arrayBuffer()); + const salt = data.slice(0, SALT_LENGTH); + const iv = data.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); + const ciphertext = data.slice(SALT_LENGTH + IV_LENGTH); + const key = await deriveKey(password, salt); + + const decrypted = await crypto.subtle.decrypt( + { name: "AES-GCM", iv }, + key, + ciphertext + ); + + const blob = new Blob([decrypted], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = file.name.replace(".encrypted", ""); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); } catch (error) { alert("Error decrypting file: " + error.message); }