Source: block_cipher.js

/*
 *	Symmetric block cipher support classes
 *	Copyright (c) 2013, Jeff Lyon. (http://rubbingalcoholic.com)
 * 
 *	Licensed under The MIT License. (http://www.opensource.org/licenses/mit-license.php)
 *	Redistributions of files must retain the above copyright notice.
 */
 /**
 *	@abstract
 *	@class
 *	@classdesc		Abstract class which provides base functionality used by all symmetric block cipher subclasses
 *	@desc			NOTE: you can't instantiate this class directly. Instead, create instances of a subclass, such as {@link AES} or {@link Twofish}.
 *	@requires		convert
 */
var BlockCipher = new Class(
/** @lends BlockCipher.prototype */
{
	/**
	 *	The block cipher mode of operation (see {@link https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation})
	 *	@type {BlockCipherMode}
	 *	@private
	 */
	block_mode: 	null,

	/**
	 *	The block byte padding mode (see {@link http://en.wikipedia.org/wiki/Padding_%28cryptography%29})
	 *	@type {PaddingMode}
	 *	@private
	 */
	pad_mode: 		null,

	/**
	 *	Initial vector for non-ECB block cipher modes
	 *	@private
	 */
	iv: 			'',

	/**
	 *	Passphrase, stored temporarily before key derivation
	 *	@private
	 */
	passphrase: 	'',

	/**
	 *	Salt used for passphrase based key derivation
	 *	@private
	 */
	salt: 			'',

	/**
	 *	Toggles OpenSSL mode. 
	 *	@private
	 */
	openssl_mode: 	false,

	/**
	 *	Turn this on to enable the {@link BlockCipher#debug_write} method to log debug output to the console.
	 *
	 *	@type {Boolean}
	 *  @default false
	 */
	debug_mode: true,

	/**
	 *	Invoked during subclass initialization. All properties needed to initialize the class must be passed in.
	 *
	 *	@param {Object} data									A list of properties used to initialize the class.
	 *	@param {string} [data.key]								A binary string containing the symmetric key. Required if a passphrase is not specified.
	 *															Must match a key length supported by the subclass.
	 *	@param {BlockCipherMode} [data.block_mode={@link CBC}]	The block cipher mode of operation to use for *cryption
	 *	@param {PaddingMode} [data.pad_mode={@link PKCS7}]		The block byte padding mode
	 *	@param {string} [data.passphrase]						A passphrase to derive a key from. Required if a key is not explicitly specified.
	 *	@param {string} [data.salt]								A binary string containing the cryptographic salt used for key derivation.
	 *	@param {boolean} [data.openssl_mode=false]				Toggles OpenSSL interoperability mode. This prepends salt data to the encryption output, and
	 *															uses the prepended salt data during decryption to derive a key (in combination with a
	 *															passphrase), if needed.
	 *	@return {BlockCipher}
	 */
	initialize: function(data)
	{
		data || (data = {});
		
		for (var attr in data)
			this[attr] = data[attr];

		if (!data.block_mode)
			this.block_mode = CBC;

		if (!data.pad_mode)
			this.pad_mode = PKCS7;

		if (data.passphrase && this.openssl_mode == false)
			this._derive_key_from_passphrase();

		return this;
	},

	/**
	 *	Encrypts a string using the padding and block cipher mode of operation specificed on initialization.
	 *
	 *	@param {String} plaintext	An ASCII string to encrypt. Can be binary or plaintext.
	 *								For plaintext, be sure to use {@link convert.utf8.encode} to encode any UTF characters.
	 *	@return {String}			Decrypted data
	 */
	encrypt: function(plaintext)
	{
		if (this.openssl_mode && this.get_key().length == 0)
			this._derive_key_from_passphrase();

		plaintext		= new this.pad_mode({ cipher: this }).do_pad(plaintext);

		var plaintext 	= convert.to_words(plaintext);
		var operator	= new this.block_mode({ cipher: this });
		var ciphertext	= operator.encrypt_blocks(plaintext);

		ciphertext		= convert.words_to_binstring(ciphertext);

		if (this.openssl_mode && this.get_salt())
			ciphertext	= 'Salted__' + this.get_salt() + ciphertext;

		return ciphertext;
	},

	/**
	 *	Decrypts a string.
	 *
	 *	@param {String} ciphertext	An ASCII string to decrypt.
	 *	@return {String}			Encrypted data
	 */
	decrypt: function(ciphertext)
	{
		if (this.openssl_mode && ciphertext.substr(0, 8) == 'Salted__')
		{
			if (this.get_key().length == 0)
			{
				this.salt	= ciphertext.substr(8, 8);
				this._derive_key_from_passphrase();
			}
			ciphertext		= ciphertext.substr(16);
		}

		var ciphertext 		= convert.to_words(ciphertext);
		var operator		= new this.block_mode({ cipher: this });
		var plaintext		= convert.words_to_binstring(operator.decrypt_blocks(ciphertext));

		plaintext			= new this.pad_mode({ cipher: this }).undo_pad(plaintext);

		return plaintext;
	},

	/**
	 *	Gets the salt used for any passphrase-based key derivation.
	 *	@return {String}
	 */
	get_salt: function()
	{
		return this.salt;
	},

	/**
	 *	Writes debug information to the console if {@link BlockCipher#debug_mode} is turned on.
	 *	@param {...mixed} arguments The variables to write to console
	 */
	debug_write: function()
	{
		if (this.debug_mode) console.log.apply(console, arguments);
	},

	/**
	 *	Derives a key from a passphrase.
	 *	@private
	 */
	_derive_key_from_passphrase: function()
	{
		var key_bytes		= (this.get_key_length() / 8);
		var block_bytes		= (this.get_block_size() / 8);

		if (this.openssl_mode)
		{
			if (!this.get_salt())
				this.salt	= this._random_salt();

			var key			= new EVPKDF({key_size: (key_bytes+block_bytes)}).compute(this.passphrase, this.get_salt().substr(0, 8));
		}
		else
			var key			= new PBKDF2({key_size: (key_bytes+block_bytes)}).compute(this.passphrase, this.get_salt());
		
		this.iv				= key.substr(key_bytes);
		this.passphrase		= '';

		this.set_key(key.substr(0, key_bytes));
	},

	/**
	 *	Generates a random salt
	 *	@private
	 */
	_random_salt: function()
	{
		var rb = function () { return String.fromCharCode(Math.floor(Math.random() * 256)); }
		return rb() + rb() + rb() + rb() + rb() + rb() + rb() + rb();
	}
});

