←blog

RSS
  1. Cryptoops — a totally pointless JS crypto library

    I've recently been working on a project that involves encryption in web browser extensions, like take some text and encrypt it using a password. Pretty standard, except how do you do that in JavaScript? Due to my obsessive aversion to third party client dependencies, the decision was clear: I must read the specifications to all the relevant cryptographic standards and implement the algorithms myself in Javascript, from scratch.

    It never dawned on me that in the half-decade since I last played with JS crypto, perhaps things might have advanced past the previous status-quo of "roll your own crypto library #yolo". I neglected to consider the possibility that all of this functionality might be already baked into modern web browsers. Well turns out it is. While I was putting the finishing touches on my library (using a Web Worker to make it multi-threaded), I stumbled onto documentation for the Crypto.subtle API, a.k.a. everything I had just built but way faster due to being compiled code that takes advantage of native cryptographic CPU instructions. Oops.

    I can't bring myself to straight-up delete this code, but in reality it should never be used by anyone for any reason. Rather than try to make some sort of point (this is pointless), I'll leave it here for those who click "Read More."

    'use strict';
    /**
     *  @classdesc
     *  A Javascript crypto library which is totally redundant. Provides AES,
     *  SHA-1, SHA-256, PBKDF2, HMAC, and various block cipher modes of operation.
     *  Nobody should ever use this code for any reason because all of this is built
     *  into modern browsers (found out about that right after I wrote it, oops).
     *  See {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle}
     *
     *  @author     henriquez <henriquez@protonmail.com>
     *  @copyright  Copyright (c) 2019, henriquez. ({@link https://www.obsessivefacts.com})
     *  @license    {@link https://opensource.org/licenses/GPL-3.0|GNU General Public License version 3}
     *
     *  @return {Cryptoops}
     */
    var Cryptoops = function() {
        return this;
    }
    
    /**
     *  @classdesc
     *  This object offers various data conversion functions for use in crypto
     *  operations. Think of it as a grab bag of useful static methods.
     *
     *  @namespace
     *  @type {Object}
     */
    Cryptoops.DataUtils = {
        /**
         *  Converts an ASCII or UTF-8 string to a Uint8Array (bytes)
         *
         *  @static
         *  @param {string} str     The input string
         *  @return {Array}         The byte array
         */
        stringToBytes: function(str) {
            return Array.from(new TextEncoder("utf-8").encode(str))
        },
    
        /**
         *  Converts a Uint8Array (bytes) to a UTF-8 string
         *
         *  @static
         *  @param {string} str     The input string
         *  @return {Array}         The byte array
         */
        bytesToString: function(uint8Array) {
            return new TextDecoder("utf-8").decode(Uint8Array.from(uint8Array));
        },
    
        /**
         *  Converts a Uint8Array of bytes into a Uint32Array of "words"
         *  The input byte array must be a multiple of 4. See {@link Cryptoops.PaddingMode}
         *  if you need to pad it for some useful purpose.
         *
         *  @param {Array|Uint8Array} uint8Array  The input array of 8 bit integers
         *  @param {boolean} reverseEndian        Flag to reverse the endian-ness (byte order) of the words.
         *  @return {Array}                       An array of 32-bit integer "words"
         */
        bytesToWords: function(uint8Array, reverseEndian) {
            var words = [],
                _toWord = this.bytesToWord;
    
            if (reverseEndian)
                for (var i=0; i<uint8Array.length; i+=4)
                    words.push(_toWord(
                        uint8Array[i+3],
                        uint8Array[i+2],
                        uint8Array[i+1],
                        uint8Array[i]
                    ));
            else
                for (var i=0; i<uint8Array.length; i+=4)
                    words.push(_toWord(
                        uint8Array[i],
                        uint8Array[i+1],
                        uint8Array[i+2],
                        uint8Array[i+3]
                    ));
    
            return words;
        },
    
        /**
         *  Joins up to 4 unsigned integer bytes into one 32-bit integer "word"
         *
         *  @param {number} byte1      8-bit integer. Most significant byte
         *  @param {number} byte2      8-bit integer. Second most significant byte
         *  @param {number} byte3      8-bit integer. Third most significant byte
         *  @param {number} byte4      8-bit integer. Least significant byte
         *  @return {number}           A 32 bit integer "word"
         */
        bytesToWord: function() {
            if (arguments.length == 4)
                return ((arguments[0] & 255) << 24)
                    | ((arguments[1] & 255) << 16)
                    | ((arguments[2] & 255) << 8)
                    | (arguments[3] & 255);
    
            var joined  = 0;    
            for (var i = arguments.length-1; i >= 0; i--)
                joined |= (arguments[i] & 255) << 8*(arguments.length-1-i);
    
            return joined; 
        },
    
        /**
         *  Converts a Uint32Array of "words" into a Uint8Array of bytes
         *
         *  @param {Array|Uint32Array} uint32Array  The input array of 32-bit integer "words"
         *  @return {Array}                         A byte array of 8-bit integers
         */
        wordsToBytes: function(uint32Array) {
            var byteArray = [],
                _wordToBytes = this.wordToBytes;
    
            for (var i=0; i<uint32Array.length; i++) {
                var wordBytes = _wordToBytes(uint32Array[i]);
                for (var j=0; j<wordBytes.length; j++) {
                    byteArray.push(wordBytes[j]);
                }
            }
            return byteArray;
        },
    
        /**
         *  Splits a 32-bit integer word to an array of four 8-bit integers
         *
         *  @param {number} word    32-bit integer word
         *  @param {Array}          Array of 8-bit integers
         */
        wordToBytes: function(word) {
            return [
                ((word >>> 24) & 255),
                ((word >>> 16) & 255),
                ((word >>> 8) & 255),
                (word & 255)
            ];
        },
    
        /**
         *  Converts a hex string to a Uint8Array of bytes
         *  
         *  @param {string} hex     Hexadecimal string (do not prefix with '0x'}
         *  @return {Array}         Array of 8-bit integer bytes
         */
        hexToBytes: function(hex) {
            hex = hex.replace('0x', '');
            if (hex.length % 2 == 1) hex = '0' + hex;
    
            var bytes = [];
    
            for (var i=0; i<hex.length; i+=2)
                bytes.push(parseInt(hex.substr(i, 2),16));
    
            return bytes;
        },
    
        /**
         *  Converts byte array to a hexadecimal string
         *
         *  @param {Array|Uint8Array} uint8Array    Byte array to convert
         *  @return {String}                        Hexadecimal string
         */
        bytesToHex: function(uint8Array) {
            var hex = '';
            for (var i=0; i<uint8Array.length; i++) {
                hex += (uint8Array[i].toString(16).length == 1 ? '0' : '')
                    +  uint8Array[i].toString(16);
            }
            return hex;
        },
    
    };
    
    /**
     *  @abstract
     *  @class
     *  @classdesc   Abstract class providing base functionality for byte padding subclasses
     *  @desc        NOTE: don't instantiate this class directly. Instead, create instances of a subclass, such as {@link Cryptoops.ZeroPadding} or {@link Cryptoops.PKCS7}.
     *
     */
    Cryptoops.PaddingMode = function() {};
    
    /**
     *  Invoked during subclass instantiation. All properties needed to initialize the class must be passed in.
     *
     *  @param {Object} options                         A list of properties used to initialize the class.
     *  @param {Cryptoops.BlockCipher} options.cipher  A reference to a BlockCipher subclass instance. Used to get information about the block size.
     *  @return {Cryptoops.PaddingMode}
     */
    Cryptoops.PaddingMode.prototype.initialize = function(options) {
        if (!options) options = {};
        for (var attr in options) this[attr] = options[attr];
        return this;
    }
    
    /**
     *  Stores a reference to the BlockCipher subclass instance (used to get data about the block size)
     *  @type {Cryptoops.BlockCipher}
     *  @private
     */
    Cryptoops.PaddingMode.prototype.cipher = null;
    
    /**
     *  @class
     *  @classdesc              Implements Zero Padding functionality.
     *                              Data is padded to the correct block length multiple with zero bytes.
     *  @extends                Cryptoops.PaddingMode
     *
     *  @constructs
     *  @desc                   Creates a new ZeroPadding padding mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.ZeroPadding#initialize}
     *  @return {Cryptoops.ZeroPadding}
     */
    Cryptoops.ZeroPadding = function(options) {
        return this.initialize(options);
    }
    Cryptoops.ZeroPadding.prototype = Object.create(Cryptoops.PaddingMode.prototype);
    
    /**
     *  Perform the padding on the data.
     *
     *  @param {Array} uint8Array   An array of 8-bit integer words
     *  @return {Array}             Padded data
     */
    Cryptoops.ZeroPadding.prototype.doPad = function(uint8Array) {
        if ((uint8Array.length * 8) % this.cipher.getBlockSize() != 0)
            for (var i=0; uint8Array.length % (this.cipher.getBlockSize() / 8) != 0; i++)
                uint8Array.push(0);
    
        return uint8Array;
    }
    
    /**
     *  There is no safe way to undo zero padding. Simply returns the input array.
     *
     *  @param {Array} data    A binary data array (8-bit integers)
     *  @return {Array}        The exact same array that was passed in.
     */
    Cryptoops.ZeroPadding.prototype.undoPad = function(uint8Array) {
        return uint8Array;
    }
    
    /**
     *  @class
     *  @classdesc              Implements ANSI X.923 Padding functionality.
     *                              Data is padded to the correct block length multiple with zero 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                Cryptoops.PaddingMode
     *
     *  @constructs
     *  @desc                   Creates a new AnsiX923 padding mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.AnsiX923#initialize}
     *  @return {Cryptoops.AnsiX923}
     */
    Cryptoops.AnsiX923 = function(options) {
        return this.initialize(options);
    }
    Cryptoops.AnsiX923.prototype = Object.create(Cryptoops.PaddingMode.prototype);
    
    /**
     *  Perform the padding on the data.
     *
     *  @param {Array} uint8Array    An array of 8-bit integer bytes
     *  @return {Array}              Padded data
     */
    Cryptoops.AnsiX923.prototype.doPad = function(uint8Array) {
        // Pad the block out with zeros
        for (var i=0; i == 0 || uint8Array.length % (this.cipher.getBlockSize() / 8) != 0; i++)
            uint8Array.push(0);
    
        // Slice off the last byte and replace with our count
        uint8Array.pop();
        uint8Array.push(i);
    
        return uint8Array;
    }
    
    /**
     *  Undo padding on padded data.
     *
     *  @param {Array} data    An array of 8-bit integer bytes
     *  @return {Array}        An array with padding elements removed.
     */
    Cryptoops.AnsiX923.prototype.undoPad = function(uint8Array) {
        var padLength = uint8Array[uint8Array.length - 1];
        uint8Array.splice(uint8Array.length - padLength);
        return uint8Array;
    }
    
    /**
     *  @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                Cryptoops.PaddingMode
     *
     *  @constructs
     *  @desc                   Creates a new PKCS7 padding mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.PKCS7#initialize}
     *  @return {Cryptoops.PKCS7}
     */
    Cryptoops.PKCS7 = function(options) {
        return this.initialize(options);
    }
    Cryptoops.PKCS7.prototype = Object.create(Cryptoops.PaddingMode.prototype);
    
    /**
     *  Perform the padding on the data.
     *
     *  @param {Array} uint8Array   An array of 8-bit integer bytes
     *  @return {Array}             Padded data
     */
    Cryptoops.PKCS7.prototype.doPad = function(uint8Array) {
        var blockBytes = (this.cipher.getBlockSize() / 8),
            padCount   = uint8Array.length % blockBytes != 0 ? blockBytes - (uint8Array.length % blockBytes) : blockBytes;
    
            for (var i=0; i == 0 || uint8Array.length % (this.cipher.getBlockSize() / 8) != 0; i++)
                uint8Array.push(padCount);
    
            return uint8Array;
    }
    
    /**
     *  Undo padding on padded data.
     *
     *  @param {Array} data     An array of 8-bit integer bytes
     *  @return {Array}         An array with padding elements removed.
     */
    Cryptoops.PKCS7.prototype.undoPad = function(uint8Array) {
        var padLength = uint8Array[uint8Array.length - 1];
        uint8Array.splice(uint8Array.length - padLength);
        return uint8Array;
    }
    
    /**
     *  @abstract
     *  @class
     *  @classdesc   Abstract class which provides base functionality for block cipher mode operator subclasses
     *  @desc        NOTE: don't instantiate this class directly. Instead, create instances of a subclass, such as {@link Cryptoops.CBC} or {@link Cryptoops.CFB}.
     */
    Cryptoops.BlockCipherMode = function() {};
    
    /**
     *  Invoked during subclass instantiation. All properties needed to initialize the class must be passed in.
     *
     *  @param {Object} options                         A list of properties used to initialize the class.
     *  @param {Cryptoops.BlockCipher} options.cipher  A reference to a BlockCipher subclass instance. Used to get information about the block size.
     *  @return {Cryptoops.BlockCipherMode}
     */
    Cryptoops.BlockCipherMode.prototype.initialize = function(options) {
        if (!options) options = {};
        for (var attr in options) this[attr] = options[attr];
        return this;
    }
    
    /**
     *  Stores a reference to the BlockCipher subclass instance (used for *cryption)
     *  @type {Cryptoops.BlockCipher}
     *  @private
     */
    Cryptoops.BlockCipherMode.prototype.cipher = null;
    
    /**
     *  Invoked during subclass initialize when an Initial Vector is required.
     *  Converts the BlockCipher instance's 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.
     */
    Cryptoops.BlockCipherMode.prototype.initIV = function() {
        var iv = Cryptoops.DataUtils.bytesToWords(this.cipher.getIV());
    
        if (iv.length != this.getWordsPerBlock())
            throw new Error('Cryptoops.BlockCipherMode: 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}
     */
    Cryptoops.BlockCipherMode.prototype.getWordsPerBlock = function() {
        return this.cipher.getBlockSize() / 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 of 32-bit integer words
     *  @param {Array} block2   The second block of 32-bit integer words
     *  @return {Array}         The result of XOR'ing the two blocks together
     */
    Cryptoops.BlockCipherMode.prototype.xorBlock = 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.
     *                              Note ECB is worthles, don't use it except for testing.
     *  @extends                Cryptoops.BlockCipherMode
     *
     *  @constructs
     *  @desc                   Creates a new ECB block cipher mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.ECB#initialize}
     *  @return {Cryptoops.ECB}
     */
    Cryptoops.ECB = function(options) {
        this.initialize(options);
        return this;
    }
    Cryptoops.ECB.prototype = Object.create(Cryptoops.BlockCipherMode.prototype);
    
    /**
     *  Encrypts blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.ECB#getWordsPerBlock}
     *  @return {Array}         Encrypted blocks
     */
    Cryptoops.ECB.prototype.encryptBlocks = function(words) {
        var ciphertext = [],
            wordsPerBlock = this.getWordsPerBlock();
    
        for (var i=0; i<words.length; i+=wordsPerBlock) {
            var encryptedBlock = this.cipher.blockEncrypt(words.slice(i, i+wordsPerBlock));
            for (var j=0; j<encryptedBlock.length; j++) {
                ciphertext.push(encryptedBlock[j]);
            }
        }
        return ciphertext;
    }
    
    /**
     *  Decrypt blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.ECB#getWordsPerBlock}
     *  @return {Array}         Decrypted blocks
     */
    Cryptoops.ECB.prototype.decryptBlocks = function(words) {
        var plaintext       = [],
            wordsPerBlock = this.getWordsPerBlock();
    
        for (var i=0; i<words.length; i+=wordsPerBlock)
        {
            var block = words.slice(i, i+wordsPerBlock),
                decryptedBlock = this.cipher.blockDecrypt(words.slice(i, i+wordsPerBlock));
    
            for (var j=0; j<decryptedBlock.length; j++) {
                plaintext.push(decryptedBlock[j]);
            }
        }
        return plaintext;
    }
    
    /**
     *  @class
     *  @classdesc              Implements CBC block cipher mode. Used internally during symmetric *cryption.
     *  @extends                Cryptoops.BlockCipherMode
     *  @requires               Cryptoops.DataUtils
     *
     *  @constructs
     *  @desc                   Creates a new CBC block cipher mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.CBC#initialize}
     *  @return {Cryptoops.CBC}
     */
    Cryptoops.CBC = function(options) {
        this.initialize(options);
        this.initIV();
        return this;
    }
    Cryptoops.CBC.prototype = Object.create(Cryptoops.BlockCipherMode.prototype);
    
    /**
     *  Array of 32-bit words for the Initial Vector (IV)
     *  @private
     */
    Cryptoops.CBC.prototype.iv = [];
    
    /**
     *  Encrypts blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.CBC#getWordsPerBlock}
     *  @return {Array}         Encrypted blocks
     */
    Cryptoops.CBC.prototype.encryptBlocks = function(words) {
        var ciphertext    = [],
            wordsPerBlock = this.getWordsPerBlock(),
            _prevBlock    = this.iv;
    
        for (var i=0; i<words.length; i+=wordsPerBlock) {
            var block = words.slice(i, i+wordsPerBlock),
                xorBlock = this.xorBlock(block, _prevBlock),
                encryptedBlock = this.cipher.blockEncrypt(xorBlock),
                _prevBlock = encryptedBlock;
    
            for (var j=0; j<encryptedBlock.length; j++) {
                ciphertext.push(encryptedBlock[j]);
            }
        }
        return ciphertext;
    }
    
    /**
     *  Decrypt blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.CBC#getWordsPerBlock}
     *  @return {Array}         Decrypted blocks
     */
    Cryptoops.CBC.prototype.decryptBlocks = function(words) {
        var plaintext     = [],
            wordsPerBlock = this.getWordsPerBlock(),
            _prevBlock   = this.iv;
    
        for (var i=0; i<words.length; i+=wordsPerBlock)
        {
            var block = words.slice(i, i+wordsPerBlock),
                decryptedBlock = this.cipher.blockDecrypt(block),
                xorDecryptedBlock = this.xorBlock(decryptedBlock, _prevBlock),
                _prevBlock = block;
    
            for (var j=0; j<xorDecryptedBlock.length; j++) {
                plaintext.push(xorDecryptedBlock[j]);
            }
        }
        return plaintext;
    }
    
    /**
     *  @class
     *  @classdesc              Implements CFB block cipher mode. Used internally during symmetric *cryption.
     *  @extends                Cryptoops.BlockCipherMode
     *  @requires               Cryptoops.DataUtils
     *
     *  @constructs
     *  @desc                   Creates a new CFB block cipher mode instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.CFB#initialize}
     *  @return {Cryptoops.CFB}
     */
    Cryptoops.CFB = function(options) {
        this.initialize(options);
        this.initIV();
        return this;
    }
    Cryptoops.CFB.prototype = Object.create(Cryptoops.BlockCipherMode.prototype);
    
    /**
     *  Array of 32-bit words for the Initial Vector (IV)
     *  @private
     */
    Cryptoops.CFB.prototype.iv = [];
    
    /**
     *  Encrypts blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.CFB#getWordsPerBlock}
     *  @return {Array}         Encrypted blocks
     */
    Cryptoops.CFB.prototype.encryptBlocks = function(words) {
        var ciphertext    = [],
            wordsPerBlock = this.getWordsPerBlock(),
            _prevBlock    = this.iv;
    
        for (var i=0; i<words.length; i+=wordsPerBlock) {
            var cipherBlock = this.cipher.blockEncrypt(_prevBlock),
                block = words.slice(i, i+wordsPerBlock),
                xorEncryptedBlock = this.xorBlock(block, cipherBlock),
                _prevBlock = xorEncryptedBlock;
    
            for (var j=0; j<xorEncryptedBlock.length; j++) {
                ciphertext.push(xorEncryptedBlock[j]);
            }
        }
        return ciphertext;
    }
    
    /**
     *  Decrypt blocks.
     *
     *  @param {Array} words    An array of 32-bit integer words whose length must be an integer multiple of {@link Cryptoops.CFB#getWordsPerBlock}
     *  @return {Array}         Decrypted blocks
     */
    Cryptoops.CFB.prototype.decryptBlocks = function(words) {
        var plaintext     = [],
            wordsPerBlock = this.getWordsPerBlock(),
            _prevBlock   = this.iv;
    
        for (var i=0; i<words.length; i+=wordsPerBlock)
        {
            var cipherBlock = this.cipher.blockEncrypt(_prevBlock),
                block = words.slice(i, i+wordsPerBlock),
                xorDecryptedBlock = this.xorBlock(cipherBlock, block),
                _prevBlock = block;
    
            for (var j=0; j<xorDecryptedBlock.length; j++) {
                plaintext.push(xorDecryptedBlock[j]);
            }
        }
        return plaintext;
    }
    
    /**
     *  @abstract
     *  @class
     *  @classdesc   Abstract class which provides base functionality used by all symmetric block cipher subclasses
     *  @desc        NOTE: don't instantiate this class directly. Instead, create instances of a subclass, such as {@link Cryptoops.AES} or {@link Cryptoops.Twofish}.
     *  @requires    Cryptoops.DataUtils
     */
    Cryptoops.BlockCipher = function() {};
    
    /**
     *  Invoked during subclass initialization. All properties needed to initialize the class must be passed in.
     *  NOTE: If you're initializing your cipher for encryption with a passphrase, a salt will be automatically
     *  generated (if not specified) and a default iteration count (if not specified) will apply for the PBKDF2
     *  key derivation algorithm. You must use the {@link Cryptoops.BlockCipher#getPassphraseSalt} and
     *  {@link Cryptoops.BlockCipher#getPassphrasePBKDF2Iterations} methods after encryption and package these
     *  values with your ciphertext data in order for the intended recipient to be able to decrypt it using your
     *  passphrase.
     *
     *  @param {Object} options                                 A list of properties used to initialize the class.
     *  @param {Uint8Array} [options.key]                       A byte array containing the symmetric key. Required if a passphrase is not specified.
     *                                                          Must match a key length supported by the subclass.
     *  @param {Cryptoops.BlockCipherMode} [options.blockMode={@link Cryptoops.CBC}]  The block cipher mode of operation to use for *cryption
     *  @param {Cryptoops.PaddingMode} [options.padMode={@link Cryptoops.PKCS7}]      The block byte padding mode
     *  @param {string} [options.passphrase]                    A passphrase to derive a key from. Required if a key is not explicitly specified.
     *  @param {Array} [options.passphraseSalt]                 A byte array of 8-bit integers containing the cryptographic salt used for key
                                                                    derivation. Randomly generated if not specified, use
                                                                    {@link Cryptoops.BlockCipher#getPassphraseSalt} to recall it in that case.
     *  @param {number} [options.passphrasePBKDF2Iterations=1]  The number of iterations to use for PBKDF2 key derivation with the passphrase
     *  @return {Cryptoops.BlockCipher}
     */
    Cryptoops.BlockCipher.prototype.initialize = function(options) {
        if (!options) options = {};
    
        for (var attr in options) this[attr] = options[attr];
    
        if (!options.blockMode) this.blockMode = Cryptoops.CBC;
        if (!options.padMode) this.padMode = Cryptoops.PKCS7;
    
        if (options.passphrase) {
            if (!options.passphraseSalt) this.generateRandomPassphraseSalt();
            this.populateKeyAndIVFromPassphrase();
        }
    
        return this;
    }
    
    /**
     *  The block cipher mode of operation (see {@link https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation})
     *  @type {Cryptoops.BlockCipherMode}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.blockMode = null;
    
    /**
     *  The block byte padding mode (see {@link http://en.wikipedia.org/wiki/Padding_%28cryptography%29})
     *  @type {Cryptoops.PaddingMode}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.padMode = null;
    
    /**
     *  Initial vector for non-ECB block cipher modes
     *  @type {Array}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.iv = null;
    
    /**
     *  Passphrase, stored temporarily before key derivation
     *  @type {string}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.passphrase = '';
    
    /**
     *  Passphrase salt, used to mix things up!
     *  @type {Array}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.passphraseSalt = null;
    
    /**
     *  The number of iterations to use for PBKDF2 key derivation with the passphrase
     *  @type {number}
     *  @private
     */
    Cryptoops.BlockCipher.prototype.passphrasePBKDF2Iterations = 1;
    
    /**
     *  Turn this on to enable the {@link Cryptoops.BlockCipher#debugWrite} method to log debug output to the console.
     *
     *  @type {Boolean}
     *  @default false
     */
    Cryptoops.BlockCipher.prototype.debugMode = false;
    
    /**
     *  Generate a random Initial Vector for the instance (used if none specified)
     *  NOTE: This is probably unnecessary since typically the initial vector is
     *  populated from the passphrase in {@link Cryptoops.BlockCipher#populateKeyAndIVFromPassphrase}
     */
    Cryptoops.BlockCipher.prototype.generateRandomIV = function() {
        var _ivTmp = new Uint8Array(this.getBlockSize() / 8),
            iv = [];
    
        window.crypto.getRandomValues(_ivTmp);
    
        for (var i=0; i<_ivTmp.length; i++)
            iv.push(_ivTmp[i]);
    
        this.iv = iv;
    }
    
    /**
     *  Generate a random Passphrase Salt value for the instance.
     *  This can later be read with {@link Cryptoops.BlockCipher#getPassphraseSalt}
     */
    Cryptoops.BlockCipher.prototype.generateRandomPassphraseSalt = function() {
        var _passphraseSaltTmp = new Uint8Array(this.getBlockSize() / 8),
            passphraseSalt = [];
    
        window.crypto.getRandomValues(_passphraseSaltTmp);
    
        for (var i=0; i<_passphraseSaltTmp.length; i++)
            passphraseSalt.push(_passphraseSaltTmp[i]);
    
        this.passphraseSalt = passphraseSalt;
    }
    
    /**
     *  Getter for the instance's Initial Vector
     *
     *  @return {Array} Initial Vector
     */
    Cryptoops.BlockCipher.prototype.getIV = function() {
        return this.iv;
    }
    
    /**
     *  Getter for the instance's Salt value
     *
     *  @return {Array} Salt value
     */
    Cryptoops.BlockCipher.prototype.getPassphraseSalt = function() {
        return this.passphraseSalt;
    }
    
    /**
     *  Getter for the instance's passphrase PBKDF2 iteration count
     *
     *  @return {number} PBKDF2 iteration count
     */
    Cryptoops.BlockCipher.prototype.getPassphrasePBKDF2Iterations = function() {
        return this.passphrasePBKDF2Iterations;
    }
    
    /**
     *  Populates the instance Key and Initial Vector from its temporarily-stored
     *  passphrase. Deletes the passphrase from the instance afterwards.
     *  @private
     */
    Cryptoops.BlockCipher.prototype.populateKeyAndIVFromPassphrase = function() {
        var keyBytes = this.getKeyLength() / 8,
            blockBytes = this.getBlockSize() / 8,
            keyIV = new Cryptoops.PBKDF2({
                keySize: (keyBytes + blockBytes),
                iterations: this.getPassphrasePBKDF2Iterations()
            })
            .compute(this.passphrase, this.getPassphraseSalt());
    
        this.iv = keyIV.splice(keyIV.length - blockBytes);
        this.passphrase = '';
        this.setKey(keyIV);
    }
    
    Cryptoops.BlockCipher.prototype.initCipherFromPassphraseThreaded = function(passphrase, iterations, hasher, callback) {
        this.generateRandomPassphraseSalt();
    
    }
    
    /**
     *  Writes debug information to the console if {@link Cryptoops.BlockCipher#debugMode} is turned on.
     *  @param {...mixed} arguments The variables to write to console
     */
    Cryptoops.BlockCipher.prototype.debugWrite = function() {
        if (this.debugMode) console.log.apply(console, arguments);
    }
    
    /**
     *  Encrypts a string using the padding and block cipher mode of operation specificed on initialization.
     *
     *  @param {String|Array} plaintext     An ASCII or UTF-8 string, or array of 8-bit integer bytes to encrypt.
     *  @return {Array}                Encrypted data array
     */
    Cryptoops.BlockCipher.prototype.encrypt = function(plaintext) {
        if (!Array.isArray(plaintext)) {
            plaintext  = Cryptoops.DataUtils.stringToBytes(plaintext);
        }
        var plaintext  = new this.padMode({ cipher: this}).doPad(plaintext),
            plaintext  = Cryptoops.DataUtils.bytesToWords(plaintext),
            operator   = new this.blockMode({ cipher: this }),
            ciphertext = operator.encryptBlocks(plaintext),
            ciphertext = Cryptoops.DataUtils.wordsToBytes(ciphertext);
    
        return ciphertext;
    }
    
    /**
     *  Decrypts a Uint8Array byte array into a plaintext byte array.
     *  You may wish to convert this back to a UTF-8 string afterwards.
     *  See {@link Cryptoops.DataUtils#bytesToString}
     *
     *  @param {Array} ciphertext   Encrypted array of 8-bit integer bytes
     *  @return {Array}             Decrypted byte array
     */
    Cryptoops.BlockCipher.prototype.decrypt = function(ciphertext) {
        var ciphertext = Cryptoops.DataUtils.bytesToWords(ciphertext),
            operator   = new this.blockMode({ cipher: this }),
            plaintext  = operator.decryptBlocks(ciphertext),
            plaintext  = Cryptoops.DataUtils.wordsToBytes(plaintext),
            plaintext  = new this.padMode({ cipher: this }).undoPad(plaintext);
    
        return plaintext;
    }
    
    /**
     *  @requires               Cryptoops.DataUtils
     *  @class
     *  @classdesc              PBKDF2 class. Derives a symmetric key from a passphrase and optional salt.
     *  @desc                   Creates a new Cryptoops.PBKDF2 instance.
     *  @this                   Cryptoops.PBKDF2
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.PBKDF2#initialize}
     *  @param {number} [options.keySize=32]    Key size in bytes (must be a multiple of 4 bytes)
     *  @param {Cryptoops.Hasher} [options.hasher=Cryptoops.SHA256]   Hasher subclass to use for the actual hashing
     *  @param {number} [options.iterations=1]   Number of iterations to use for key derivation.
     *  @return {Cryptoops.PBKDF2}
     *
     *  @</p>
     *
     *  _kdf        = new Cryptoops.PBKDF2({keySize: 16, hasher: Cryptoops.SHA256, iterations: 1000});
     *  derived_key = _kdf.compute('password1234', 'saltsalt');
     *  derived_iv  = _kdf.compute(derived_key, 'saltsaltsaltsalt');
     *  ciphertext  = new Cryptoops.AES({key: derived_key, iv: derived_iv}).encrypt('mydata');
     *
     *  // Note you don't have to do all this work to encrypt using a passphrase.
     *  // Just {@link Cryptoops.BlockCipher#initialize|initialize} your cipher instance using the passphrase option.
     *  // Doing so will automatically use Cryptoops.PBKDF2 internally to generate a key and IV.
     *
     */
    Cryptoops.PBKDF2 = function(options) {
        if (!options) options = {};
    
        if (options.keySize)
            if (options.keySize % 4 != 0)
                throw new Error('Cryptoops.PBKDF2: Key size must be a multiple of 4 bytes.');
            else
                this.keySize = options.keySize;
    
        if (options.iterations)
            this.iterations = options.iterations;
    
        this.hasher = options.hasher ? options.hasher : Cryptoops.SHA1;
    
        return this;
    }
    
    /**
     *  The length in bytes of the derived key. Set with {@link Cryptoops.PBKDF2#initialize}
     *  @private
     *  @type {number}
     */
    Cryptoops.PBKDF2.prototype.keySize = 32;
    
    /**
     *  The Hasher subclass to use for key derivation. Default SHA256 is set by {@link Cryptoops.PBKDF2#initialize} if none passed.
     *  @private
     *  @type {Cryptoops.Hasher}
     */
    Cryptoops.PBKDF2.prototype.hasher = null;
    
    /**
     *  The number of iterations to hash our key data. One of the few times slowing down an
     *  algorithm is arguably better.
     *  @private
     *  @type {number}
     */
    Cryptoops.PBKDF2.prototype.iterations = 1;
    
    /**
     *  Derives a PBKDF2 key from a given passphrase and salt value
     *
     *  @param {string} passphrase                          Passphrase to be used for the derived key.
     *  @param {Array|string} [salt]                        Salt value either a string or array of 8-bit bytes
     *  @param {Object} [options={}]                        Optional options object.
     *  @param {string} [options.return_format='bytes']     (bytes|hex|words) The return format.
     *
     *  @return {Array|string|Uint32Array}                  A string or byte array depending on options.returnFormat
     */
    Cryptoops.PBKDF2.prototype.compute = function(passphrase, salt, options) {
        if (!salt) salt = [];
        if (!options) options = {};
        if (!options.returnFormat) options.returnFormat = 'bytes';
    
        if (!Array.isArray(salt)) {
            salt = Cryptoops.DataUtils.stringToBytes(salt);
        }
    
        var hmac = new Cryptoops.HMAC({ hasher: this.hasher, passphrase: passphrase }),
            key = [],
            keySize = this.keySize;
    
        for (var i=1; key.length < keySize; i++) {
            var block = hmac.hash(salt, {stream: true})
                            .hash(Cryptoops.DataUtils.wordToBytes(i), {returnFormat: 'words'});
    
            var temp = block;
    
            for (var j=1; j<this.iterations; j++) {
                temp = hmac.hash(Cryptoops.DataUtils.wordsToBytes(temp), {returnFormat: 'words'});
    
                for (var k=0; k<block.length; k++) {
                    block[k] ^= temp[k];
                }
            }
    
            var blockBytes = Cryptoops.DataUtils.wordsToBytes(block);
            for (var j=0; j<blockBytes.length; j++) {
                key.push(blockBytes[j]);
            }
        }
        key = key.slice(0, keySize);
    
        switch (options.returnFormat) {
            case 'hex':
                return Cryptoops.DataUtils.bytesToHex(key);
            case 'words':
                return Cryptoops.DataUtils.bytesToWords(key);
            case 'bytes':
            default:
                return key;
        }
    
    }
    
    /**
     *  @requires               Cryptoops.DataUtils
     *  @class
     *  @classdesc              HMAC Hashing Class
     *  @desc                   Creates a new Cryptoops.HMAC instance.
     *  @this                   Cryptoops.HMAC
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.PBKDF2#initialize}
     *  @param {number} [options.passphrase]    Passphrase string
     *  @param {Cryptoops.Hasher} [options.hasher=Cryptoops.SHA256]   Hasher subclass to use for the actual hashing
     *  @return {Cryptoops.HMAC}
     */
    Cryptoops.HMAC = function(options) {
        if (!options) options = {};
    
        var hasher = options.hasher ? options.hasher : Cryptoops.SHA256;
    
        if (!options.passphrase)
            throw new Error('Cryptoops.HMAC: You must specify a passphrase for the HMAC class initialization function.');
    
        this.hasherInstance = new hasher();
        var blockBytes = this.hasherInstance.getBlockSize() / 8;
    
        if (!Array.isArray(options.passphrase))
            var key = Cryptoops.DataUtils.stringToBytes(options.passphrase)
        else
            var key = options.passphrase;
    
        if (key.length > blockBytes) {
            key = this.hasherInstance.hash(key, {returnFormat: 'bytes'});
            this.hasherInstance.clear();
        } else if (key.length < blockBytes) {
            key = this.padKey(key, blockBytes, 0x00);
        }
    
        key = Cryptoops.DataUtils.bytesToWords(key);
    
        var oPad = Cryptoops.DataUtils.bytesToWords(this.padKey([], blockBytes, 0x5c));
        var iPad = Cryptoops.DataUtils.bytesToWords(this.padKey([], blockBytes, 0x36));
    
        this.iKeyPad = Cryptoops.DataUtils.wordsToBytes(this.xorBlock(iPad, key));
        this.oKeyPad = Cryptoops.DataUtils.wordsToBytes(this.xorBlock(oPad, key));
    
        return this;
    }
    
    /**
     *  Placeholder for initialized hasher instance. Set by {@link Cryptoops.HMAC#initialize}
     *  @type {Cryptoops.Hasher}
     *  @private
     */
    Cryptoops.HMAC.prototype.hasherInstance = null;
    
    /**
     *  Tracks whether the hasher has already received message data. Used for progressive hashing.
     *  @type {boolean}
     *  @private
     */
    Cryptoops.HMAC.prototype.hasStreamedData = false;
    
    /**
     *  32-bit integer word array for inner key ^ pad block matching length of hasher's block size. Set by {@link Cryptoops.HMAC#initialize}
     *  @type {Array}
     *  @private
     */
    Cryptoops.HMAC.prototype.iKeyPad = null;
    
    /**
     *  32-bit integer word array for outer key ^ pad block matching length of hasher's block size. Set by {@link Cryptoops.HMAC#initialize}
     *  @type {Array}
     *  @private
     */
    Cryptoops.HMAC.prototype.oKeyPad = null;
    
    /**
     *  Resets the hasher instance used for the HMAC hashing, but maintains key information
     *  derived from the passphrase. This effectively allows you to re-use the class
     *  without reinitializing / re-specifying the passphrase.
     */
    Cryptoops.HMAC.prototype.reset = function() {
        this.hasStreamedData = false;
        this.hasherInstance.clear();
    }
    
    /**
     *  Performs HMAC hashing on message data.
     *
     *  This optionally supports streaming mode (or "progressive hashing"), which allows you to hash data
     *  in chunks over multiple calls, using the same hasher instance. Progressive hashing should improve
     *  memory usage for large datasets, since we don't necessarily need to keep all of it in memory at
     *  once. Once you are finished hashing data in progressive mode, call finalize() to return the hash.
     *
     *  @param {string|Array} data                      Either a string, or array of 8-bit integer bytes to hash
     *  @param {Object} options                         Options object. (Parameter descriptions below)
     *  @param {boolean} [options.stream=false]         Uses progressive hashing. Default: false
     *  @param {string} [options.returnFormat='hex']    (bytes|hex|words) The return format. Default: hex
     *
     *  @return {mixed}                                 Returns desired output format if options.stream is false, else this.
     */
    Cryptoops.HMAC.prototype.hash = function(data, options) {
        if (!options) options = {};
        if (!options.stream) options.stream = false;
    
        if (!Array.isArray(data))
            data = Cryptoops.DataUtils.stringToBytes(data);
    
        if (this.hasStreamedData == false) {
            var _tmpData = [],
                _iKeyPad = this.iKeyPad;
    
            for (var i=0; i<_iKeyPad.length; i++)
                _tmpData.push(_iKeyPad[i]);
    
            for (var i=0; i<data.length; i++)
                _tmpData.push(data[i]);
    
            data = _tmpData;
            this.hasStreamedData = true;
        }
    
        this.hasherInstance.hash(data, {stream: true});
    
        if (options.stream == false)
            return this.finalize(options);
    
        return this;
    }
    
    /**
     *  Finalizes the data hashing, computes and returns the final HMAC hash.
     *
     *  This is called internally by hash() when progressive mode is turned off.
     *  Otherwise, you call it explicitly when you're done hashing data in progressive mode.
     *
     *  @param {Object} [options={}]                    Options object. (Parameter descriptions below)
     *  @param {string} [options.returnFormat='hex']    (bytes|hex|words) The return format. Default: hex
     *
     *  @return {string}
     */
    Cryptoops.HMAC.prototype.finalize = function(options) {
        if (!options) options = {};
        if (!options.returnFormat) options.returnFormat = 'hex';
    
        var innerHash = this.hasherInstance.finalize({returnFormat: 'bytes'});
    
        this.hasherInstance.clear();
    
        var finalBytes = [],
            _oKeyPad = this.oKeyPad;
    
        for (var i=0; i<_oKeyPad.length; i++)
            finalBytes.push(_oKeyPad[i]);
    
        for (var i=0; i<innerHash.length; i++)
            finalBytes.push(innerHash[i]);
    
        var hmac = this.hasherInstance.hash(finalBytes, {returnFormat: options.returnFormat});
    
        this.reset();
    
        return hmac;
    }
    
    /**
     *  Pads a key to a specified length with a repeating byte value.
     *
     *  @private
     *
     *  @param {Array} key              The key to pad (an 8-bit integer array)
     *  @param {number} paddedLength    The desired length after padding
     *  @param {number} padValue        The ASCII character code of the byte to pad into the key
     *
     *  @return Array
     */
    Cryptoops.HMAC.prototype.padKey = function(key, paddedLength, padValue) {
        for (var i=0; key.length < paddedLength; i++) key.push(padValue);
        return key;
    }
    
    /**
     *  XORs two blocks of 32-bit words together
     *
     *  @private
     *
     *  @param {Array} block1   32-bit integer array for first block
     *  @param {Array} block2   32-bit integer array for the second block
     *  @return {Array}
     */
    Cryptoops.HMAC.prototype.xorBlock = function(block1, block2) {
        for (var i=0; i < block1.length; i++) block1[i] ^= block2[i];   
        return block1;
    }
    
    /**
     *  Generic Hasher Base Class
     *
     *  @abstract 
     *  @class
     *  @classdesc      Abstract class providing shared functionality to Hasher subclasses.
     *  @desc           NOTE: you can't instantiate this class directly. Instead, create instances of a subclass, such as {@link Cryptoops.SHA256}.
     *  @requires       Cryptoops.DataUtils
     */
    Cryptoops.Hasher = function() {};
    
    /**
     *  Internal state for the byte buffer
     *  @type {Array}
     *  @private
     */
    Cryptoops.Hasher.prototype.buffer = [];
    
    /**
     *  Internal state for the hashed data as it is processed
     *  @type {Array}
     *  @private
     */
    Cryptoops.Hasher.prototype.h = [];
    
    /**
     *  The length in bits of data that has been processed
     *  @type {number}
     *  @private
     */
    Cryptoops.Hasher.prototype.processedLength = 0;
    
    /**
     *  Internal flag depending on the Hasher subclass instance
     *  When true, the bytes are listed in words in small-endian order instead of the default big-endian
     *  @type {boolean}
     *  @private
     */
    Cryptoops.Hasher.prototype.reverseEndianWords = false;
    
    /**
     *  Initializes class member values. Invoked by the clear method from a subclass instance.
     */
    Cryptoops.Hasher.prototype.resetState = function() {
        this.buffer = [];
        this.processedLength = 0;
    }
    
    /**
     *  Pads the remaining buffer to 512 bits.
     *  @private
     */
    Cryptoops.Hasher.prototype.pad = function() {
        var finalLength = this.processedLength + (this.buffer.length * 8);
    
        this.buffer.push(128); // 10000000 to begin padding
    
        // Pad the buffer out to (512 - 64) bits
        for (var i=0; (this.buffer.length + 8) % 64 != 0; i++)
            this.buffer.push(0);
    
        // Add our 64 bit length value to the end of the buffer
        var finalBinary = finalLength.toString(2);
    
        for (var i=0; finalBinary.length % 64 != 0; i++)
            finalBinary = '0' + finalBinary;
    
        if (this.reverseEndianWords == false) {
            var finalWord1 = Cryptoops.DataUtils.wordToBytes(parseInt(finalBinary.substr(0, 32), 2)),
                finalWord2 = Cryptoops.DataUtils.wordToBytes(parseInt(finalBinary.substr(32), 2));
        } else {
            var finalWord1 = Cryptoops.DataUtils.wordToBytes(this.reverseWord(parseInt(finalBinary.substr(32), 2))),
                finalWord2 = Cryptoops.DataUtils.wordToBytes(this.reverseWord(parseInt(finalBinary.substr(0, 32), 2)));
        }
    
        for (var i=0; i<finalWord1.length; i++) this.buffer.push(finalWord1[i]);
        for (var i=0; i<finalWord2.length; i++) this.buffer.push(finalWord2[i]);
    }
    
    /**
     *  Finalizes the hash and returns the result.
     *  This is called internally by the hash method of the Hasher subclass instance when streaming mode
     *  is turned off. It should be called explicitly when using streaming mode after all data
     *  is passed into the hash method.
     *
     *  @param {Object} options                     Optional options object (descriptions of parameters below)      
     *  @param {string} [options.returnFormat=hex]  (bytes|hex|words) The return format. Default: hex
     *
     *  @return {string|Array}                      A string or array depending on options.return_format
     */
    Cryptoops.Hasher.prototype.finalize = function(options) {
        if (!options) options = {};
        if (!options.returnFormat) options.returnFormat = 'hex';
    
        // DO FINALIZE
        this.pad();
    
        // Hash our padded final buffer in streaming mode
        this.hash([], {stream: true});
    
        if (this.reverseEndianWords) {
            this.h = [
                this.reverseWord(this.h[0]),
                this.reverseWord(this.h[1]),
                this.reverseWord(this.h[2]),
                this.reverseWord(this.h[3])
            ];
        }
    
        var output = this.h.slice(0);
    
        this.clear();
    
        switch (options.returnFormat) {
            case 'hex':
                return Cryptoops.DataUtils.bytesToHex(Cryptoops.DataUtils.wordsToBytes(output));
            case 'bytes':
                return Cryptoops.DataUtils.wordsToBytes(output);
            case 'words':
                return output;
        }
    }
    
    /**
     *  Returns the block size in bits for the current instance
     *  @return {number} The block size in bits.
     */
    Cryptoops.Hasher.prototype.getBlockSize = function() {
        return this.blockSize;
    }
    
    /**
     *  Returns the length in bits of the output block for this instance
     *  @return {number} The output block size in bits.
     */
    Cryptoops.Hasher.prototype.getOutlen = function() {
        return this.outlen;
    }
    
    /**
     *  Reverses the endian-ness of the bytes in a word
     *
     *  @param {number} word        32-bit input word
     *  @return {number}            32-bit word with bytes swapped around
     *  @private
     */
    Cryptoops.Hasher.prototype.reverseWord = function(word) {
        return Cryptoops.DataUtils.bytesToWord(
            word & 255,
            (word >>> 8) & 255,
            (word >>> 16) & 255,
            (word >>> 24) & 255
        );
    }
    
    /**
     *  @class
     *  @classdesc      Implements the SHA-256 secure hash algorithm specified in FIPS 180-4 ({@link http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf})
     *  @extends        Cryptoops.Hasher
     *  @desc           Creates a new SHA-256 instance.
     *  @requires       Cryptoops.DataUtils
     *  @return         {Cryptoops.SHA256}
     */
    Cryptoops.SHA256 = function() {
        this.clear();
        return this;
    }
    Cryptoops.SHA256.prototype = Object.create(Cryptoops.Hasher.prototype);
    
    /**
     *  The block size in bits for the hashing operation
     *  @private
     *  @type {number}
     */
    Cryptoops.SHA256.prototype.blockSize = 512;
    
    /**
     *  The length in bits of the output block
     *  @private
     *  @type {number}
     */
    Cryptoops.SHA256.prototype.outlen = 256;
    
    /**
     *  Constants lookup table used for bitwise operations in the SHA256 algorithm
     *  @private
     */
    Cryptoops.SHA256.prototype.k = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
    ]
    
    /**
     *  Initialize class values, invoking {@link Cryptoops.Hasher#clear} before clearing values specific to {@link Cryptoops.SHA256}.
     *  This is called internally at the end of {@link Cryptoops.SHA256#finalize}, and can be called explicitly when
     *  using progressive hashing.
     *  @override
     */
    Cryptoops.SHA256.prototype.clear = function() {
        this.resetState();
        this.h = [
            0x6a09e667,
            0xbb67ae85,
            0x3c6ef372,
            0xa54ff53a,
            0x510e527f,
            0x9b05688c,
            0x1f83d9ab,
            0x5be0cd19
        ];
    }
    
    /**
     *  Hashes data into the SHA256 class instance.
     *  
     *  @param {string|Array} data                      String or array of 8-bit integer bytes to hash
     *  @param {Object} [options]                       Optional options object
     *  @param {boolean} [options.stream=false]         Whether to use streaming (progressive hashing) mode. In streaming
     *                                                  mode, you can repeatedly hash data into the SHA256 object.
     *                                                  The hash will not be finalized and returned until you call
     *                                                  {@link Cryptoops.SHA256#finalize}. Streaming mode is useful when you have to
     *                                                  hash a huge amount of data and you don't want to store all
     *                                                  of it in memory at one time.
     *  @param {string} [options.returnFormat='hex']    (bytes|hex|words) The return format. Default: hex
     *
     *  @return {string|Array|Cryptoops.SHA256}        Desired output format if streaming mode is turned off. Otherwise this instance (chainable)
     */
    Cryptoops.SHA256.prototype.hash = function(data, options) {
        if (!options) options = {};
        if (!options.stream) options.stream = false;
    
        if (!Array.isArray(data))
            data = Cryptoops.DataUtils.stringToBytes(data);
    
        var _buffer = [],
            realBuffer = this.buffer;
    
        for (var i=0; i<realBuffer.length; i++)
            _buffer.push(realBuffer[i]);
    
        for (var i=0; i<data.length; i++)
            _buffer.push(data[i]);
    
        var _processedLength = this.processedLength,
            k = this.k,
            h = this.h;
    
        for (var i=0; (i+64) <= _buffer.length; i += 64) {
            var w   = Cryptoops.DataUtils.bytesToWords(_buffer.slice(i, i+64)),
                a   = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], _h = h[7];
    
            for (var t = 0; t < 64; t++) {
                if (t >= 16) {
                    w[t]    = (
                                (((w[t-2] >>> 17) | (w[t-2] << 15)) ^ ((w[t-2] >>> 19) | (w[t-2] << 13)) ^ (w[t-2] >>> 10))
                                + w[t-7]
                                + (((w[t-15] >>> 7) | (w[t-15] << 25)) ^ ((w[t-15] >>> 18) | (w[t-15] << 14)) ^ (w[t-15] >>> 3))
                                + w[t-16]
                              );
                }
                var temp1   = (
                                _h
                                + (((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)))
                                + ((e & f) ^ (~e & g))
                                + k[t]
                                + w[t]
                              ) % Math.pow(2,32);
    
                var temp2   = (
                                ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)))
                                + ((a & b) ^ (a & c) ^ (b & c)
                              );
    
                var _h = g, g = f, f = e, e = d + temp1, d = c, c = b, b = a, a = temp1 + temp2;
            }
    
            h[0] += a;
            h[1] += b;
            h[2] += c;
            h[3] += d;
            h[4] += e;
            h[5] += f;
            h[6] += g;
            h[7] += _h;
    
            _processedLength += 512;
        }
    
        this.buffer = _buffer.slice(i);
        this.processedLength = _processedLength;
    
        if (options.stream == false)
            return this.finalize(options);
    
        return this;
    }
    
    /**
     *  @class
     *  @classdesc      Implements the SHA-1 secure hash algorithm specified in FIPS 180-4 ({@link http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf})
     *  @extends        Cryptoops.Hasher
     *  @desc           Creates a new SHA-1 instance.
     *  @requires       Cryptoops.DataUtils
     *  @return         {Cryptoops.SHA1}
     */
    Cryptoops.SHA1 = function() {
        this.clear();
        return this;
    }
    Cryptoops.SHA1.prototype = Object.create(Cryptoops.Hasher.prototype);
    
    /**
     *  The block size in bits for the hashing operation
     *  @private
     *  @type {number}
     */
    Cryptoops.SHA1.prototype.blockSize = 512;
    
    /**
     *  The length in bits of the output block
     *  @private
     *  @type {number}
     */
    Cryptoops.SHA1.prototype.outlen = 160;
    
    /**
     *  Initialize class values, invoking {@link Cryptoops.Hasher#clear} before clearing values specific to {@link Cryptoops.SHA1}.
     *  This is called internally at the end of {@link Cryptoops.SHA1#finalize}, and can be called explicitly when
     *  using progressive hashing.
     *  @override
     */
    Cryptoops.SHA1.prototype.clear = function() {
        this.resetState();
        this.h = [
            0x67452301,
            0xEFCDAB89,
            0x98BADCFE,
            0x10325476,
            0xC3D2E1F0
        ];
    }
    
    /**
     *  Hashes data into the SHA1 class instance.
     *  
     *  @param {string|Array} data                      String or array of 8-bit integer bytes to hash
     *  @param {Object} [options]                       Optional options object
     *  @param {boolean} [options.stream=false]         Whether to use streaming (progressive hashing) mode. In streaming
     *                                                  mode, you can repeatedly hash data into the SHA1 object.
     *                                                  The hash will not be finalized and returned until you call
     *                                                  {@link SHA1#finalize}. Streaming mode is useful when you have to
     *                                                  hash a huge amount of data and you don't want to store all
     *                                                  of it in memory at one time.
     *  @param {string} [options.returnFormat='hex']    (bytes|hex|words) The return format. Default: hex
     *
     *  @return {string|Array|Cryptoops.SHA1}          Desired output format if streaming mode is turned off. Otherwise this instance (chainable)
     */
    Cryptoops.SHA1.prototype.hash = function(data, options) {
        if (!options) options = {};
        if (!options.stream) options.stream = false;
    
        if (!Array.isArray(data))
            data = Cryptoops.DataUtils.stringToBytes(data);
    
        var _buffer = [],
            realBuffer = this.buffer;
    
        for (var i=0; i<realBuffer.length; i++)
            _buffer.push(realBuffer[i]);
    
        for (var i=0; i<data.length; i++)
            _buffer.push(data[i]);
    
        var _processedLength = this.processedLength,
            h = this.h;
    
        for (var i=0; (i+64) <= _buffer.length; i += 64) {
            var a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], w = Cryptoops.DataUtils.bytesToWords(_buffer.slice(i, i+64));
    
            for (var t = 0; t < 80; t++)
            {
                if (t >= 16)
                {
                    var _wt = w[t-3] ^ w[t-8] ^ w[t-14] ^ w[t-16];
                    w[t] = (_wt << 1) | (_wt >>> 31);
                }
    
                var temp = ((a << 5) | (a >>> 27)) + e + w[t];
                if (t < 20)
                    temp += ((b & c) | (~b & d)) + 0x5A827999;
                else if (t < 40)
                    temp += (b ^ c ^ d) + 0x6ed9eba1;
                else if (t < 60)
                    temp += ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC;
                else
                    temp += (b ^ c ^ d) + 0xCA62C1D6;
    
                var e = d, d = c, c = (b << 30) | (b >>> 2), b = a, a = temp;
            }
    
            h[0] += a;
            h[1] += b;
            h[2] += c;
            h[3] += d;
            h[4] += e;
    
            _processedLength += 512;
        }
    
        this.buffer = _buffer.slice(i);
        this.processedLength = _processedLength;
    
        if (options.stream == false)
            return this.finalize(options);
    
        return this;
    }
    
    /**
     *  AES Symmetric Encryption Class
     *
     *  @class
     *  @classdesc              Implements the AES symmetric encryption algorithm (specified in FIPS 197 [{@link http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf}])
     *  @extends                Cryptoops.BlockCipher
     *  @requires               Cryptoops.DataUtils
     *
     *  @desc                   Creates a new Cryptoops.AES instance
     *  @param {Object} options Initialization options for the class, passed automatically into {@link Cryptoops.AES#initialize}
     */
    Cryptoops.AES = function(options) {
        this.initialize(options);
    
        if (this.key.length)
            this.setKey(this.key);
    
        return this;
    }
    Cryptoops.AES.prototype = Object.create(Cryptoops.BlockCipher.prototype);
    
    /**
     *  Internal state for the key (not-expanded). Can be 16, 24 or 32 bytes.
     *  @private
     */
    Cryptoops.AES.prototype.key = [];
    
    /**
     *  Internal state 32-bit integer word array for the expanded key
     *  @private
     */
    Cryptoops.AES.prototype.keyExpanded = [];
    
    /**
     *  Internal state for the key length in bits. Can be overridden by {@link Cryptoops.AES#setKey}
     *  @private
     */
    Cryptoops.AES.prototype.keyLength = 256; // 128 | 192 | 256 (can be overridden by setKey)
    
    /**
     *  Internal constant for the block size in bits.
     *  @private
     */
    Cryptoops.AES.prototype.blockSize = 128; // 128 bits
    
    /**
     *  Controls whether to log debug output to the console.
     *  @override
     *  @type {Boolean}
     *  @default false
     *  @see {@link Cryptoops.BlockCipher#debugMode}
     */
    Cryptoops.AES.prototype.debugMode = false;
    
    /**
     *  Set the key, either from a string or a Uint8Array of byte values
     *
     *  @throws                     Will throw an error if the key is not 128, 192, or 256 bits
     *
     *  @param {string|Array} key   The symmetric key, either a string or array of 8-bit integer bytes.
     *  @return {AES}               This AES instance (chainable)
     */
    Cryptoops.AES.prototype.setKey = function(key) {
        if (!Array.isArray(key))
            key = Cryptoops.DataUtils.stringToBytes(key);
    
        if (key.length != 16 && key.length != 24 && key.length != 32)
            throw new Error('Cryptoops.AES: key must be 16, 24, or 32 bytes!');
    
        this.key = key;
        this.keyExpanded = [];
        this.keyLength = key.length * 8;
    
        return this;
    }
    
    /**
     *  Getter function for the key
     *  @return {Array} The byte array for the key
     */
    Cryptoops.AES.prototype.getKey = function() { return this.key; }
    
    /**
     *  Getter function for the key length
     *  @return {Number} The key length (in bits)
     */
    Cryptoops.AES.prototype.getKeyLength = function() { return this.keyLength; }
    
    /**
     *  Getter function for the block size
     *  @return {Number} The block size (in bits)
     */
    Cryptoops.AES.prototype.getBlockSize = function() { return this.blockSize; }
    
    /**
     *  Encrypts a block. This is normally called internally by a subclass instance of {@link Cryptoops.BlockCipherMode}.
     *
     *  @private
     *  @param {Array} state    An array of 32-bit integer words
     *  @return {Array}         An array of encrypted 32-bit integer words
     */
    Cryptoops.AES.prototype.blockEncrypt = function(state) {
        this.debugWrite('Encrypting...');
    
        var k = this.getExpandedKey();
        state = this.addRoundKey(state, k.slice(0, 4));
    
        for (var i=0; i<(this.key.length / 4) + 5; i++) {
            state = this.addRoundKey(this.mixColumns(this.shiftRows(this.bytesSub(state))), k.slice((i+1)*4, ((i+1)*4)+4));
        }
    
        state = this.addRoundKey(this.shiftRows(this.bytesSub(state)), k.slice((i+1)*4, ((i+1)*4)+4));
    
        return state;
    }
    
    /**
     *  Decrypts a block. This is normally called internally by a subclass instance of {@link Cryptoops.BlockCipherMode}.
     *
     *  @private
     *  @param {Array} state    An array of 32-bit integer words
     *  @return {Array}         An array of decrypted 32-bit integer words
     */
    Cryptoops.AES.prototype.blockDecrypt = function(state) {
        this.debugWrite('Decrypting...');
    
        var k  = this.getExpandedKey(),
            ki = k.length;
    
        state = this.addRoundKey(state, k.slice(ki-4, ki));
    
        for (var i = (this.key.length / 4) + 5, ki = ki - 4; i>0; i--, ki -= 4) {
            state = this.inverseMixColumns(this.addRoundKey(this.inverseBytesSub(this.inverseShiftRows(state)), k.slice(ki-4, ki)));
        }
    
        state = this.addRoundKey(this.inverseBytesSub(this.inverseShiftRows(state)), k.slice(0, 4));
    
        return state;
    }
    
    /**
     *  Adds the stupid round key
     *  @private
     *  @param {Array} state        32-bit integer word array for the current state
     *  @param {Array} round_key    32-bit integer word array for the round key
     *  @return {Array}             32-bit integer word array of XOR'ed result
     */
    Cryptoops.AES.prototype.addRoundKey = function(state, roundKey) {
        return [
            state[0] ^ roundKey[0],
            state[1] ^ roundKey[1],
            state[2] ^ roundKey[2],
            state[3] ^ roundKey[3]
        ];
    }
    
    /**
     *  This simulates Galois Field multiplication
     *  It does matrix multiplication with a lookup table
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.mixColumns = function(state) {
        var m2 = this.MULT_2, m3 = this.MULT_3, out = [];
    
        for (var i = 0; i < 4; i++) {
            var a0 = (state[i] >>> 24) & 255;
            var a1 = (state[i] >>> 16) & 255;
            var a2 = (state[i] >>> 8) & 255;
            var a3 = (state[i] & 255);
    
            out[i]  = (( m2[a0] ^ m3[a1] ^ a2     ^ a3)     << 24)
                    | (( a0     ^ m2[a1] ^ m3[a2] ^ a3)     << 16)
                    | (( a0     ^ a1     ^ m2[a2] ^ m3[a3]) << 8)
                    | (( m3[a0] ^ a1     ^ a2     ^ m2[a3]) << 0)
        }
        return out;
    }
    
    /**
     *  This is the inverse of mix_columns
     *  It uses a lookup table for the inverted matrix used previously
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.inverseMixColumns = function(state) {
        var m9 = this.MULT_9, mb = this.MULT_11, md = this.MULT_13, me = this.MULT_14, out = [];
    
        for (var i = 0; i < 4; i++) {
            var a0 = (state[i] >>> 24) & 255;
            var a1 = (state[i] >>> 16) & 255;
            var a2 = (state[i] >>> 8) & 255;
            var a3 = (state[i] & 255);
    
            out[i]  = (( me[a0] ^ mb[a1] ^ md[a2] ^ m9[a3]) << 24)
                    | (( m9[a0] ^ me[a1] ^ mb[a2] ^ md[a3]) << 16)
                    | (( md[a0] ^ m9[a1] ^ me[a2] ^ mb[a3]) << 8)
                    | (( mb[a0] ^ md[a1] ^ m9[a2] ^ me[a3]) << 0)
        }
        return out;
    }
    
    /**
     *  Shuffles bytes between the 4 32-bit integers that make up the state
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.shiftRows = function(state) {
        return [
            (state[3] & 255) | (((state[2] >>> 8) & 255) << 8) | (((state[1] >>> 16) & 255) << 16) | (((state[0] >>> 24) & 255) << 24),
            (state[0] & 255) | (((state[3] >>> 8) & 255) << 8) | (((state[2] >>> 16) & 255) << 16) | (((state[1] >>> 24) & 255) << 24),
            (state[1] & 255) | (((state[0] >>> 8) & 255) << 8) | (((state[3] >>> 16) & 255) << 16) | (((state[2] >>> 24) & 255) << 24),
            (state[2] & 255) | (((state[1] >>> 8) & 255) << 8) | (((state[0] >>> 16) & 255) << 16) | (((state[3] >>> 24) & 255) << 24)
        ];
    }
    
    /**
     *  (Inverse of shift_rows)
     *  Unshuffles bytes between the 4 32-bit integers that make up the state
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.inverseShiftRows = function(state) {
        return [
            (state[1] & 255) | (((state[2] >>> 8) & 255) << 8) | (((state[3] >>> 16) & 255) << 16) | (((state[0] >>> 24) & 255) << 24),
            (state[2] & 255) | (((state[3] >>> 8) & 255) << 8) | (((state[0] >>> 16) & 255) << 16) | (((state[1] >>> 24) & 255) << 24),
            (state[3] & 255) | (((state[0] >>> 8) & 255) << 8) | (((state[1] >>> 16) & 255) << 16) | (((state[2] >>> 24) & 255) << 24),
            (state[0] & 255) | (((state[1] >>> 8) & 255) << 8) | (((state[2] >>> 16) & 255) << 16) | (((state[3] >>> 24) & 255) << 24)
        ];
    }
    
    /**
     *  Does an s-box lookup replacement on each byte of the state
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.bytesSub = function(state) {
        var s = this.S_E;
    
        return [
            (s[state[0] >>> 24] << 24) | (s[(state[0] >>> 16) & 255] << 16) | (s[(state[0] >>> 8) & 255] << 8) | s[state[0] & 255],
            (s[state[1] >>> 24] << 24) | (s[(state[1] >>> 16) & 255] << 16) | (s[(state[1] >>> 8) & 255] << 8) | s[state[1] & 255],
            (s[state[2] >>> 24] << 24) | (s[(state[2] >>> 16) & 255] << 16) | (s[(state[2] >>> 8) & 255] << 8) | s[state[2] & 255],
            (s[state[3] >>> 24] << 24) | (s[(state[3] >>> 16) & 255] << 16) | (s[(state[3] >>> 8) & 255] << 8) | s[state[3] & 255]
        ];
    }
    
    /**
     *  Does an inverse s-box lookup replacement on each byte of the state for decryption
     *  @private
     *  @param {Array} state    32-bit integer word array for the current state
     *  @return {Array}         32-bit integer word array
     */
    Cryptoops.AES.prototype.inverseBytesSub = function(state) {
        var s = this.S_D;
    
        return [
            (s[state[0] >>> 24] << 24) | (s[(state[0] >>> 16) & 255] << 16) | (s[(state[0] >>> 8) & 255] << 8) | s[state[0] & 255],
            (s[state[1] >>> 24] << 24) | (s[(state[1] >>> 16) & 255] << 16) | (s[(state[1] >>> 8) & 255] << 8) | s[state[1] & 255],
            (s[state[2] >>> 24] << 24) | (s[(state[2] >>> 16) & 255] << 16) | (s[(state[2] >>> 8) & 255] << 8) | s[state[2] & 255],
            (s[state[3] >>> 24] << 24) | (s[(state[3] >>> 16) & 255] << 16) | (s[(state[3] >>> 8) & 255] << 8) | s[state[3] & 255]
        ];
    }
    
    /**
     *  Performs the stupid key expansion routine
     *  @private
     *  @throws Throws an error if key is missing or invalid
     *  @return {Array} The 32-bit integer word array for the expanded key
     */
    Cryptoops.AES.prototype.getExpandedKey = function() {
        if (this.keyExpanded.length)
            return this.keyExpanded;
    
        if (this.key.length != 32 && this.key.length != 24 && this.key.length != 16)
            throw new Error('Cryptoops.AES: missing or invalid key!');
    
        this.debugWrite('Generating key schedule...');
    
        var w   = [],
            k   = this.key,
            n_k = (k.length / 4),
            n_r = n_k + 6,
            n_b = 4;
    
        for (var i=0; i<n_k; i++) {
            w[i] = Cryptoops.DataUtils.bytesToWord(k[4*i], k[4*i+1], k[4*i+2], k[4*i+3]);
        }
    
        // It was nice of the FIPS-197 spec to give pseudo code that actually works
        for (i = i; i < n_b * (n_r + 1); i++) {
            var temp = w[i-1];
    
            if (i % n_k == 0)
                temp = this.wordBytesSub(this.rotWord(temp)) ^ this.RCON[(i/n_k)-1];
            else if (n_k > 6 && i % n_k == 4)
                temp = this.wordBytesSub(temp);
    
            w[i] = w[i-n_k] ^ temp;
        }
        this.keyExpanded = w;
    
        return w;
    }
    
    /**
     *  Does an s-box lookup replacement on each byte of a word
     *  @private
     *  @param {Number} word    32-bit word
     *  @return {Number}        S-box'ed word
     */
    Cryptoops.AES.prototype.wordBytesSub = function(word) {
        var s = this.S_E;
        return (s[word >>> 24] << 24) | (s[(word >>> 16) & 255] << 16) | (s[(word >>> 8) & 255] << 8) | s[word & 255];
    }
    
    /**
     *  rot_word function used in key generation (does a circular left shift on a 32-bit word)
     *  @private
     *  @param {Number} word    32-bit word
     *  @return {Number}        Rotated word
     */
    Cryptoops.AES.prototype.rotWord = function(word) {
        return (word << 8) | (word >>> 24);
    }
    
    /**
     *  debug.writes a pretty formatted table containing the state values
     *  only outputs anything if this.debug_mode is true.
     *
     *  @private
     *  @see                        {@link Cryptoops.BlockCipher#debugMode}
     *  @param {Array} state        The 32-bit integer word state array
     */
    Cryptoops.AES.prototype.debugDumpState = function(state) {
        var hex = function(num) {
            var str = (num).toString(16)
            if (str.length != 2)
                str = '0' + str;
            return str;
        };
        var pad32 = function(num) {
            var str = (num >>> 0).toString(16);
            for (i=0; str.length % 8 != 0; i++)
                var str = '0' + str;
            return str;
        };
        var s = state;
        this.debugWrite('----\n'
            +   pad32(s[0]) + pad32(s[1]) + pad32(s[2]) + pad32(s[3]) + '\n'
            +   hex((s[0]>>>24) & 255)+' '+hex((s[1]>>>24)&255)+' '+hex((s[2]>>>24)&255)+' '+hex((s[3]>>>24)&255)+'\n'
            +   hex((s[0]>>>16) & 255)+' '+hex((s[1]>>>16)&255)+' '+hex((s[2]>>>16)&255)+' '+hex((s[3]>>>16)&255)+'\n'
            +   hex((s[0]>>>8) & 255) +' '+hex((s[1]>>>8)&255) +' '+hex((s[2]>>>8)&255) +' '+hex((s[3]>>>8)&255)+'\n'
            +   hex((s[0])&255)       +' '+hex((s[1])&255)     +' '+hex((s[2])&255)     +' '+hex((s[3])&255)+'\n----'
            );
    }
    
    /**
     *  RCON array used in Key Generation
     *  @private
     */
    Cryptoops.AES.prototype.RCON = [
        0x01000000,
        0x02000000,
        0x04000000,
        0x08000000,
        0x10000000,
        0x20000000,
        0x40000000,
        0x80000000,
        0x1B000000,
        0x36000000,
        0x6C000000,
        0xD8000000,
        0xAB000000,
        0x4D000000,
        0x9A000000
    ];
    
    /**
     *  s-box for encryption
     *  @private
     */
    Cryptoops.AES.prototype.S_E = [
        0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
        0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
        0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
        0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
        0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
        0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
        0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
        0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
        0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
        0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
        0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
        0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
        0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
        0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
        0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
        0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
    ];
    
    /**
     *  s-box for decryption
     *  @private
     */
    Cryptoops.AES.prototype.S_D = [
        0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
        0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
        0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
        0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
        0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
        0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
        0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
        0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
        0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
        0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
        0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
        0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
        0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
        0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
        0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
        0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 2
     *  @private
     */
    Cryptoops.AES.prototype.MULT_2 = [
        0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 
        0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 
        0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 
        0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 
        0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 
        0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 
        0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 
        0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 
        0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 
        0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 
        0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 
        0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 
        0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 
        0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 
        0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 
        0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 3
     *  @private
     */
    Cryptoops.AES.prototype.MULT_3 = [
        0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, 
        0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 
        0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 
        0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, 
        0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 
        0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, 
        0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, 
        0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 
        0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, 
        0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, 
        0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 
        0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, 
        0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, 
        0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 
        0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, 
        0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 9
     *  @private
     */
    Cryptoops.AES.prototype.MULT_9 = [
        0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, 
        0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 
        0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 
        0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, 
        0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 
        0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, 
        0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, 
        0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 
        0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, 
        0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, 
        0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 
        0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, 
        0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, 
        0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 
        0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, 
        0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 11
     *  @private
     */
    Cryptoops.AES.prototype.MULT_11 = [
        0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, 
        0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 
        0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, 
        0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, 
        0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 
        0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, 
        0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, 
        0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 
        0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, 
        0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, 
        0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 
        0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, 
        0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, 
        0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 
        0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, 
        0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 13
     *  @private
     */
    Cryptoops.AES.prototype.MULT_13 = [
        0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, 
        0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 
        0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, 
        0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, 
        0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 
        0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, 
        0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, 
        0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 
        0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, 
        0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, 
        0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 
        0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, 
        0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, 
        0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 
        0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, 
        0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97
    ];
    
    /**
     *  precomputed matrix for Galois field multiplication by 14
     *  @private
     */
    Cryptoops.AES.prototype.MULT_14 = [
        0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, 
        0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 
        0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, 
        0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, 
        0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 
        0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, 
        0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, 
        0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 
        0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, 
        0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, 
        0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 
        0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, 
        0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, 
        0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 
        0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, 
        0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d
    ];
    
    /**
     *  Sanity test for the class.
     *
     *  NOTE: You must initialize the instance with data.blockMode set to {@link Cryptoops.ECB}
     *  and data.padMode set to {@link Cryptoops.ZeroPadding} for this to work!
     *  See {@link Cryptoops.BlockCipher#initialize} for more information on initialization properties.
     *
     *  @return {boolean}
     */
    Cryptoops.AES.prototype.test = function() {
        var _doTest = function(key, plaintext, expected) {
            var keyBin     = Cryptoops.DataUtils.hexToBytes(key),
                plainBin   = Cryptoops.DataUtils.hexToBytes(plaintext);
    
            this.setKey(keyBin);
    
            this.debugWrite('------------------------------------------');
            this.debugWrite('Test key: [binary] (length: '+keyBin.length+' bytes)');
            this.debugWrite('Test key (hex): '+key);
            this.debugWrite('Plaintext: [binary] (length: '+plainBin.length+' bytes)');
            this.debugWrite('Plaintext (hex): '+plaintext);
            this.debugWrite('Expecting ciphertext (hex): '+expected);
    
            var ciphertext  = this.encrypt(plainBin),
                hex         = Cryptoops.DataUtils.bytesToHex(ciphertext);
    
            this.debugWrite('Ciphertext: [binary] (length: '+ciphertext.length+' bytes)');
            this.debugWrite('Ciphertext (hex): '+hex);
    
            if (hex != expected)
                throw new Error('TEST FAILED: Invalid ciphertext! Expected: '+expected+', Got: ' + hex);
    
            this.debugWrite('GOT EXPECTED CIPHERTEXT!');
    
            var plaintext2  = this.decrypt(ciphertext),
                hex2        = Cryptoops.DataUtils.bytesToHex(plaintext2);
    
            this.debugWrite('Decrypted ciphertext: [binary] (length: '+plaintext2.length+' bytes)');
            this.debugWrite('Decrypted ciphertext (hex): '+hex2);
    
            if (hex2 != plaintext)
                throw new Error('TEST FAILED: Invalid decrypted ciphertext! Expected: '+plaintext+', Got: ' + hex2);
    
            this.debugWrite('SUCCESSFULLY DECRYPTED CIPHERTEXT!');
            this.debugWrite('------------------------------------------');
    
            return true;
        }.bind(this);
    
        this.debugWrite('AES-256 TEST');
        _doTest(
            '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', 
            '00112233445566778899aabbccddeeff',
            '8ea2b7ca516745bfeafc49904b496089'
        );
    
        this.debugWrite('AES-192 TEST');
        _doTest(
            '000102030405060708090a0b0c0d0e0f1011121314151617', 
            '00112233445566778899aabbccddeeff',
            'dda97ca4864cdfe06eaf70a0ec0d7191'
        );      
    
        this.debugWrite('AES-128 TEST');
        _doTest(
            '000102030405060708090a0b0c0d0e0f', 
            '00112233445566778899aabbccddeeff',
            '69c4e0d86a7b0430d8cdb78070b4c55a'
        );
    
        return true;
    }
    
    /**
     *  Run a battery of tests on the various algorithms.
     *
     *  @example
     *
     *  new Cryptoops().sanityTest();  // check the console.
     */
    Cryptoops.prototype.sanityTest = function() {
        var _aes = new Cryptoops.AES({blockMode: Cryptoops.ECB, padMode: Cryptoops.ZeroPadding});
        _aes.debugMode = true;
        _aes.test();
    
        console.log('SHA256 test: ');
        var data = "This is my data to hash.";
        var hash = new Cryptoops.SHA256().hash(data);
        console.log('SHA256 hash: ', hash);
    
        console.log('------------------------------------------');
        console.log('PBKDF2 test: ');
    
        var passphrase = 'passphrase',
            salt = 'salt',
            iterations = 10000,
            byteLength = 32;
    
        var start   = new Date().getTime(),
            rawKey  = new Cryptoops.PBKDF2({
                        hasher: Cryptoops.SHA256,
                        keySize: byteLength,
                        iterations: iterations,
                        returnFormat: 'bytes'
                    }).compute(passphrase, salt),
            key     = Cryptoops.DataUtils.bytesToHex(rawKey),
            end     = new Date().getTime();
    
        console.log('PBKDF2 key: ', key, rawKey, '; time in ms: ', (end - start));
    
        console.log('------------------------------------------');
        console.log('AES granular test: ');
    
        var key = Cryptoops.DataUtils.hexToBytes('0123456712345678234567893456789a0123456712345678234567893456789a'),
            iv = Cryptoops.DataUtils.hexToBytes('9876543210fedcba9876543210fedcba'),
            blockMode = Cryptoops.CBC,
            plaintext = 'Lorem ipsum.';
    
        console.log('encrypting plaintext: ', plaintext);
        console.log('plaintext bytes: ', Cryptoops.DataUtils.stringToBytes(plaintext));
    
        var start   = new Date().getTime();
        var _aes = new Cryptoops.AES({
            passphrase: 'cheers fuckface',
            passphrasePBKDF2Iterations: 10101,
            blockMode: blockMode
        });
        var salt = _aes.getPassphraseSalt();
        console.log('passphraseSalt: ', salt);
        console.log('key : ', _aes.key);
        console.log('iv  : ', _aes.iv);
        var ciphertext = _aes.encrypt(plaintext);
        var end     = new Date().getTime();
    
        console.log('raw ciphertext: ', ciphertext, '; time in ms: ', (end - start));
    
        console.log('decrypting ciphertext...');
    
        var _decAes = new Cryptoops.AES({
            passphrase: 'cheers fuckface',
            passphrasePBKDF2Iterations: 10101,
            passphraseSalt: salt,
            blockMode: blockMode
        });
        var plaintext = _decAes.decrypt(ciphertext);
    
        console.log('raw plaintext: ', plaintext);
    
        ciphertext = Cryptoops.DataUtils.bytesToHex(ciphertext);
        console.log('converted ciphertext: ', ciphertext);
    
        plaintext = Cryptoops.DataUtils.bytesToString(plaintext);
        console.log('converted plaintext: ', plaintext);
    
        return this.nativeSanityTestComparison(salt);
    }
    
    /**
     *  Shows how pointless all of this is by comparing to native crypto functions.
     *  Called automatically by {@link Cryptoops#sanityTest}
     */
    Cryptoops.prototype.nativeSanityTestComparison = async function(existingSalt) {
        console.log('------------------------------------------');
        console.log('Native PBKDF2 test: ');
    
        var textEncoder = new TextEncoder('utf-8');
        var passwordBytes = textEncoder.encode('passphrase');
        var saltBytes = textEncoder.encode('salt');
        console.log('passwordBytes: ', passwordBytes, '; saltBytes: ', saltBytes);
        var start   = new Date().getTime();
        var importedKey = await window.crypto.subtle.importKey(
            'raw',
            passwordBytes,
            'PBKDF2',
            false,
            ['deriveBits']
        );
        var key = await window.crypto.subtle.deriveBits({
            'name': 'PBKDF2',
            'salt': saltBytes,
            'iterations': 10000,
            'hash': 'SHA-256'
        }, importedKey, 32 * 8);
        var end     = new Date().getTime();
        console.log('pbkdf2 native: ', new Uint8Array(key), '; time in ms: ', (end - start));
    
        console.log('------------------------------------------');
        console.log('Native AES granular test: ');
    
        var textEncoder = new TextEncoder('utf-8');
        var passwordBytes = textEncoder.encode('cheers fuckface');
        var plaintext = textEncoder.encode('Lorem ipsum.');
        var saltBytes = Uint8Array.from(existingSalt);
        console.log('passwordBytes: ', passwordBytes, '; saltBytes: ', saltBytes);
        var start   = new Date().getTime();
        var importedKey = await window.crypto.subtle.importKey(
            'raw',
            passwordBytes,
            'PBKDF2',
            false,
            ['deriveBits']
        );
        var keyIV = await window.crypto.subtle.deriveBits({
            'name': 'PBKDF2',
            'salt': saltBytes,
            'iterations': 10101,
            'hash': 'SHA-384'
        }, importedKey, 384);
    
        var key = keyIV.slice(0, 32);
        var iv = keyIV.slice(32);
        console.log('key: ', new Uint8Array(key));
        console.log('iv: ', new Uint8Array(iv));
        var symKey = await window.crypto.subtle.importKey(
            'raw',
            key,
            'AES-CBC',
            false,
            ['encrypt']
        );
    
        var encrypted = await window.crypto.subtle.encrypt({
            name: 'AES-CBC',
            iv: iv
        }, symKey, plaintext);
        var end     = new Date().getTime();
    
        console.log('encrypted: ', new Uint8Array(encrypted), '; time in ms: ', (end - start));
    }

    Put that in your pipe and smoke it.

    Posted 2019-11-02 19:24:00 CST by henriquez.

    Comments