-
Notifications
You must be signed in to change notification settings - Fork 0
/
decrypt-file.html
215 lines (187 loc) · 6.88 KB
/
decrypt-file.html
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File Decryptor</title>
<script src="https://cdn.jsdelivr.net/npm/node-forge@0.9.0/dist/forge.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web-streams-polyfill@3.0.0/dist/ponyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/streamsaver@2.0.5/StreamSaver.min.js"></script>
</head>
<body>
<div>
File to decrypt:
<input type="file" id="file-upload"/>
<br/>
Type Password:
<input type="text" id="password" size="128"/>
<br/>
<button id="decryptBtn" onclick="decryptFileAsync(event)">Decrypt File</button>
</div>
<br/>
<br/>
<div>
Progress:
<div id="progressBar"></div>
</div>
<script type="text/Javascript">
var decryptionDone = false
function readFilePromise(file, start, end) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
// .slice() allows you to get slices of the file here we take a slice of the entire file
const fileSliceBlob = file.slice(start, end);
reader.readAsBinaryString(fileSliceBlob);
reader.onload = function() {
resolve(reader.result);
}
reader.onerror = function() {
reject(reader.error);
}
});
}
async function decryptFileAsync() {
var writer = undefined
try {
decryptionDone = false
const file = document.getElementById("file-upload").files[0];
const password = document.getElementById("password").value;
const algorithm = "AES-GCM";
// Size of key in bytes
// Use 32 byte (256 bit) key
const keySize = 32;
// Size of iv in bytes
// Use 12 byte (96 bit) iv
const ivLength = 12;
// Size of authentication tag in bytes
// Use 16 byte (128 bit) tag
const tagLength = 16;
// Size of salt in bytes
// Use 16 byte (128 bit) salt
const saltSize = 16;
// Number of PBKDF cycles
// The more the better but also the slower the key will be generated
const numIterations = 100000;
// Lets create an empty iv that contains all 0s
const iv = new Uint8Array(ivLength);
iv.fill(0, 0, ivLength);
// Read the salt from the destination file
const salt = await readFilePromise(file, 0, saltSize, "binaryString");
// This is public info. No need to hide it.
console.log("Salt: " + forge.util.bytesToHex(salt))
// Generate the key from the salt and password
// This is a secret. Don't show it to anybody.
const key = forge.pkcs5.pbkdf2(password, salt, numIterations, keySize);
// Read the authentication tag
const tag = await readFilePromise(file, -tagLength, file.size)
// Also public info. Let's display it.
console.log("Tag: " + forge.util.bytesToHex(tag))
const decipher = forge.cipher.createDecipher(algorithm, key);
decipher.start({
iv: iv, // should be a 12-byte binary-encoded string or byte buffer
//additionalData: 'binary-encoded string', // optional
tagLength: tagLength * 8, // optional, defaults to 128 bits
tag: tag
});
// Read the file 1MB at a time
const sliceSize = 1024 * 1000;
var numberOfSlices = parseInt((file.size - saltSize - tagLength) / sliceSize, 10) + 1;
if (sliceSize > file.size) {
numberOfSlices = 1;
}
console.log(
"Processing file: " + file.name + " with size: " + file.size, ", slice size: " +
sliceSize + ", number of slices: " + numberOfSlices
);
// Create a writable stream
const finalFileSize = file.size - saltSize - tagLength
const fileName = file.name + '.dec'
writer = createStream(fileName, finalFileSize)
let startTime = performance.now(); //start time
for (i = 0; i < numberOfSlices; ++i) {
const startOffset = i * sliceSize + saltSize;
var endOffset = startOffset + sliceSize;
// Make sure we don't read the authentication tag
// It is saved at the end of the file
if (endOffset >= file.size - tagLength) {
endOffset = file.size - tagLength
}
// Update the user with some progress
// Report approximately every 10MB: ith slice is equal to i * sliceSize number of bytes processed
// 10MB are equal to 10MB / sliceSize. Since our slice size is a megabyte we can report each 10th slice
if ((i % 10) == 0) {
progressBar.innerHTML = "" + ((i + 1) / numberOfSlices * 100) + "%";
}
// This is a cool way to use a promise returning function.
const data = await readFilePromise(file, startOffset, endOffset)
// Decrypt the data
encryptedBytes = forge.util.createBuffer(data)
decipher.update(encryptedBytes)
// Convert the decrypted bytes into a Uint8Array for the WriteStream to work
const decryptedBytes = convertBinaryStringToUint8Array(decipher.output.getBytes())
// We are ready to write the result
writer.write(decryptedBytes)
}
const pass = decipher.finish();
// This will fail if the authentication tag doesn't match
if (!pass) {
console.log("!!! Error decrypting " + file.name)
alert("Error decrypting the file. Might be corrupted or tampered with.")
}
progressBar.innerHTML = "100%"
// Write remaining data if any
const decryptedBytes = convertBinaryStringToUint8Array(decipher.output.getBytes())
writer.write(decryptedBytes)
let endTime = performance.now(); //end time
let executionTimeInSeconds = (endTime - startTime) / 1000;
console.log('Decryption time: '+ executionTimeInSeconds +' seconds');
} catch(error) {
console.log(error);
} finally {
if (typeof writer != "undefined") {
writer.close()
}
decryptionDone = true
}
}
function createStream(fileName, fileSize) {
console.log("Creating a stream for file " + fileName + " with total size: " + fileSize)
// streamSaver.createWriteStream() returns a wirtable byte stream
// The WritableStream only accepts Uint8Array chunks
// (no other typed arrays, arrayBuffers or strings are allowed)
const fileStream = streamSaver.createWriteStream(fileName, {
size: fileSize, // (optional filesize) Will show progress
writableStrategy: undefined, // (optional)
readableStrategy: undefined // (optional)
})
writer = fileStream.getWriter()
// abort so it dose not look stuck
window.onunload = () => {
fileStream.abort()
// also possible to call abort on the writer you got from `getWriter()`
writer.abort()
}
window.onbeforeunload = evt => {
if (!decryptionDone) {
evt.returnValue = `Are you sure you want to leave?`;
alert("Ar you sure")
}
}
return writer
}
function convertUint8ArrayToBinaryString(u8Array) {
var i, len = u8Array.length, b_str = "";
for (i=0; i<len; i++) {
b_str += String.fromCharCode(u8Array[i]);
}
return b_str;
}
function convertBinaryStringToUint8Array(bStr) {
var i, len = bStr.length, u8_array = new Uint8Array(len);
for (var i = 0; i < len; i++) {
u8_array[i] = bStr.charCodeAt(i);
}
return u8_array;
}
</script>
</body>
</html>