/**
 *	@abstract
 *	@class
 *	@classdesc		Abstract class which provides base functionality for block cipher mode operator subclasses
 *	@desc			NOTE: you can't instantiate this class directly. Instead, create instances of a subclass, such as {@link CBC} or {@link ECB}.
 *	@requires		convert
 */
var BlockCipherMode = new Class(
/** @lends BlockCipherMode.prototype */
{
	/**
     *	Stores a reference to the BlockCipher subclass instance used for *cryption
     *	@type {BlockCipher}
     *	@private
     */
	cipher: null,

	/**
	 *	Invoked during subclass initialization. All properties needed to initialize the class must be passed in.
	 *
	 *	@param {Object} data				A list of properties used to initialize the class.
	 *	@param {BlockCipher} data.cipher	A reference to a BlockCipher subclass instance. Used to actually *crypt any given block.
	 *	@param {string} [data.iv]			ASCII string containing Initial Vector (IV). Not required for {@link ECB}
	 *	@return {BlockCipherMode}
	 */
	initialize: function(data)
	{
		for (var attr in data)
			this[attr] = data[attr];

		return this;
	},

	/**
	 *	Invoked during subclass initialize when an Initial Vector is required.
	 *	Converts any supplied Initial Vector to an array of 32-bit words and does a length check.
	 *	@private
	 *	@throws Throws an error if the Initial Vector length doesn't match the block length.
	 */
	init_iv: function()
	{
		var iv = convert.to_words(this.cipher.iv);

		if (iv.length != this.get_words_per_block())
			throw new Error('Initial vector size must match block size!');

		this.iv = iv;
	},

	/**
	 *	Convenience function to get the number of 32-bit words required for each block
	 *	@return {number}
	 */
	get_words_per_block: function()
	{
		return this.cipher.get_block_size() / 32;
	},

	/**
	 *	XORs two blocks together. (A block is an array of 32-bit words of the correct length)
	 *	@private
	 *	@param {Array} block1	The first block
	 *	@param {Array} block2	The second block
	 *	@return {Array}			The result of XOR'ing the two blocks together
	 */
	xor_block: function(block1, block2)
	{
		for (var i=0; i < block1.length; i++) block1[i] ^= block2[i];		
		return block1;
	}

});

