More api fixes

This commit is contained in:
Tyler
2025-05-17 14:49:12 -10:00
committed by GitHub
parent b7a85b8d84
commit 973aa0f20f
4 changed files with 150 additions and 60 deletions
+49 -18
View File
@@ -690,24 +690,38 @@ def robots_txt():
# ===== API Endpoints ===== # ===== API Endpoints =====
@app.route("/api/encrypt", methods=["POST"]) @app.route("/api/encrypt", methods=["POST"])
def api_encrypt_file(): def api_encrypt():
if "file" not in request.files or "password" not in request.form: try:
return jsonify({"error": "Missing file or password"}), 400 # 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
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 jsonify({"result": encrypted_b64})
# File encryption
if "file" in request.files and "enc_password" in request.form:
uploaded_file = request.files["file"] uploaded_file = request.files["file"]
password = request.form["enc_password"] password = request.form["enc_password"]
try:
file_data = uploaded_file.read() file_data = uploaded_file.read()
salt = os.urandom(16) salt = os.urandom(16)
key = derive_key(password, salt)
nonce = os.urandom(12) nonce = os.urandom(12)
key = derive_key(password, salt)
ct = AESGCM(key).encrypt(nonce, file_data, None) ct = AESGCM(key).encrypt(nonce, file_data, None)
encrypted_binary = salt + nonce + ct encrypted_binary = salt + nonce + ct
# Create proper output filename output_filename = f"{uploaded_file.filename}.encrypted"
original_filename = uploaded_file.filename
output_filename = f"{original_filename}.encrypted"
return send_file( return send_file(
BytesIO(encrypted_binary), BytesIO(encrypted_binary),
@@ -716,37 +730,54 @@ def api_encrypt_file():
mimetype="application/octet-stream" mimetype="application/octet-stream"
) )
return jsonify({"error": "Missing or invalid input"}), 400
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route("/api/decrypt", methods=["POST"]) @app.route("/api/decrypt", methods=["POST"])
def api_decrypt_file(): def api_decrypt():
if "file" not in request.files or "password" not in request.form: try:
return jsonify({"error": "Missing file or password"}), 400 # 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
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 jsonify({"result": plaintext.decode()})
# File decryption
if "file" in request.files and "enc_password" in request.form:
uploaded_file = request.files["file"] uploaded_file = request.files["file"]
password = request.form["enc_password"] password = request.form["enc_password"]
try:
encrypted_data = uploaded_file.read() encrypted_data = uploaded_file.read()
salt, nonce, ct = encrypted_data[:16], encrypted_data[16:28], encrypted_data[28:] salt, nonce, ct = encrypted_data[:16], encrypted_data[16:28], encrypted_data[28:]
key = derive_key(password, salt) key = derive_key(password, salt)
decrypted = AESGCM(key).decrypt(nonce, ct, None) decrypted = AESGCM(key).decrypt(nonce, ct, None)
# Strip `.encrypted` from filename filename = uploaded_file.filename
original_filename = uploaded_file.filename if filename.endswith(".encrypted"):
if original_filename.endswith(".encrypted"): filename = filename[:-10]
original_filename = original_filename[:-10]
else: else:
original_filename = f"decrypted_{original_filename}" filename = f"decrypted_{filename}"
return send_file( return send_file(
BytesIO(decrypted), BytesIO(decrypted),
as_attachment=True, as_attachment=True,
download_name=original_filename, download_name=filename,
mimetype="application/octet-stream" mimetype="application/octet-stream"
) )
return jsonify({"error": "Missing or invalid input"}), 400
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
+1
View File
@@ -1,6 +1,7 @@
### **requirements.txt** ### **requirements.txt**
flask==3.0.3 flask==3.0.3
flask-cors
cryptography==42.0.5 cryptography==42.0.5
waitress==2.1.2 waitress==2.1.2
werkzeug==3.0.1 werkzeug==3.0.1
+19 -4
View File
@@ -10,6 +10,23 @@ const IV_LENGTH = 12;
const PBKDF2_ITERATIONS = 200_000; const PBKDF2_ITERATIONS = 200_000;
const KEY_LENGTH = 256; 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 ===== // ===== Key Derivation =====
/** /**
* Derives an AES-GCM key from a password using PBKDF2. * 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(iv, salt.length);
output.set(new Uint8Array(ciphertext), salt.length + iv.length); output.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(...output)); return base64Encode(output.buffer);
} }
// ===== Decryption ===== // ===== Decryption =====
@@ -77,9 +94,7 @@ export async function encryptAdvanced(message, password) {
* @returns {Promise<string>} - Decrypted plaintext. * @returns {Promise<string>} - Decrypted plaintext.
*/ */
export async function decryptAdvanced(encryptedData, password) { export async function decryptAdvanced(encryptedData, password) {
const encrypted = new Uint8Array( const encrypted = base64Decode(encryptedData);
atob(encryptedData).split('').map(c => c.charCodeAt(0))
);
const salt = encrypted.slice(0, SALT_LENGTH); const salt = encrypted.slice(0, SALT_LENGTH);
const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
+54 -11
View File
@@ -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. * Encrypts a full file and downloads the encrypted version.
* Handles file encryption and decryption operations.
*/ */
// ===== Constants =====
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
// ===== Public Interface =====
export async function encryptFile(fileInput, password) { export async function encryptFile(fileInput, password) {
const file = fileInput.files[0]; const file = fileInput.files[0];
if (!file) return; if (!file) return;
try { try {
const encryptedChunks = await processFile(file, password, true); const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
downloadEncryptedFile(encryptedChunks, file.name); 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) { } catch (error) {
alert("Error encrypting file: " + error.message); alert("Error encrypting file: " + error.message);
} }
@@ -24,8 +48,27 @@ export async function decryptFile(fileInput, password) {
if (!file) return; if (!file) return;
try { try {
const decryptedChunks = await processFile(file, password, false); const data = new Uint8Array(await file.arrayBuffer());
downloadDecryptedFile(decryptedChunks, file.name); 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) { } catch (error) {
alert("Error decrypting file: " + error.message); alert("Error decrypting file: " + error.message);
} }