/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* AES counter-mode (CTR) implementation in JavaScript (c) Chris Veness 2005-2016 */ /* MIT Licence */ /* www.movable-type.co.uk/scripts/aes.html */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* eslint no-redeclare: 0 *//* global WorkerGlobalScope */ 'use strict'; if (typeof module!='undefined' && module.exports) var Aes = require('./aes.js'); // ≡ import Aes from 'aes.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 for encryption. * @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); // '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)) throw new Error('Key size is not 128 / 192 / 256'); 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] = i<password.length ? password.charCodeAt(i) : 0; } 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 ciphertext = ;
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]); } ciphertext += cipherChar.join();
// if within web worker, announce progress every 1000 blocks (roughly every 50ms) if (typeof WorkerGlobalScope != 'undefined' && self instanceof WorkerGlobalScope) { if (b%1000 == 0) self.postMessage({ progress: b/blockCount }); } }
ciphertext = (ctrTxt+ciphertext);//.base64Encode();
return ciphertext;
};
/**
* Decrypt a text encrypted by AES in counter mode of operation * * @param {string} ciphertext - Cipher text to be decrypted. * @param {string} password - Password to use to generate a key for decryption. * @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256. * @returns {string} Decrypted text * * @example * var decr = Aes.Ctr.decrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // '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)) throw new Error('Key size is not 128 / 192 / 256'); 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] = i<password.length ? password.charCodeAt(i) : 0; } 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 plaintext = ;
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 plaintext with ciphered counter byte-by-byte -- plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i); plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]); } plaintext += plaintxtByte.join();
// if within web worker, announce progress every 1000 blocks (roughly every 50ms) if (typeof WorkerGlobalScope != 'undefined' && self instanceof WorkerGlobalScope) { if (b%1000 == 0) self.postMessage({ progress: b/nBlocks }); } }
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 * - note utf8Encode is an identity function with 7-bit ascii strings, but not with 8-bit strings; * - utf8Encode('x') = 'x', but utf8Encode('ça') = 'ça', and utf8Encode('ça') = 'ça'*/
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: btoa & Buffer/binary work on single-byte Unicode (C0/C1), so ok for utf8 strings, not for general Unicode... * - 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, 'binary').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('binary'); // Node.js throw new Error('No Base64 Decode'); };
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (typeof module != 'undefined' && module.exports) module.exports = Aes.Ctr; // ≡ export default Aes.Ctr