/**
 *	@class
 *	@classdesc				Implements ECB block cipher mode. Used internally during symmetric *cryption.
 *	@extends				BlockCipherMode
 *	@requires				convert
 *
 *	@desc					Creates a new ECB block cipher mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link ECB#initialize}
 */
var ECB = new Class(
/** @lends ECB.prototype */
{
	
	Extends: BlockCipherMode,

	/**
	 *	Called automatically on class instantiation.
	 *	Invokes {@link BlockCipherMode#initialize}.
	 *
	 *	@override
	 *	@param {Object} data See {@link BlockCipherMode#initialize} for a list of supported properties.
	 *	@return {ECB}
	 */
	initialize: function(data)
	{
		this.parent(data);
		return this;
	},

	/**
	 *	Encrypts blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link ECB#get_words_per_block}
	 *	@return {Array}			Encrypted blocks
	 */
	encrypt_blocks: function(words)
	{
		var ciphertext 		= [];
		var words_per_block	= this.get_words_per_block();

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var block = words.slice(i, i+words_per_block);
			ciphertext = ciphertext.concat(this.cipher.block_encrypt(block));
		}

		return ciphertext;
	},

	/**
	 *	Decrypt blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link ECB#get_words_per_block}
	 *	@return {Array}			Decrypted blocks
	 */
	decrypt_blocks: function(words)
	{
		var plaintext 		= [];
		var words_per_block	= this.get_words_per_block();

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var block = words.slice(i, i+words_per_block);
			plaintext = plaintext.concat(this.cipher.block_decrypt(block));
		}

		return plaintext;
	},
	
});

/**
 *	@class
 *	@classdesc				Implements CBC block cipher mode. Used internally during symmetric *cryption.
 *	@extends				BlockCipherMode
 *	@requires				convert
 *
 *	@desc					Creates a new CBC block cipher mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link CBC#initialize}
 */
var CBC = new Class(
/** @lends CBC.prototype */
{
	Extends: BlockCipherMode,
	
	/**
	 *	Array of 32-bit words for the Initial Vector (IV)
	 *	@private
	 */
	iv: [],

	/**
	 *	Called automatically on class instantiation.
	 *	Invokes {@link BlockCipherMode#initialize} before handling class-specific functionality.
	 *
	 *	@override
	 *	@param {Object} data See {@link BlockCipherMode#initialize} for a list of supported properties.
	 *	@return {CBC}
	 */
	initialize: function(data)
	{
		this.parent(data);
		this.init_iv();		
		return this;
	},

	/**
	 *	Encrypts blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link CBC#get_words_per_block}
	 *	@return {Array}			Encrypted blocks
	 */
	encrypt_blocks: function(words)
	{
		var ciphertext 		= [];
		var words_per_block	= this.get_words_per_block();
		var _prev_block		= this.iv;

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var block			= words.slice(i, i+words_per_block);
			var xor_block		= this.xor_block(block, _prev_block);
			var cipher_block	= this.cipher.block_encrypt(xor_block)
			ciphertext 			= ciphertext.concat(cipher_block);
			_prev_block			= cipher_block;
		}

		return ciphertext;
	},

	/**
	 *	Decrypt blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link CBC#get_words_per_block}
	 *	@return {Array}			Decrypted blocks
	 */
	decrypt_blocks: function(words)
	{
		var plaintext 		= [];
		var words_per_block	= this.get_words_per_block();
		var _prev_block		= this.iv;

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var block 			= words.slice(i, i+words_per_block);
			var decrypted 		= this.cipher.block_decrypt(block);
			var xor_decrypted	= this.xor_block(decrypted, _prev_block);
			plaintext 			= plaintext.concat(xor_decrypted);
			_prev_block			= block;
		}

		return plaintext;
	},
	
});

