I have a task: Implement a program that encrypts a file using a strong symmetric cipher. After researching the requirements and features, I chose the RC4 algorithm and its implementation in the CryptoJS library.
https://jsfiddle.net/alexander_js_developer/nuevwrp0/
HTML part:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div>
<h1>encrypt/decrypt file</h1>
<ol>
<li>Set password</li>
<li>Pick a file</li>
<li>Download decrypted/encrypted file</li>
</ol>
<div>
<input type="text" id="pass" placeholder="pass">
<button id="encrypt">encrypt file</button>
<button id="decrypt">decrypt file</button>
</div>
</div>
JavaScript part:
// support
const download = (data, filename, type) => {
const file = new Blob([data], { type: type });
const a = document.createElement('a');
const url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
const pickAFile = (getText = true) => {
return new Promise((resolve, reject) => {
const input = document.createElement('input');
input.type = 'file';
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
if (!getText) {
resolve(file);
} else {
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e);
reader.readAsText(file);
}
};
input.click();
});
};
// /support
function app () {
const passNode = document.querySelector('input#pass');
const encryptNode = document.querySelector('#encrypt');
const decryptNode = document.querySelector('#decrypt');
encryptNode.addEventListener('click', () => {
if (!passNode.value) return alert('Password input is empty! Aborting.');
const pass = CryptoJS.SHA3(passNode.value);
pickAFile(false).then((file) => {
const reader = new FileReader();
reader.onload = (e) => {
const encrypted = CryptoJS.RC4.encrypt(e.target.result, pass).toString();
download(encrypted, `encrypted-${file.name}`, file.type);
};
reader.readAsText(file);
});
});
decryptNode.addEventListener('click', () => {
if (!passNode.value) return alert('Password input is empty! Aborting.');
const pass = CryptoJS.SHA3(passNode.value);
pickAFile(false).then((file) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const decrypted = CryptoJS.RC4.decrypt(e.target.result, pass).toString(
CryptoJS.enc.Utf8
);
download(decrypted, `decrypted-${file.name}`, file.type);
} catch (error) {
console.log('wrong password!');
}
};
reader.readAsText(file);
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', app);
} else {
app();
}
This code quickly and stably encrypts and decrypts files with utf-8 content (.txt
, .js
). But binary files (pictures, .exe
, etc.) break.
I suspect this is the place: reader.readAsText(file)
.
The program reads the file as text and already at this stage the binary files get corrupted. I think that I need to convert the file into a bit stream and encrypt them already.
But I still don't understand how to do it. I know about typed arrays, buffers and views, but I have very little experience with them.
How can I implement the following schema:
encrypt: file.ex => bytes => encrypt (CryptoJS) => bytes => file.ex
decrypt: file.ex => bytes => decrypt (CryptoJS) => bytes => file.ex
?
Metadata is important to keep. It would be nice if they were also encrypted, but this is not necessary. The speed of encryption / decryption and the weight of encrypted files are also critical.
Thank you!