More api fixes
This commit is contained in:
@@ -690,24 +690,38 @@ 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
|
||||
def api_encrypt():
|
||||
try:
|
||||
# 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"]
|
||||
password = request.form["enc_password"]
|
||||
|
||||
try:
|
||||
file_data = uploaded_file.read()
|
||||
salt = os.urandom(16)
|
||||
key = derive_key(password, salt)
|
||||
nonce = os.urandom(12)
|
||||
key = derive_key(password, salt)
|
||||
ct = AESGCM(key).encrypt(nonce, file_data, None)
|
||||
encrypted_binary = salt + nonce + ct
|
||||
|
||||
# Create proper output filename
|
||||
original_filename = uploaded_file.filename
|
||||
output_filename = f"{original_filename}.encrypted"
|
||||
output_filename = f"{uploaded_file.filename}.encrypted"
|
||||
|
||||
return send_file(
|
||||
BytesIO(encrypted_binary),
|
||||
@@ -716,37 +730,54 @@ def api_encrypt_file():
|
||||
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
|
||||
def api_decrypt():
|
||||
try:
|
||||
# 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"]
|
||||
password = request.form["enc_password"]
|
||||
|
||||
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)
|
||||
|
||||
# Strip `.encrypted` from filename
|
||||
original_filename = uploaded_file.filename
|
||||
if original_filename.endswith(".encrypted"):
|
||||
original_filename = original_filename[:-10]
|
||||
filename = uploaded_file.filename
|
||||
if filename.endswith(".encrypted"):
|
||||
filename = filename[:-10]
|
||||
else:
|
||||
original_filename = f"decrypted_{original_filename}"
|
||||
filename = f"decrypted_{filename}"
|
||||
|
||||
return send_file(
|
||||
BytesIO(decrypted),
|
||||
as_attachment=True,
|
||||
download_name=original_filename,
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
### **requirements.txt**
|
||||
|
||||
flask==3.0.3
|
||||
flask-cors
|
||||
cryptography==42.0.5
|
||||
waitress==2.1.2
|
||||
werkzeug==3.0.1
|
||||
|
||||
+19
-4
@@ -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<string>} - 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);
|
||||
|
||||
+54
-11
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user