From 9a0c11cce8414462859d0caa5cb4c06f3f80627a Mon Sep 17 00:00:00 2001 From: bertrand gaillard Date: Thu, 4 Feb 2021 15:57:54 +0100 Subject: [PATCH 1/4] Add doc about encryption --- samples/Encryption.md | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 samples/Encryption.md diff --git a/samples/Encryption.md b/samples/Encryption.md new file mode 100644 index 00000000..d2766bcb --- /dev/null +++ b/samples/Encryption.md @@ -0,0 +1,79 @@ + +# Flow.js with end to end encryption (E2EE) + +Warning: Crypto is complex, you have to clearly understand what you're doing. Risk is to create fake security sensation for users. +Quote from [mdn](https://developer.mozilla.org/fr/docs/Web/API/SubtleCrypto): +"If you're not sure you know what you are doing, you probably shouldn't be using this API." + +Warning 2: This code will only works with `flow.js` versions `3.x`. + +End to end encryption means you encrypt (and decrypt) data on client side and server side has no idea what data are about. + +There are multiple ways to encrypt files before send them to server: + +- 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. +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). + +- Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. +This approach is much more flexible and memory saving. + +Here is a code sample using second approach: + +```js +const flow = new Flow({ + testChunks: false, + target: '/upload', + chunkSize: 10 * 1024 * 1024, + allowDuplicateUploads: true, + forceChunkSize: false, + simultaneousUploads: 4, + uploadMethod: 'POST', + fileParameterName: 'file', + // Asynchronous function called before each chunk upload request + asyncReadFileFn: async function(flowObj, startByte, endByte, fileType, chunk) { + // Load file chunk in memory + const plaintextbytes = await readFileChunk(flowObj.file, startByte, endByte); + // Encrypt chunk + const cypherbytes = await encryptFileChunk(plaintextbytes, window.ivbytes, window.key); + + // Update chunk size to match encrypted chunk [Add 16 bytes from initialization vector] + chunk.chunkSize = chunk.chunkSize + 16; + + // Return new blob ready to send + const blob = new Blob([cypherbytes], {type: 'application/octet-stream'}); + return blob; + } +}); + +flow.on('fileAdded', file => { + // Update file size to match encrypted file [Add 16 bytes from initialization vector for each encrypted chunk] + file.size += file.chunks.length * 16; +}); + + +// Add an HTML5 File object to the list of files. +// The library will takes care about splitting in chunks +flow.addFile(file); + +function readFileChunk(file, startByte, endByte) { + return new Promise(resolve => { + const reader = new FileReader(); + reader.onload = () => { + const bytes = new Uint8Array(reader.result); + resolve(bytes); + }; + const blob = file.slice(startByte, endByte); + reader.readAsArrayBuffer(blob); + }); +} + +async function encryptFileChunk(plaintextbytes, iv, key) { + let cypherchunkbytes = await window.crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plaintextbytes); + + if(cypherchunkbytes) { + cypherchunkbytes = new Uint8Array(cypherchunkbytes); + return cypherchunkbytes; + } +} +``` + From 606b1cfec8fefb68f55bafdb8871e6327730a446 Mon Sep 17 00:00:00 2001 From: bertrand gaillard Date: Tue, 23 Feb 2021 17:29:05 +0100 Subject: [PATCH 2/4] Update encryption doc --- samples/Encryption.md | 44 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/samples/Encryption.md b/samples/Encryption.md index d2766bcb..3ac6d849 100644 --- a/samples/Encryption.md +++ b/samples/Encryption.md @@ -14,10 +14,9 @@ There are multiple ways to encrypt files before send them to server: - 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. 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). -- Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. -This approach is much more flexible and memory saving. +- Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. -Here is a code sample using second approach: +Here is an example: ```js const flow = new Flow({ @@ -77,3 +76,42 @@ async function encryptFileChunk(plaintextbytes, iv, key) { } ``` + +- Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/). + +Here is an example: + +```js +class StreamEncryptor { + constructor(gpgKeys) { + this.gpgKeys = gpgKeys; + this._reader = []; + } + + async init(flowObj) { + const { message } = await openpgp.encrypt({ + message: openpgp.message.fromBinary(flowObj.file.stream(), flowObj.file.name), + publicKeys: this.gpgKeys + }); + + this._reader[flowObj.uniqueIdentifier] = openpgp.stream.getReader(message.packets.write()); + flowObj.size = flowObj.file.size + compute_pgp_overhead(this.gpgKeys, flowObj.file.name); + } + + async read(flowObj, startByte, endByte, fileType, chunk) { + const buffer = await this._reader[flowObj.uniqueIdentifier].readBytes(flowObj.chunkSize); + if (buffer && buffer.length) { + return new Blob([buffer], {type: 'application/octet-stream'}); + } + } +} + +var encryptor = new StreamEncryptor(gpgKeys); +new Flow({ + // ... + asyncReadFileFn: encryptor.read.bind(encryptor), + initFileFn: encryptor.init.bind(encryptor), + forceChunkSize: true, +}); +``` + From a7f38f4b0f5ab4ee1b7ab461153687bb94aa4bd9 Mon Sep 17 00:00:00 2001 From: bertrandg Date: Tue, 23 Feb 2021 17:32:19 +0100 Subject: [PATCH 3/4] Update Encryption.md --- samples/Encryption.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/samples/Encryption.md b/samples/Encryption.md index 3ac6d849..5fb0dd02 100644 --- a/samples/Encryption.md +++ b/samples/Encryption.md @@ -9,12 +9,12 @@ Warning 2: This code will only works with `flow.js` versions `3.x`. End to end encryption means you encrypt (and decrypt) data on client side and server side has no idea what data are about. -There are multiple ways to encrypt files before send them to server: +## There are multiple ways to encrypt files before send them to server: -- 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. +### 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. 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). -- Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. +### Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. Here is an example: @@ -76,8 +76,7 @@ async function encryptFileChunk(plaintextbytes, iv, key) { } ``` - -- Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/). +### Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/). Here is an example: From e54b6e629f7da27a7a9cf0308654b95d13f0f24a Mon Sep 17 00:00:00 2001 From: bertrandg Date: Tue, 23 Feb 2021 17:33:47 +0100 Subject: [PATCH 4/4] Update Encryption.md --- samples/Encryption.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/Encryption.md b/samples/Encryption.md index 5fb0dd02..39228028 100644 --- a/samples/Encryption.md +++ b/samples/Encryption.md @@ -11,10 +11,11 @@ End to end encryption means you encrypt (and decrypt) data on client side and se ## There are multiple ways to encrypt files before send them to server: -### 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. +- **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.** + 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). -### Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request. +- **Add plaintext file to `flow.js` and, then, load & encrypt file chunks on the fly just before sending POST server request.** Here is an example: @@ -76,7 +77,7 @@ async function encryptFileChunk(plaintextbytes, iv, key) { } ``` -### Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/). +- **Encrypt the file as a stream using an asymmetric StreamEncryptor and [openpgpjs](https://openpgpjs.org/).** Here is an example: