CryptoJS-Problem beim Entschlüsseln eines verschlüsselten Datei-Download-Chunked-StreamsJavaScript

Javascript-Forum
Guest
 CryptoJS-Problem beim Entschlüsseln eines verschlüsselten Datei-Download-Chunked-Streams

Post by Guest »

Ich habe eine beispielhafte clientseitige HTML-Webseite, die es einem Benutzer ermöglicht, eine Datei auf einen NodeJS-Dateispeicherserver hochzuladen. Wenn ein Benutzer eine Datei von der Webseite hochlädt, verschlüsselt der Browser des Clients die Datei mit einem Passwort, bevor er sie an den Server sendet. Der Benutzer kann die Datei dann von dieser Webseite herunterladen, indem er den hochgeladenen Dateinamen und das Passwort eingibt – der Browser lädt die Datei herunter und entschlüsselt sie. Dies geschieht mithilfe von Streams und CryptoJS, sodass ich große Dateien hoch- und herunterladen kann. (Ich hatte schon früher Erfolg mit der Verwendung von Zeichenfolgen, aber bei großen Dateien funktioniert das offensichtlich nicht.)
Ich habe es geschafft, den Verschlüsselungsprozess zum Laufen zu bringen: Ich kann eine Datei hochladen der Server, den der Browser des Clients verschlüsselt – ich kann die hochgeladene Datei auf dem Server sehen. Allerdings habe ich beim Herunterladen Probleme beim Entschlüsseln und Herunterladen der Datei. Die heruntergeladene Datei wird jedes Mal mit einer Größe von 0 Bytes und ohne Daten angezeigt.
Ich habe überprüft, dass die verschlüsselte Datei-Blob-Größe in Bytes vom Client beim Herunterladen korrekt empfangen wurde. Das Salz und die Infusion werden ebenfalls erhalten. Es scheint jedoch, dass die Variable „value“ immer undefiniert ist, wenn sie auf „wait reader.read();“ eingestellt wird – „Kein Wert erkannt“ wird protokolliert. Zu diesem Zeitpunkt bin ich mir nicht ganz sicher, wo das Problem liegt, da keine Fehler ausgegeben werden.
Ich habe unten ein kleines reproduzierbares Beispiel angehängt.
Browser-Client-Code (HTML):

Code: Select all





File Upload & Download


Upload and Download Large Files

Upload File

Upload

Download File

Download







Code für Client app.js:

Code: Select all

// Utility: Derive AES Key from Password
async function deriveKey(password, salt) {
const enc = new TextEncoder();
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveKey']
);

return window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-CBC', length: 256 },
false,
['encrypt', 'decrypt']
);
}

// Utility: Generate Random Initialization Vector (IV)
function generateIV() {
return crypto.getRandomValues(new Uint8Array(16)); // 16 bytes for AES-CBC
}

// Encrypt File in Chunks
async function encryptFile(file, password) {
const chunkSize = 64 * 1024; // 64 KB
const reader = file.stream().getReader();
const enc = new TextEncoder();

// Generate salt and IV
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = generateIV();

// Derive AES key
const key = await deriveKey(password, salt);

// Create a Blob for the encrypted output
const encryptedChunks = [];
encryptedChunks.push(salt); // Store salt in the output
encryptedChunks.push(iv);   // Store IV in the output

let done = false;

while (!done) {
const { value, done: isDone } = await reader.read();
if (value) {
const encryptedChunk = await crypto.subtle.encrypt(
{ name: 'AES-CBC', iv: iv },
key,
value
);
encryptedChunks.push(new Uint8Array(encryptedChunk));
}
done = isDone;
}

return new Blob(encryptedChunks, { type: 'application/octet-stream' });
}

