-
Notifications
You must be signed in to change notification settings - Fork 0
/
encrypt-file.html
235 lines (204 loc) · 7.88 KB
/
encrypt-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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File Encryptor</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/forge.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ponyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/StreamSaver.min.js"></script>
</head>
<body>
<div>
File to encrypt:
<input type="file" id="file-upload"/>
<br/>
Type Password:
<input type="text" id="password" size="128"/>
<br/>
<button id="encryptBtn" onclick="encryptFileAsync(event)">Encrypt File</button>
</div>
<br/>
<br/>
<div>
Progress:
<div id="progressBar"></div>
</div>
<script type="text/Javascript">
var encryptionDone = 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 encryptFileAsync() {
var writer = undefined
try {
encryptionDone = 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);
const salt = forge.random.getBytesSync(saltSize);
// This is public info. No need to hide it.
console.log("Salt: " + forge.util.bytesToHex(salt))
// This will take some time depending on the numIterations
// This is a secret. Don't show it to anybody.
const key = forge.pkcs5.pbkdf2(password, salt, numIterations, keySize);
const cipher = forge.cipher.createCipher(algorithm, key);
cipher.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
});
// Read the file 512kb at a time
// I did some tests and it doesn't seem that larger values improve performance.
// I tried with 10MB but that results in around 6.5MB/s operation
// 512kb results in 9MB/s speed which is almost twice as fast.
const sliceSize = 1024 * 512;
var numberOfSlices = parseInt(file.size / 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 + '.enc'
writer = createStream(fileName, finalFileSize)
// The WriteStream requries all the data to be in Uint8Array form
writer.write(convertBinaryStringToUint8Array(salt))
let startTime = performance.now(); //start time
var hashPromise = null;
for (i = 0; i < numberOfSlices; ++i) {
const startOffset = i * sliceSize;
const endOffset = startOffset + sliceSize;
// 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 half a megabyte we need 2 slices
// to form a megabyte. Therefore 20 slices to get to 10MB.
if ((i % 20) == 0) {
progressBar.innerHTML = "" + ((i + 1) / numberOfSlices * 100) + "%";
}
// The data is read as ArrayBuffer.
// This is a neat trick. Since we are an async function we caxn use await to
// read the file "sequentially" and not bother with promises etc.
const data = await readFilePromise(file, startOffset, endOffset);
// We need to create a forge buffer in order to encrypt it successfully
// If the data is read as a binary string forge.util.createBuffer() is
// much more efficient. I wish it didn't have to convert the data at all.
const byteBuffer = forge.util.createBuffer(data)
// We are ready to encrypt
cipher.update(forge.util.createBuffer(data));
// Grabbing the bytes from the cipher will free the memory of the already encrypted bytes
// This allows us to run the encryption and decryption with constant memory
const encryptedBytes = cipher.output.getBytes()
// The encrypted bytes are a binary string so we need to
// convert them to a Uint8Array for the WriteStream to work
const encryptedArray = convertBinaryStringToUint8Array(encryptedBytes)
writer.write(encryptedArray)
}
// This will close the cipher and might have some remaining data in it
cipher.finish();
progressBar.innerHTML = "100%"
// Write remaining data
const encryptedBytes = convertBinaryStringToUint8Array(cipher.output.getBytes())
writer.write(encryptedBytes)
// Handle the authentication tag
authenticationTag = cipher.mode.tag.getBytes()
// This is public info. No need to hide it.
console.log("Tag: " + forge.util.bytesToHex(authenticationTag))
writer.write(convertBinaryStringToUint8Array(authenticationTag))
let endTime = performance.now(); //end time
let executionTimeInSeconds = (endTime - startTime) / 1000;
console.log('Encryption took: '+ executionTimeInSeconds +' seconds');
} catch(error) {
console.log(error);
} finally {
if (typeof writer != "undefined") {
writer.close()
}
encryptionDone = true
}
}
/**
* This function creates a WriteStream for the specified fileName and size.
**/
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()
alert("onunload")
}
window.onbeforeunload = evt => {
if (!encryptionDone) {
evt.returnValue = `Are you sure you want to leave?`;
alert("Ar you sure")
}
}
return writer
}
/**
* Converts a Uint8Array to a Binary String. This is needed to do proper
* writing and reading from the WriteStream
**/
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;
}
/**
* Converts a Binary String to a Uint8Array. This is needed to do proper
* writing and reading from the WriteStream
**/
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>