Skip to content

Commit 218b2bf

Browse files
bertrandgBertrand GaillardRaphaël Droz
authored
doc: examples of async hooks for file & stream encryption (fix #337)
Documentation about encrypting files and stream relying on Flow.js (v3) chunking. Co-authored-by: Bertrand Gaillard <bertrand.gaillard@infomaniak.com> Co-authored-by: Raphaël Droz <raphael.droz@gmail.com>
1 parent 2d88480 commit 218b2bf

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

samples/Encryption.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
2+
# Flow.js with end to end encryption (E2EE)
3+
4+
Warning: Crypto is complex, you have to clearly understand what you're doing. Risk is to create fake security sensation for users.
5+
Quote from [mdn](https://developer.mozilla.org/fr/docs/Web/API/SubtleCrypto):
6+
"If you're not sure you know what you are doing, you probably shouldn't be using this API."
7+
8+
Warning 2: This code will only works with `flow.js` versions `3.x`.
9+
10+
End to end encryption means you encrypt (and decrypt) data on client side and server side has no idea what data are about.
11+
12+
## There are multiple ways to encrypt files before send them to server:
13+
14+
- **Load and encrypt full file and, then, give this big blob to `flow.js` which will takes care of spliting and sending it as usual.**
15+
16+
The big downside of this approach is that, at one time, you will have full plaintext file AND full cyphertext file in browser memory which is critical if you want to allow users to send big files on multiple devices (each device/os has his own memory managment policy).
17+
18+
- **Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request.**
19+
20+
Here is an example:
21+
22+
```js
23+
const flow = new Flow({
24+
testChunks: false,
25+
target: '/upload',
26+
chunkSize: 10 * 1024 * 1024,
27+
allowDuplicateUploads: true,
28+
forceChunkSize: false,
29+
simultaneousUploads: 4,
30+
uploadMethod: 'POST',
31+
fileParameterName: 'file',
32+
// Asynchronous function called before each chunk upload request
33+
asyncReadFileFn: async function(flowObj, startByte, endByte, fileType, chunk) {
34+
// Load file chunk in memory
35+
const plaintextbytes = await readFileChunk(flowObj.file, startByte, endByte);
36+
// Encrypt chunk
37+
const cypherbytes = await encryptFileChunk(plaintextbytes, window.ivbytes, window.key);
38+
39+
// Update chunk size to match encrypted chunk [Add 16 bytes from initialization vector]
40+
chunk.chunkSize = chunk.chunkSize + 16;
41+
42+
// Return new blob ready to send
43+
const blob = new Blob([cypherbytes], {type: 'application/octet-stream'});
44+
return blob;
45+
}
46+
});
47+
48+
flow.on('fileAdded', file => {
49+
// Update file size to match encrypted file [Add 16 bytes from initialization vector for each encrypted chunk]
50+
file.size += file.chunks.length * 16;
51+
});
52+
53+
54+
// Add an HTML5 File object to the list of files.
55+
// The library will takes care about splitting in chunks
56+
flow.addFile(file);
57+
58+
function readFileChunk(file, startByte, endByte) {
59+
return new Promise(resolve => {
60+
const reader = new FileReader();
61+
reader.onload = () => {
62+
const bytes = new Uint8Array(reader.result);
63+
resolve(bytes);
64+
};
65+
const blob = file.slice(startByte, endByte);
66+
reader.readAsArrayBuffer(blob);
67+
});
68+
}
69+
70+
async function encryptFileChunk(plaintextbytes, iv, key) {
71+
let cypherchunkbytes = await window.crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plaintextbytes);
72+
73+
if(cypherchunkbytes) {
74+
cypherchunkbytes = new Uint8Array(cypherchunkbytes);
75+
return cypherchunkbytes;
76+
}
77+
}
78+
```
79+
80+
- **Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/).**
81+
82+
Here is an example:
83+
84+
```js
85+
class StreamEncryptor {
86+
constructor(gpgKeys) {
87+
this.gpgKeys = gpgKeys;
88+
this._reader = [];
89+
}
90+
91+
async init(flowObj) {
92+
const { message } = await openpgp.encrypt({
93+
message: openpgp.message.fromBinary(flowObj.file.stream(), flowObj.file.name),
94+
publicKeys: this.gpgKeys
95+
});
96+
97+
this._reader[flowObj.uniqueIdentifier] = openpgp.stream.getReader(message.packets.write());
98+
flowObj.size = flowObj.file.size + compute_pgp_overhead(this.gpgKeys, flowObj.file.name);
99+
}
100+
101+
async read(flowObj, startByte, endByte, fileType, chunk) {
102+
const buffer = await this._reader[flowObj.uniqueIdentifier].readBytes(flowObj.chunkSize);
103+
if (buffer && buffer.length) {
104+
return new Blob([buffer], {type: 'application/octet-stream'});
105+
}
106+
}
107+
}
108+
109+
var encryptor = new StreamEncryptor(gpgKeys);
110+
new Flow({
111+
// ...
112+
asyncReadFileFn: encryptor.read.bind(encryptor),
113+
initFileFn: encryptor.init.bind(encryptor),
114+
forceChunkSize: true,
115+
});
116+
```
117+

0 commit comments

Comments
 (0)