-
-
Save gleissonmattos/f5484607b73108df18f454d8fc36cc13 to your computer and use it in GitHub Desktop.
Javascript / Node.js end-to-end encryption
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- compiled from multiple sources and much trial and error, please feel free to use and pass on | |
-- please note this is not exhaustive, it's meant only as a guide | |
- please see | |
https://www.industrialcuriosity.com/2016/10/without-or-without-certificates-an-idiots-guide-to-end-to-end-web-encryption/ | |
for a full explanation. please feel free to comment, question and criticize there! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
The following packages are required: | |
aes-js: https://rawgit.com/ricmoo/aes-js/master/index.js | |
(documentation: https://github.com/ricmoo/aes-js) | |
md5: https://github.com/blueimp/JavaScript-MD5 | |
jsencrypt: https://github.com/travist/jsencrypt | |
*/ | |
// SECURITY_LEVEL is the encryption key size in bits | |
var SECURITY_LEVEL = 2048; | |
// internet explorer can't handle 2048 bit key generation in a reasonable amount of time, so we use 1024 bit. | |
// this will have minimal impact as the credentials are secured using an externally transmitted verification | |
// code and cracking the client->server comms won't (usually) compromise server->client comms | |
// if client->server comms being compromised is a serious problem, then simply force the user to wait | |
if ((window.navigator.userAgent.indexOf('MSIE') > 0) || | |
(window.navigator.userAgent.indexOf('Trident/7') > 0) || | |
(window.navigator.userAgent.indexOf('Edge/') > 0)) { | |
SECURITY_LEVEL = 1024; | |
} | |
// RSA keys used to secure the session | |
var sessionKeys = { | |
client: {}, | |
server: {} | |
}; | |
function generateRandomString(length) { | |
var text = ""; | |
var charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |
for (var i = 0; i < length; i++) | |
text += charset.charAt(Math.floor(Math.random() * charset.length)); | |
return text; | |
} | |
// generate the client's keys for the session | |
function generateSessionKeys { | |
console.log('generating ' + SECURITY_LEVEL + '-bit key pair...'); | |
var crypt = new JSEncrypt({ default_key_size: SECURITY_LEVEL }); | |
var dt = new Date(); | |
var time = -(dt.getTime()); | |
crypt.getKey(); | |
dt = new Date(); | |
time += (dt.getTime()); | |
console.log('Keys Generated in ' + time + ' ms'); | |
sessionKeys.client = { | |
'private': crypt.getPrivateKey(), | |
'public': crypt.getPublicKey() | |
}; | |
} | |
// configure the encrypter and decrypter objects | |
// to be called after key exchange | |
function loadEncryptionObjects(serverPublicKey) { | |
sessionKeys.server.public = publicKey; | |
// store sessionKeys in html storage | |
if (typeof (Storage) !== "undefined") { | |
sessionStorage.RSAKeys = JSON.stringify(rsa.keys); | |
console.log('session key stored: ' + sessionStorage.RSAKeys); | |
} | |
// server's public key is used to encrypt AES secrets | |
var encrypter = new JSEncrypt(); | |
encrypter.setPublicKey(sessionKeys.server.public); | |
// client's private key is used to decrypt AES secrets | |
var decrypter = new JSEncrypt(); | |
decrypters.setPrivateKey(sessionKeys.client.private); | |
} | |
// load existing session keys from storage or generate new keys | |
function loadSessionKeys() { | |
// ensure html5 storage available | |
if (typeof (Storage) !== "undefined") { | |
if (sessionStorage.RSAKeys) { | |
sessionKeys = JSON.parse(sessionStorage.RSAKeys); | |
console.log('client keys loaded from session storage'); | |
} else { | |
generateSessionKeys(); | |
sessionStorage.RSAKeys = JSON.stringify(sessionKeys); | |
console.log('session keys saved to storage'); | |
} | |
} else { | |
console.log('Sorry! No Web Storage support..'); | |
// it's possible to continue with new keys generated per page, | |
// but then you'll have to repeat the key exchange with a new code | |
} | |
} | |
// https://github.com/ricmoo/aes-js | |
var aes = { | |
// text should be JSON encoded | |
encrypt: function (secret, text) { | |
// hash secret to 256 bit (32 byte) key using md5 | |
var secretHash = md5(secret); | |
var key = aesjs.util.convertStringToBytes(secretHash); | |
var textBytes = aesjs.util.convertStringToBytes(text); | |
var aesCtr = new aesjs.ModeOfOperation.ctr(key); | |
var encryptedBytes = aesCtr.encrypt(textBytes); | |
return encryptedBytes; | |
}, | |
decrypt: function (secret, encryptedBytes) { | |
// convert node.js buffer object to byte array | |
if (encryptedBytes.type && (encryptedBytes.type == "Buffer")) { | |
encryptedBytes = encryptedBytes.data | |
} | |
// hash secret to 256 bit (32 byte) key | |
var secretHash = md5(secret); | |
var key = aesjs.util.convertStringToBytes(secretHash); | |
var aesCtr = new aesjs.ModeOfOperation.ctr(key); | |
var decryptedBytes = aesCtr.decrypt(encryptedBytes); | |
return aesjs.util.convertBytesToString(decryptedBytes); | |
}, | |
generateKey: function () { | |
return generateRandomString(32); | |
} | |
}; | |
function packMessageData(data) { | |
var packedData = {}; | |
// generate aes key | |
var aesKey = aes.generateKey(); | |
try { | |
// add encrypted aes key to output | |
packedData.key = encrypter.encrypt(aesKey); | |
// add encrypted data to output | |
packedData.encrypted = aes.encrypt(aesKey, JSON.stringify(data)); | |
return packedData; | |
} catch (dataEncryptionException) { | |
console.log('failed to pack message: ' + dataEncryptionException.message); | |
return {}; | |
} | |
} | |
function unpackMessageData(data) { | |
var secret = decrypter.decrypt(data.key); | |
var message = JSON.parse(aes.decrypt(secret, data.encrypted)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var crypto = require('crypto'); | |
// npm install aes-js node-rsa randomstring | |
var aesjs = require('aes-js'); | |
var NodeRSA = require('node-rsa'); | |
var randomstring = require("randomstring"); | |
var SECURITY_LEVEL = 2048; | |
// https://github.com/ricmoo/aes-js | |
var aes = { | |
encrypt: function (secret, text) { | |
// hash secret to 256 bit (32 byte) key | |
var secretHash = crypto.createHash('md5').update(secret).digest("hex"); | |
var key = aesjs.util.convertStringToBytes(secretHash); | |
var textBytes = aesjs.util.convertStringToBytes(text); | |
var aesCtr = new aesjs.ModeOfOperation.ctr(key); | |
var encryptedBytes = aesCtr.encrypt(textBytes); | |
return encryptedBytes; | |
}, | |
decrypt: function (secret, encryptedBytes) { | |
// hash secret to 256 bit (32 byte) key | |
var secretHash = crypto.createHash('md5').update(secret).digest("hex"); | |
var key = aesjs.util.convertStringToBytes(secretHash); | |
var aesCtr = new aesjs.ModeOfOperation.ctr(key); | |
var decryptedBytes = aesCtr.decrypt(encryptedBytes); | |
return aesjs.util.convertBytesToString(decryptedBytes); | |
}, | |
generateKey: function () { | |
return crypto.createHash('md5').update(randomstring.generate()).digest("hex"); | |
} | |
}; | |
// generate shared secret for session authentication | |
var generateSessionSecret = function () { | |
// number between 4 and 8 | |
var secretLength = Math.floor((Math.random() * 5) + 4); | |
// secret should not include ambiguous characters like O/0, 1/l | |
var secret = randomstring.generate({ | |
length: secretLength, | |
charset: '23456789abcdefghijkmnpqrstuvwxyz' | |
}); | |
return secret; | |
}; | |
var rsa = { | |
// both parameters must be strings, publicKey PEM formatted | |
encrypt: function (publicKey, message) { | |
var buffer = new Buffer(message); | |
// padding type must be compatible with client-side packages | |
encrypted = crypto.publicEncrypt( | |
{ | |
key: publicKey, | |
padding: crypto.constants.RSA_PKCS1_PADDING | |
}, | |
buffer | |
); | |
return encrypted.toString('base64'); | |
}, | |
// both parameters must be strings, publicKey PEM formatted | |
decrypt: function (privateKey, message) { | |
var buffer = new Buffer(message, 'base64'); | |
// padding type must be compatible with client-side packages | |
var decrypted = crypto.privateDecrypt( | |
{ | |
key: privateKey, | |
padding: crypto.constants.RSA_PKCS1_PADDING | |
}, | |
buffer | |
); | |
return decrypted.toString('utf8'); | |
}, | |
// generate PEM formatted public / private key pair | |
generateKeys: function () { | |
var key = new NodeRSA({ b: SECURITY_LEVEL }); | |
// formatting must be compatible with client-side packages | |
return { | |
'private': key.exportKey('pkcs1-private-pem'), | |
'public': key.exportKey('pkcs8-public-pem') | |
}; | |
} | |
}; | |
function pack(data) { | |
var packedData = {}; | |
// generate aes key | |
var aesKey = aes.generateKey(); | |
// add encrypted aes key to output | |
packedData.key = rsa.encrypt(clientPublicKey, aesKey); | |
// add encrypted data to output | |
packedData.encrypted = aes.encrypt(aesKey, JSON.stringify(data)); | |
return packedData; | |
} | |
function unpack(data) { | |
var aesKey = rsa.decrypt(serverPrivateKey, data.key); | |
return aes.decrypt(aesKey, data.encrypted); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment