end-to-end
encryption
Requirements
- Cipher / Decipher large files
 - Server must not be able to read files
 - Multiple access point
 - Users can share files
 - Groups concepts
 
Javascript Encryption
Web Cryptography API
- Encryption
	
- RSA
 - AES
 
 
- Sign
	
- RSA
 - AES
 - ECDSA
 - HMAC
 
 
Generate key
window.crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",
        modulusLength: 2048, //can be 1024, 2048, or 4096
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    true, //whether the key is extractable (i.e. can be used in exportKey)
    ["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)
.then(function(key){
    window.demoKey = key;
});Object {
  publicKey: CryptoKey {
    algorithm: Object,
    extractable: true,
    type: "private",
    usages: ["decrypt"]
  },
  privateKey: CryptoKey {
    algorithm: Object,
    extractable: true,
    type: "public",
    usages: ["encrypt"]
  }
}Export key
window.crypto.subtle.exportKey(
    "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
    window.demoKey.publicKey //can be a publicKey or privateKey, as long as extractable was true
)
.then(function(keyData){
    window.demoPublicJwk = keyData;
});Object {
  alg: "RSA-OAEP-256"
  e: "AQAB"
  ext: true
  key_ops: ["encrypt"],
  kty: "RSA"
  n : "8khgF5fQz7OR209ASxTUyz7Oijayy-1kPRoy6MXF2L8cxVsqulk1jiJjyeXJcJoWhMfNVxHYblOQRFzuvhIuusrCJVI9RE5dun1EPydPTehEA8nmtbAkwBDQdU_Vwngyohk6FLrAMsD8fWbZpuacfxgdczLJimWbenZl5CIE2zHHsBS3-OcS7y7fDNdK1Am00QaUo2MeettlTMnlgO0LqzBSfymENMWG5lvsKvID8I7pjZaHZ38dwUXoCd5hued-ihn0cTOdujOecR6uzMu8ZPFumJu0HKqaM3_uJoUNfMw3c3RV2PaPVuPdHMNAib0S8clBXPbRe1ApqgytpCqSgQ"
}Import key
window.crypto.subtle.importKey(
    "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
    window.demoPublicJwk,
    {   //these are the algorithm options
        name: "RSA-OAEP",
        hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    false, //whether the key is extractable (i.e. can be used in exportKey)
    ["encrypt"] //"verify" for public key import, "sign" for private key imports
)
.then(function(publicKey){
    console.log(publicKey);
});CryptoKey {
  algorithm: {
    hash: {
      name: "SHA-256"
    },
    modulusLength: 2048,
    name: "RSA-OAEP",
    publicExponent: Uint8Array[1, 0, 1]
  }
  extractable: false,
  type: "public",
  usages: ["encrypt"]
}
Converter
var converter = {
    arrayBufferToBase64: function (buffer) {
        return window.btoa(this.arrayBufferToString(buffer));
    },
    base64ToArrayBuffer: function (base64) {
        return this.stringToArrayBuffer(window.atob(base64));
    },
    stringToArrayBuffer: function (str) {
        var buf = new ArrayBuffer(str.length);
        var bufView = new Uint8Array(buf);
        for (var i = 0, strLen = str.length; i < strLen; i++) {
            bufView[i] = str.charCodeAt(i);
        }
        return buf;
    },
    arrayBufferToString(buf) {
        var bufView = new Uint8Array(buf);
        var unis = "";
        for (var i = 0; i < bufView.length; i++) {
            unis = unis + String.fromCharCode(bufView[i]);
        }
        return unis;
    },
};Cipher
window.crypto.subtle.encrypt(
    {
        name: "RSA-OAEP",
        //label: Uint8Array([...]) //optional
    },
    window.demoKey.publicKey, //from generateKey or importKey above
    converter.stringToArrayBuffer("demoData") //ArrayBuffer of data you want to encrypt
)
.then(function(ciphered){
    window.demoCiphered = converter.arrayBufferToString(ciphered);
});
"YFòð©³ê=´4©À?¡XúÅÉG=$?%7znE¨Ãáfi*öe6rúSê GIÿ³ÄÊSózM§SA4\+°E[C×ͨ·ѩFsyç,ÊKV
÷3hþ÷Ìa°ª;-@YöaTèÑGÐyyÕYÿByÊ ù(,+ 
ȱõ#£Q¾¾Vø-Ç&íØ²}\7°+ëÙ<¢+.P±/%F¸o:e
>R줲Ópàcg÷3ÝÀ4lvÅx4Wíg=~&=-±uò5é
ðU°Wø/Ð×U?*î²7Óäû;"Decipher
window.crypto.subtle.decrypt(
    {
        name: "RSA-OAEP",
        //label: Uint8Array([...]) //optional
    },
    window.demoKey.privateKey, //from generateKey or importKey above
    converter.stringToArrayBuffer(window.demoCiphered) //ArrayBuffer of the data
)
.then(function(deciphered){
    window.demoDeciphered = converter.arrayBufferToString(deciphered);
});
"demoData"Upload
File API
function FileReader(file) {
  this.file = file;
  this.pointer = 0;
}
FileIO.prototype.eof = function () {
  return this.pointer >= this.file.size;
};
FileIO.prototype.read = function (length) {
  return new Promise(function (resolve, reject) {
    var slice = this.file.slice(this.pointer, this.pointer + length);
    this.pointer += length;
    var reader = new FileReader();
    reader.onload = function (e) {
      resolve(new Uint8Array(e.target.result));
    };
    reader.readAsArrayBuffer(slice);
  }.bind(this));
};
<input type="file" onchange="upload(this.files[0])"/>
<script>
function upload(file) {
  http.post('/api/files', {}).then(function (fileItem) {
    return uploadNextBlock(window.demoFileKey, fileItem, new FileReader(file))
  })
}
</script>Upload block
function uploadNextBlock(fileKey, fileItem, fileReader) {
  return fileReader.read(
      1024 * 1024
  ).then(function (block) {
    var iv = window.crypto.getRandomValues(new Uint8Array(16));
    return window.crypto.subtle.encrypt({
        name: "AES-CBC",
        iv: iv,
      },
      fileKey,
      block
    ).then(function (ciphered) {
      var tmp = new Uint8Array(16 + ciphered.byteLength);
      tmp.set(iv, 0);
      tmp.set(new Uint8Array(ciphered), 16);
      return tmp;
    });
  }).then(function (ciphered) {
    return http.post('/api/files/' + fileItem.id + '/blocks', {
      'content': converter.arrayBufferToBase64(ciphered)
    });
  }).then(function () {
    if (!fileReader.eof()) {
      return uploadNextBlock(fileKey, fileItem, fileReader);
    } else {
      return fileItem;
    }
  });
}
Download
FileSystem API
function FileTempHtml5(fileName) {
  this.entryPromise = new Promise(function (resolve, reject) {
    window.requestFileSystem(
        window.TEMPORARY,
        1024 * 1024,
        function (fs) {
          fs.root.getFile(
            fileName,
            {create: true},
            function (fileEntry) {resolve(fileEntry);},
            function (err) {reject(err)}
          );
        }
    );
  });
  this.writerPromise = new Promise(function (resolve, reject) {
    this.entryPromise.then(function (fileEntry) {
      fileEntry.createWriter(function (fileWriter) {
        fileWriter.truncate(0);
        resolve(fileWriter);
      }, reject);
    }, function (err) {
      reject(err);
    });
  }.bind(this));
}FileSystem API
FileTempHtml5.prototype.write = function (content) {
    return this.writerPromise.then(function (fileWriter) {
        return new Promise(function (resolve, reject) {
            fileWriter.onwriteend = resolve;
            fileWriter.onerror = reject;
            fileWriter.write(new Blob([content]));
        });
    });
};
FileTempHtml5.prototype.download = function () {
    this.entryPromise.then(function (fileEntry) {
        var elem = document.createElement('a');
        elem.download = fileEntry.name;
        elem.href = fileEntry.toURL();
        document.body.appendChild(elem);
        elem.click();
        document.body.removeChild(elem);
        setTimeout(function () {fileEntry.remove(function(){});}, 1000);
    });
};
Download block
function downloadNextBlock(fileKey, fileWriter, index, fileItem) {
  http.request(
    'get', '/api/files/' + fileItem.id + '/blocks/' + index
  ).then(function (ciphered) {
    return window.crypto.subtle.decrypt(
      {
        name: "AES-CFB-8",
        iv: ciphered.slice(0, 16)
      },
      fileKey
      ciphered.slice(16)
    )
  }).then(function (deciphered) {
    return fileWriter.write(new Uint8Array(deciphered));
  }).then(function () {
    if (index + 1 < fileItem.blocksLength) {
      downloadNextBlock(fileWriter, index + 1);
    } else {
      fileWriter.download();
    }
  });
}multiplatform
Solution
- Generate the personal key
 - Cipher the key with a master password
 - Store the key in the server
 
Generate MasterKey
crypto.subtle.digest(
  {name: "SHA-256"}, converter.stringToArrayBuffer(prompt("Master Password?"))
).then(function (result) {
  return window.crypto.subtle.importKey(
    "raw",
    result,
    {name: "AES-CBC"},
    true,
    ["encrypt", "decrypt"]
  );
}).then(function (masterKey) {
    window.demoMasterKey = masterKey;
});Generate a Personal Key
window.crypto.subtle.generateKey({
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
    hash: {name: "SHA-256"},
  },
  true,
  ["encrypt", "decrypt"]
).then(function (key) {
  window.demoPersonalKey = key;
});Cipher Personal Key
Promise.all([
  // export publicKey
  window.crypto.subtle.exportKey("spki", window.demoPersonalKey.publicKey),
  // export privateKey
  window.crypto.subtle.exportKey("pkcs8", window.demoPersonalKey.privateKey)
]).then(function (responses) {
  // Cipher private key
  var iv = window.crypto.getRandomValues(new Uint8Array(16));
  return window.crypto.subtle.encrypt({name: "AES-CBC", iv: iv},
    window.demoMasterKey,
    responses[1]
  ).then(function (encrypted) {
    var tmp = new Uint8Array(16 + encrypted.byteLength);
    tmp.set(iv, 0);
    tmp.set(new Uint8Array(encrypted), 16);
  
    return [responses[0], tmp];
  });
}).then(function (responses) {
  var payload = {
    algorithm: window.demoPersonalKey.publicKey.algorithm.name,
    publicKey: converter.arrayBufferToBase64(responses[0]),
    cipheredPrivateKey: converter.arrayBufferToBase64(responses[1])
  };
  return http.post(
    '/api/keys', payload
  });
});Decipher Personal Key
http.get('/api/keys/' + id).then(function (keyItem) {
  window.demoPersonalKey = {};
  window.crypto.subtle.importKey(
    "spki",
    converter.base64ToArrayBuffer(keyItem.publicKey),
    {name: "RSA-OAEP", hash: {name: "SHA-256"}},
    true,
    ["encrypt"]
  ).then(function (key) {
    window.demoPersonalKey.publicKey = key;
  });
  const cipheredKey = converter.base64ToArrayBuffer(cryptoKey.cipheredPrivateKey)
  return window.crypto.subtle.decrypt(
    {name: "AES-CBC", iv: cipheredKey.slice(0, 16)},
    window.demoMasterKey,
    cipheredKey.slice(16)
  ).then(function (decipheredPrivateKey) {
    return window.crypto.subtle.importKey(
      "pkcs8",
      decipheredPrivateKey,
      {name: "RSA-OAEP", hash: {name: "SHA-256"}},
      true,
      ["decrypt"]
    );
  });
}).then(function (key) {
  window.demoPersonalKey.privateKey = key;
});Sharing
Solution
- Use one key per file
 - Cipher file's key with user's personal public key
 - store the ciphered key in the server
 
Sharing
function shareFile(fileKey, fileItem, targetKeyId) {
  http.get('/api/keys/' + targetKeyId).then(function (keyItem) {
    return Promise.all([
      window.crypto.subtle.importKey(
        "spki",
        converter.base64ToArrayBuffer(keyItem.publicKey),
        {
          name: "RSA-OAEP",
          hash: {name: "SHA-256"},
        },
        true,
        ["encrypt"]
      ),
      window.crypto.subtle.exportKey("jwk", fileKey)
    ]);
  }).then(function (results) {
    var fileKey = converter.stringToArrayBuffer(JSON.stringify(results[1]));
    var targetKey = results[0];
    return rsaCrypter.encrypt(targetKey, fileKey)
  }).then(function (cipĥeredKey) {
    return http.post('/api/files/' + fileItem.id + '/keys', {
      encoder: {
        id: targetKeyId
      },
      encryptedKey: converter.arrayBufferToBase64(cipĥeredKey)
    });
  });
}Group sharing
Cipher chain
- Generate the group's Key
 - Cipher the group's Key with the member's public key
 - Store the ciphered key in the server
 
Use the group's key
- Retrieves the ciphered group's key
 - Retrieves file's Key
 - Decipher group's key with the personal Key
 - Decipher file's Key with the group's Key
 - Decipher document with the file's Key
 
Add a new member
- Retrieves the ciphered group's key
 - Decipher group's key our personal Key
 - Retrieves member's personalKey
 - Cipher group's key with the member public key
 - Store the ciphered key in the server
 
Demo
Questions
End to end encryption
By Jérémy DERUSSÉ
End to end encryption
- 1,341