-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathhadecrypt.js
More file actions
108 lines (100 loc) · 4.24 KB
/
Copy pathhadecrypt.js
File metadata and controls
108 lines (100 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
function printStatus(str) {
document.getElementById("status").innerHTML += str + "<br>";
}
async function getPasswordHash(pw) {
let encoder = new TextEncoder();
let data = encoder.encode(pw);
for (let i = 0; i < 100; i++) {
data = await window.crypto.subtle.digest("SHA-256", data);
}
return new Uint8Array(data.slice(0, 16));
}
async function generateIV(key, salt) {
let iv = new Uint8Array([...key, ...salt]);
for (var i = 0; i < 100; i++) {
iv = await window.crypto.subtle.digest("SHA-256", iv);
}
return iv.slice(0, 16);
}
async function processFile(file, keyInput) {
try {
const reader = file.stream().getReader();
const keyRaw = await getPasswordHash(keyInput);
const key = await crypto.subtle.importKey("raw", keyRaw, { name: "AES-CBC" }, true, ["decrypt", "encrypt"]);
// Create handle to unencrypted file.
const root = await navigator.storage.getDirectory();
const targetFilename = "unencrypted-" + file.name;
const handle = await root.getFileHandle(targetFilename, { create: true });
const writable = await handle.createWritable();
const blocksize = 16;
const chunkSize = 4 * 1024 * 1024; // 4 MB
const numChunks = Math.ceil((file.size - 16) / chunkSize);
// Decompress in chunks to avoid loading the whole file in memory.
// WebCrypto code based on https://stackoverflow.com/a/75839277.
let index = 0;
let iv = null;
let availableData = new Uint8Array();
while (true) {
let availableBytes = availableData.length;
if (availableBytes < chunkSize) {
let parts = [availableData];
while (availableBytes < chunkSize) {
let data = await reader.read();
if (data.done) {
break;
}
parts.push(data.value);
availableBytes += data.value.length;
}
let buf = new Uint8Array(availableBytes);
let offset = 0;
parts.forEach(chunk => {
buf.set(chunk, offset);
offset += chunk.byteLength;
});
availableData = buf;
}
if (index === 0 && iv === null) {
const cbcRand = availableData.slice(0, 16);
availableData = availableData.slice(16);
iv = await generateIV(keyRaw, cbcRand);
continue;
}
if (index === numChunks - 1) {
// Last chunk.
const unencrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: iv }, key, availableData);
await writable.write(unencrypted);
break;
}
const chunk = availableData.slice(0, chunkSize);
const lastCiphertextBlock = chunk.slice(-blocksize);
const padCiphertextBlock = await crypto.subtle.encrypt({ name: 'AES-CBC', iv: lastCiphertextBlock }, key, new Uint8Array());
const fullChunk = new Uint8Array(chunk.byteLength + padCiphertextBlock.byteLength);
fullChunk.set(chunk, 0);
fullChunk.set(new Uint8Array(padCiphertextBlock), chunk.byteLength);
const unencrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: iv }, key, fullChunk);
await writable.write(unencrypted);
iv = lastCiphertextBlock;
index++;
availableData = availableData.slice(chunkSize);
}
await writable.close();
printStatus(`<span style="color:green">Decrypted ${file.name}!</span>`);
const targetFile = await handle.getFile();
const uri = window.URL.createObjectURL(targetFile);
const a = document.createElement("a");
a.style.display = "none";
a.download = targetFilename;
a.href = uri;
document.body.appendChild(a);
a.click();
} catch (e) {
printStatus(`<span style="color:red">Error: ${e.toString()} (wrong key?)</span>`);
}
}
function processFiles(files, keyInput) {
for (let file of files) {
printStatus(`Decrypting ${file.name}...`);
processFile(file, keyInput);
}
}