Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gleissonmattos/f5484607b73108df18f454d8fc36cc13 to your computer and use it in GitHub Desktop.
Save gleissonmattos/f5484607b73108df18f454d8fc36cc13 to your computer and use it in GitHub Desktop.
Javascript / Node.js end-to-end encryption
- 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!
/*
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));
}
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