async function decryptFile(encryptedBlob, password) {
// Check there is a blob to begin with
if (!encryptedBlob) {
throw new Error('No encrypted file provided.');
} else {
console.log('Encrypted Blob size:', encryptedBlob.size);
}

const reader = encryptedBlob.stream().getReader();

// Read the first 32 bytes: 16 for salt, 16 for IV
const { value: header } = await reader.read();
if (!header) {
throw new Error('Failed to read the file header.');
}
const salt = header.slice(0, 16); // First 16 bytes are salt
const iv = header.slice(16, 32);  // Next 16 bytes are IV

console.log(`Salt:`, salt);
console.log(`IV:`, iv);

// Derive AES key
const key = await deriveKey(password, salt);

console.log(`Key: ${key}`);

const decryptedChunks = [];
let done = false;

// Start reading chunks
while (!done) {
const { value, done: isDone } = await reader.read();
done = isDone;

if (value) {
console.log("Value detected!");
try {
// Decrypt each chunk
const decryptedChunk = await crypto.subtle.decrypt(
{ name: 'AES-CBC', iv },
key,
value.buffer
);
decryptedChunks.push(new Uint8Array(decryptedChunk));
} catch (error) {
throw new Error('Decryption failed.  Check the password or file integrity.');
}
} else {
console.warn('No value detected!');
}
}

// Combine all decrypted chunks into a single Blob
return new Blob(decryptedChunks, { type: 'application/octet-stream' });
}

document.getElementById('uploadButton').addEventListener('click', async () => {
const fileInput = document.getElementById('fileInput');
const uploadStatus = document.getElementById('uploadStatus');
const password = prompt('Enter a password to encrypt the file:');

if (!fileInput.files.length || !password) {
uploadStatus.textContent = 'Please select a file and enter a password.';
return;
}

const file = fileInput.files[0];
uploadStatus.textContent = 'Encrypting file...';

try {
const encryptedFile = await encryptFile(file, password);

const formData = new FormData();
formData.append('file', encryptedFile, `${file.name}.enc`);

const response = await fetch('/upload', {
method: 'POST',
body: formData,
});

if (response.ok) {
uploadStatus.textContent = 'File uploaded successfully!';
} else {
uploadStatus.textContent = 'File upload failed.';
}
} catch (err) {
console.error('Error encrypting file:', err);
uploadStatus.textContent = 'Encryption failed.';
}
});

document.getElementById('downloadButton').addEventListener('click', async () => {
const filename = document.getElementById('downloadFilename').value;
const password = prompt('Enter the password to decrypt the file:');

if (!filename || !password) {
alert('Please enter the filename and password.');
return;
}

const response = await fetch(`/download/${filename}.enc`);

if (response.ok) {
const encryptedBlob = await response.blob();

try {
const decryptedBlob = await decryptFile(encryptedBlob, password);

// Trigger download of the decrypted file
const a = document.createElement('a');
const url = URL.createObjectURL(decryptedBlob);
a.href = url;
a.download = filename.replace('.enc', '');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

alert('File decrypted and downloaded successfully!');
} catch (err) {
alert(err);
}
} else {
alert('Failed to download the file.');
}
});
Node.JS-Servercode (sollte keine Kryptografie durchführen; speichert nur verschlüsselte Daten):

Code: Select all

const express = require('express');
const fs = require('fs');
const multer = require('multer');
const path = require('path');

const app = express();
const PORT = 3000;

// Configure multer for file uploads
const upload = multer({
dest: 'uploads/' // Directory to store uploaded files
});

// Serve static files for the client-side HTML/JS
app.use(express.static(path.join(__dirname, 'public')));

// Endpoint to upload a file
app.post('/upload', upload.single('file'), (req, res) => {
const tempPath = req.file.path;
const targetPath = path.join(__dirname, 'uploads', req.file.originalname);

// Move the file to a permanent location
const readStream = fs.createReadStream(tempPath);
const writeStream = fs.createWriteStream(targetPath);

readStream.on('error', (err) => {
console.error('Error reading file:', err);
res.status(500).send('File upload failed.');
});

writeStream.on('error', (err) => {
console.error('Error writing file:', err);
res.status(500).send('File upload failed.');
});

writeStream.on('finish', () => {
fs.unlink(tempPath, (err) => {
if (err) console.error('Failed to delete temp file:', err);
});
res.status(200).send('File uploaded successfully!');
});

readStream.pipe(writeStream);
});

// Endpoint to download a file
app.get('/download/:filename', (req, res) =>  {
const filePath = path.join(__dirname, 'uploads', req.params.filename);

if (!fs.existsSync(filePath)) {
return res.status(404).send('File not found');
}

const readStream = fs.createReadStream(filePath);
res.setHeader('Content-Disposition', `attachment; filename="${req.params.filename}"`);
res.setHeader('Content-Type', 'application/octet-stream');

readStream.on('error', (err) => {
console.error('Error reading file:', err);
res.status(500).send('File download failed.');
});

readStream.pipe(res);
});

// Start the server
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
});
Kann mir jemand helfen zu verstehen, was ich falsch mache?

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post