/**
 *	@class
 *	@classdesc				Implements CFB block cipher mode. Used internally during symmetric *cryption.
 *	@extends				BlockCipherMode
 *	@requires				convert
 *
 *	@desc					Creates a new CFB block cipher mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link CFB#initialize}
 */
var CFB = new Class(
/** @lends CFB.prototype */
{
	
	Extends: BlockCipherMode,

	/**
	 *	Array of 32-bit words for the Initial Vector (IV)
	 *	@private
	 */
	iv: [],

	/**
	 *	Called automatically on class instantiation.
	 *	Invokes {@link BlockCipherMode#initialize} before handling class-specific functionality.
	 *
	 *	@override
	 *	@param {Object} data See {@link BlockCipherMode#initialize} for a list of supported properties.
	 *	@return {CFB}
	 */
	initialize: function(data)
	{
		this.parent(data);
		this.init_iv();
		return this;
	},

	/**
	 *	Encrypts blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link CFB#get_words_per_block}
	 *	@return {Array}			Encrypted blocks
	 */
	encrypt_blocks: function(words)
	{
		var ciphertext 		= [];
		var words_per_block	= this.get_words_per_block();
		var _prev_block		= this.iv;

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var cipher_block	= this.cipher.block_encrypt(_prev_block)
			var block			= words.slice(i, i+words_per_block);
			var xor_block		= this.xor_block(block, cipher_block);
			ciphertext 			= ciphertext.concat(xor_block);
			_prev_block			= xor_block;
		}

		return ciphertext;
	},

	/**
	 *	Decrypt blocks.
	 *
	 *	@param {Array} words	An array of 32-bit words whose length must be an integer multiple of {@link CFB#get_words_per_block}
	 *	@return {Array}			Decrypted blocks
	 */
	decrypt_blocks: function(words)
	{
		var plaintext 		= [];
		var words_per_block	= this.get_words_per_block();
		var _prev_block		= this.iv;

		for (var i = 0; i < words.length; i += words_per_block)
		{
			var cipher_block	= this.cipher.block_encrypt(_prev_block)
			var block 			= words.slice(i, i+words_per_block);
			var xor_decrypted	= this.xor_block(cipher_block, block);
			plaintext 			= plaintext.concat(xor_decrypted);
			_prev_block			= block;
		}

		return plaintext;
	},
});

/**
 *	@abstract
 *	@class
 *	@classdesc		Abstract class which provides base functionality for byte padding subclasses
 *	@desc			NOTE: you can't instantiate this class directly. Instead, create instances of a subclass, such as {@link ZeroPadding} or {@link PKCS7}.
 */
var PaddingMode = new Class(
/** @lends PaddingMode.prototype */
{
	/**
     *	Stores a reference to the BlockCipher subclass instance (used to get data about the block size)
     *	@type {BlockCipher}
     *	@private
     */
	cipher: null,

	/**
	 *	Invoked during subclass instantiation. All properties needed to initialize the class must be passed in.
	 *
	 *	@param {Object} data				A list of properties used to initialize the class.
	 *	@param {BlockCipher} data.cipher	A reference to a BlockCipher subclass instance. Used to get information about the block size.
	 *	@return {PaddingMode}
	 */
	initialize: function(data)
	{
		for (var attr in data)
			this[attr] = data[attr];

		return this;
	}
});

