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,211