UNPKG

httpsession

Version:

http middlewares session

217 lines (176 loc) 9.87 kB
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2014 / MIT Licence */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* jshint node:true *//* global define, escape, unescape, btoa, atob */ 'use strict'; if (typeof module!='undefined' && module.exports) var Aes = require('./aes'); // CommonJS (Node.js) /** * Aes.Ctr: Counter-mode (CTR) wrapper for AES. * * This encrypts a Unicode string to produces a base64 ciphertext using 128/192/256-bit AES, * and the converse to decrypt an encrypted ciphertext. * * See http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf * * @augments Aes */ Aes.Ctr = {}; /** * Encrypt a text using AES encryption in Counter mode of operation. * * Unicode multi-byte character safe * * @param {string} plaintext - Source text to be encrypted. * @param {string} password - The password to use to generate a key. * @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256. * @returns {string} Encrypted text. * * @example * var encr = Aes.Ctr.encrypt('big secret', 'pāşšŵōřđ', 256); // encr: 'lwGl66VVwVObKIr6of8HVqJr' */ Aes.Ctr.encrypt = function(plaintext, password, nBits) { var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys plaintext = String(plaintext).utf8Encode(); password = String(password).utf8Encode(); // use AES itself to encrypt password to get cipher key (using plain password as source for key // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use) var nBytes = nBits/8; // no bytes in key (16/24/32) var pwBytes = new Array(nBytes); for (var i=0; i<nBytes; i++) { // use 1st 16/24/32 chars of password for key pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i); } var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes)); // gives us 16-byte key key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec, // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106 var counterBlock = new Array(blockSize); var nonce = (new Date()).getTime(); // timestamp: milliseconds since 1-Jan-1970 var nonceMs = nonce%1000; var nonceSec = Math.floor(nonce/1000); var nonceRnd = Math.floor(Math.random()*0xffff); // for debugging: nonce = nonceMs = nonceSec = nonceRnd = 0; for (var i=0; i<2; i++) counterBlock[i] = (nonceMs >>> i*8) & 0xff; for (var i=0; i<2; i++) counterBlock[i+2] = (nonceRnd >>> i*8) & 0xff; for (var i=0; i<4; i++) counterBlock[i+4] = (nonceSec >>> i*8) & 0xff; // and convert it to a string to go on the front of the ciphertext var ctrTxt = ''; for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]); // generate key schedule - an expansion of the key into distinct Key Rounds for each round var keySchedule = Aes.keyExpansion(key); var blockCount = Math.ceil(plaintext.length/blockSize); var ciphertxt = new Array(blockCount); // ciphertext as array of strings for (var b=0; b<blockCount; b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff; for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8); var cipherCntr = Aes.cipher(counterBlock, keySchedule); // -- encrypt counter block -- // block size is reduced on final block var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1; var cipherChar = new Array(blockLength); for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter char-by-char -- cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i); cipherChar[i] = String.fromCharCode(cipherChar[i]); } ciphertxt[b] = cipherChar.join(''); } // use Array.join() for better performance than repeated string appends var ciphertext = ctrTxt + ciphertxt.join(''); ciphertext = ciphertext.base64Encode(); return ciphertext; }; /** * Decrypt a text encrypted by AES in counter mode of operation * * @param {string} ciphertext - Source text to be encrypted. * @param {string} password - Password to use to generate a key. * @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256. * @returns {string} Decrypted text * * @example * var decr = Aes.Ctr.encrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // decr: 'big secret' */ Aes.Ctr.decrypt = function(ciphertext, password, nBits) { var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys ciphertext = String(ciphertext).base64Decode(); password = String(password).utf8Encode(); // use AES to encrypt password (mirroring encrypt routine) var nBytes = nBits/8; // no bytes in key var pwBytes = new Array(nBytes); for (var i=0; i<nBytes; i++) { pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i); } var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes)); key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long // recover nonce from 1st 8 bytes of ciphertext var counterBlock = new Array(8); var ctrTxt = ciphertext.slice(0, 8); for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i); // generate key schedule var keySchedule = Aes.keyExpansion(key); // separate ciphertext into blocks (skipping past initial 8 bytes) var nBlocks = Math.ceil((ciphertext.length-8) / blockSize); var ct = new Array(nBlocks); for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize); ciphertext = ct; // ciphertext is now array of block-length strings // plaintext will get generated block-by-block into array of block-length strings var plaintxt = new Array(ciphertext.length); for (var b=0; b<nBlocks; b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff; for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff; var cipherCntr = Aes.cipher(counterBlock, keySchedule); // encrypt counter block var plaintxtByte = new Array(ciphertext[b].length); for (var i=0; i<ciphertext[b].length; i++) { // -- xor plaintxt with ciphered counter byte-by-byte -- plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i); plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]); } plaintxt[b] = plaintxtByte.join(''); } // join array of blocks into single plaintext string var plaintext = plaintxt.join(''); plaintext = plaintext.utf8Decode(); // decode from UTF8 back to Unicode multi-byte chars return plaintext; }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** Extend String object with method to encode multi-byte string to utf8 * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */ if (typeof String.prototype.utf8Encode == 'undefined') { String.prototype.utf8Encode = function() { return unescape( encodeURIComponent( this ) ); }; } /** Extend String object with method to decode utf8 string to multi-byte */ if (typeof String.prototype.utf8Decode == 'undefined') { String.prototype.utf8Decode = function() { try { return decodeURIComponent( escape( this ) ); } catch (e) { return this; // invalid UTF-8? return as-is } }; } /** Extend String object with method to encode base64 * - developer.mozilla.org/en-US/docs/Web/API/window.btoa, nodejs.org/api/buffer.html * note: if btoa()/atob() are not available (eg IE9-), try github.com/davidchambers/Base64.js */ if (typeof String.prototype.base64Encode == 'undefined') { String.prototype.base64Encode = function() { if (typeof btoa != 'undefined') return btoa(this); // browser if (typeof Buffer != 'undefined') return new Buffer(this, 'utf8').toString('base64'); // Node.js throw new Error('No Base64 Encode'); }; } /** Extend String object with method to decode base64 */ if (typeof String.prototype.base64Decode == 'undefined') { String.prototype.base64Decode = function() { if (typeof atob != 'undefined') return atob(this); // browser if (typeof Buffer != 'undefined') return new Buffer(this, 'base64').toString('utf8'); // Node.js throw new Error('No Base64 Decode'); }; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ if (typeof module != 'undefined' && module.exports) module.exports = Aes.Ctr; // CommonJs export if (typeof define == 'function' && define.amd) define(['Aes'], function() { return Aes.Ctr; }); // AMD