/**
 *	@class
 *	@classdesc				Implements Zero Padding functionality. Data is padded to the correct block length multiple with zero bytes.
 *	@extends				PaddingMode
 *
 *	@desc					Creates a new ZeroPadding padding mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link ZeroPadding#initialize}
 */
var ZeroPadding = new Class(
/** @lends ZeroPadding.prototype */
{
	Extends: PaddingMode,

	/**
	 *	Perform the padding on the data.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		Padded data
	 */
	do_pad: function(data)
	{
		if ((data.length * 8) % this.cipher.get_block_size() != 0)
			for (var i=0; data.length % (this.cipher.get_block_size() / 8) != 0; i++)
				data += String.fromCharCode(0);

		return data;
	},

	/**
	 *	There is no safe way to undo zero padding. Simply returns the input string.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		The exact same string that was passed in.
	 */
	undo_pad: function(data)
	{
		return data;
	}
});

/**
 *	@class
 *	@classdesc				Implements ANSI X.923 Padding functionality.
 *							Data is padded with zeros until the last byte, which is then set to the number of padded bytes.
 *							If the data length is already a "clean" multiple of the block length, it is still padded out
 *							to another block, so we can safely undo the padding afterwards.
 *	@extends				PaddingMode
 *
 *	@desc					Creates a new AnsiX923 padding mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link AnsiX923#initialize}
 */
var AnsiX923 = new Class(
/** @lends AnsiX923.prototype */
{
	Extends: PaddingMode,

	/**
	 *	Perform the padding on the data.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		Padded data
	 */
	do_pad: function(data)
	{
		// Pad the block out with zeros
		for (var i=0; i == 0 || data.length % (this.cipher.get_block_size() / 8) != 0; i++)
			data += String.fromCharCode(0);

		// Slice off the last byte and replace with our count
		data = data.substr(0, data.length - 1);
		data += String.fromCharCode(i);

		return data;
	},

	/**
	 *	Undo padding on padded data.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		A binary string with the padding data stripped off the end.
	 */
	undo_pad: function(data)
	{
		var pad_length = data.charCodeAt(data.length-1);
		return data.substr(0, data.length - pad_length);
	}
});

/**
 *	@class
 *	@classdesc				Implements PKCS7 Padding functionality.
 *							The byte value we pad with is the number total number of bytes being padded onto the data.
 *							If the data length is already a "clean" multiple of the block length, it is still padded out
 *							to another block, so we can safely undo the padding afterwards.
 *	@extends				PaddingMode
 *
 *	@desc					Creates a new PKCS7 padding mode instance
 *	@param {Object} data	Initialization options for the class, passed automatically into {@link PKCS7#initialize}
 */
var PKCS7 = new Class(
/** @lends AnsiX923.prototype */
{
	Extends: PaddingMode,

	/**
	 *	Perform the padding on the data.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		Padded data
	 */
	do_pad: function(data)
	{
		var block_bytes	= (this.cipher.get_block_size() / 8);

		var pad_count	= data.length % block_bytes != 0 ? block_bytes - (data.length % block_bytes) : block_bytes;

		for (var i=0; i == 0 || data.length % (this.cipher.get_block_size() / 8) != 0; i++)
			data += String.fromCharCode(pad_count);

		return data;
	},

	/**
	 *	Undo padding on padded data.
	 *
	 *	@param {string} data	A binary data string (ASCII)
	 *	@return {string} 		A binary string with the padding data stripped off the end.
	 */
	undo_pad: function(data)
	{
		var pad_length = data.charCodeAt(data.length-1);
		return data.substr(0, data.length - pad_length);
	}
});