14

I need to let users load files from their system, encrypt them on-the-fly and upload to the server and do the opposite thing (download files from the server, decrypt on the fly and let the user save them locally). The exact crypto method is not very important although AES is preferred.

Links like Encryption / decryption of binary data in the browser just tell you "use CryptoJS" but I was unable to find any actually working samples. All samples I found focus on dealing with strings while in binary data you can easily find invalid Unicode sequences.

Is there any working sample I can test which can process files of any kind?

Community
  • 1
  • 1
Alex
  • 2,469
  • 3
  • 28
  • 61

3 Answers3

14

Note: I won't explain how to decrypt the data, but that should be rather easy to figure out using the code for encryption and the documentation-links provided.

First of all, the user has to be able to select a file via an input element.

<input type="file" id="file-upload" onchange="processFile(event)">

You can then load the content of the file using the HTML5 FileReader API

function processFile(evt) {
    var file = evt.target.files[0],
        reader = new FileReader();

    reader.onload = function(e) {
        var data = e.target.result;

        // to be continued...
    }

    reader.readAsArrayBuffer(file);   
}

Encrypt the acquired data using the WebCrypto API.
If you don't want to randomly generate the key use crypto.subtle.deriveKey to create a key, for example, from a password that the user entered.

// [...]
var iv = crypto.getRandomValues(new Uint8Array(16)); // Generate a 16 byte long initialization vector

crypto.subtle.generateKey({ 'name': 'AES-CBC', 'length': 256 ]}, false, [ 'encrypt', 'decrypt' ])
    .then(key => crypto.subtle.encrypt({ 'name': 'AES-CBC', iv }, key, data))
    .then(encrypted => { /* ... */ });

Now you can send your encrypted data to the server (e.g. with AJAX). Obviously you will also have to somehow store the Initialization Vector to later successfully decrypt everything.


Here is a little example which alerts the length of the encrypted data.

Note: If it says Only secure origins are allowed, reload the page with https and try the sample again (This is a restriction of the WebCrypto API): HTTPS-Link

function processFile(evt) {
    var file = evt.target.files[0],
        reader = new FileReader();

    reader.onload = function(e) {
        var data = e.target.result,
            iv = crypto.getRandomValues(new Uint8Array(16));
      
        crypto.subtle.generateKey({ 'name': 'AES-CBC', 'length': 256 }, false, ['encrypt', 'decrypt'])
            .then(key => crypto.subtle.encrypt({ 'name': 'AES-CBC', iv }, key, data) )
            .then(encrypted => {
                console.log(encrypted);
                alert('The encrypted data is ' + encrypted.byteLength + ' bytes long'); // encrypted is an ArrayBuffer
            })
            .catch(console.error);
    }

    reader.readAsArrayBuffer(file);   
}
<input type="file" id="file-upload" onchange="processFile(event)">
schroffl
  • 579
  • 7
  • 17
  • 1
    Thank you. However, can it work with large files? In my tests, it's not possible to process 500+MB files with Chrome or Firefox on 8GB RAM machine.. Browsers crash. – Alex Nov 21 '16 at 13:43
  • I had the same problem when I played around with it, but I can't help you with that, sorry. – schroffl Nov 21 '16 at 14:08
  • how can i decrypt the console.log(encrypted) data – Manoj Bhardwaj Jul 13 '18 at 09:59
  • @ManojBhardwaj You put it through [`crypto.subtle.decrypt`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt), but pass the encrypted data as the third parameter. – schroffl Jul 13 '18 at 10:02
  • @schroffl i try but Uncaught Error: The error you provided does not contain a stack trace. please check my code https://jsfiddle.net/3ubLjhkn/3/ – Manoj Bhardwaj Jul 13 '18 at 10:08
  • You obviously can't use a different key for decryption, which you do when calling `crypto.subtle.generateKey` a second time and then passing it to `crypto.subtle.decrypt`. [**Working jsfiddle**](https://jsfiddle.net/3ubLjhkn/16/) – schroffl Jul 13 '18 at 10:22
6

See https://github.com/meixler/web-browser-based-file-encryption-decryption for an example showing encryption/decryption of arbitrary binary files using Javascript in the web browser, based on the Web Crypto API.

mti2935
  • 11,465
  • 3
  • 29
  • 33
  • Thanks. Do you happen to know if it supports streamed encryption/decryption of files of unlimited size so that it would not be limited by the amount of memory available to the web page? Does it work with Chrome/Firefox/Safari/Edge on desktop and mobile? – Alex Apr 08 '20 at 14:15
  • According to https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API#Browser_compatibility, the Web Crypto API is now supported by all of the major browsers for desktop and mobile. With regard to large files, I believe the size would be limited to the amount of memory available to the web browser. But, it could probably be modified to process the files in chunks to get around this limit. – mti2935 Apr 08 '20 at 16:25
  • A year ago (or so) we played with it and it didn't work. Chunking itself worked but appending to the file being written on the device wasn't working consistently across all major browsers (so encryption/decryption itself is just half of the work, you need to write the data you processed then, and this is the major caveat). – Alex Apr 09 '20 at 07:40
  • 1
    @Alex I haven't actually tried it, but it seems possible to write-stream to local files now, using the new Streams API (https://developer.mozilla.org/en-US/docs/Web/API/Streams_API). https://github.com/jimmywarting/StreamSaver.js also looks useful. – mti2935 Apr 09 '20 at 14:32
  • Hi guys. Did it work out for you with large files? – Tan Apr 04 '22 at 18:11
3

You can convert a file into blob using:

new Blob([document.querySelector('input').files[0]])

Here's the code to encrypt & decrypt a blob

async function encryptblob(blob) {

    let iv = crypto.getRandomValues(new Uint8Array(12));
    let algorithm = {
        name: "AES-GCM",
        iv: iv
    }

    let key = await crypto.subtle.generateKey(
        {
            name: "AES-GCM",
            length: 256
        },
        true,
        ["encrypt", "decrypt"]
    );

    let data = await blob.arrayBuffer();

    const result = await crypto.subtle.encrypt(algorithm, key, data);

    let exportedkey =  await crypto.subtle.exportKey("jwk", key)
 
    return [new Blob([result]), iv.toString(), exportedkey]

}

async function decryptblob(encblob, ivdata, exportedkey) {


    let key = await crypto.subtle.importKey(
        "jwk",
        exportedkey,
        { name: "AES-GCM" },
        true,
        ["encrypt", "decrypt"]
    );

    let iv = new Uint8Array(ivdata.split(','))

    let algorithm = {
        name: "AES-GCM",
        iv: iv
    }

    let data = await encblob.arrayBuffer();

    let decryptedData = await crypto.subtle.decrypt(algorithm, key, data);

    return new Blob([decryptedData])


}
Fawaz Ahmed
  • 1,082
  • 2
  • 14
  • 18