/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define('pdfjs/core/crypto', ['exports', 'pdfjs/shared/util',
            'pdfjs/core/primitives', 'pdfjs/core/stream'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('../shared/util.js'), require('./primitives.js'),
            require('./stream.js'));
    } else {
        factory((root.pdfjsCoreCrypto = {}), root.pdfjsSharedUtil,
            root.pdfjsCorePrimitives, root.pdfjsCoreStream);
    }
}(this, function(exports, sharedUtil, corePrimitives, coreStream) {

    var PasswordException = sharedUtil.PasswordException;
    var PasswordResponses = sharedUtil.PasswordResponses;
    var bytesToString = sharedUtil.bytesToString;
    var error = sharedUtil.error;
    var isInt = sharedUtil.isInt;
    var stringToBytes = sharedUtil.stringToBytes;
    var utf8StringToString = sharedUtil.utf8StringToString;
    var warn = sharedUtil.warn;
    var Name = corePrimitives.Name;
    var isName = corePrimitives.isName;
    var isDict = corePrimitives.isDict;
    var DecryptStream = coreStream.DecryptStream;

    var ARCFourCipher = (function ARCFourCipherClosure() {
        function ARCFourCipher(key) {
            this.a = 0;
            this.b = 0;
            var s = new Uint8Array(256);
            var i, j = 0, tmp, keyLength = key.length;
            for (i = 0; i < 256; ++i) {
                s[i] = i;
            }
            for (i = 0; i < 256; ++i) {
                tmp = s[i];
                j = (j + tmp + key[i % keyLength]) & 0xFF;
                s[i] = s[j];
                s[j] = tmp;
            }
            this.s = s;
        }

        ARCFourCipher.prototype = {
            encryptBlock : function ARCFourCipher_encryptBlock(data) {
                var i, n = data.length, tmp, tmp2;
                var a = this.a, b = this.b, s = this.s;
                var output = new Uint8Array(n);
                for (i = 0; i < n; ++i) {
                    a = (a + 1) & 0xFF;
                    tmp = s[a];
                    b = (b + tmp) & 0xFF;
                    tmp2 = s[b];
                    s[a] = tmp2;
                    s[b] = tmp;
                    output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF];
                }
                this.a = a;
                this.b = b;
                return output;
            }
        };
        ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;

        return ARCFourCipher;
    })();

    var calculateMD5 = (function calculateMD5Closure() {
        var r = new Uint8Array([
            7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
            5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
            4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
            6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);

        var k = new Int32Array([
            -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
            -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
            1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
            643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
            568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
            1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
            -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
            -722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
            -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
            -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
            -145523070, -1120210379, 718787259, -343485551]);

        function hash(data, offset, length) {
            var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
            // pre-processing
            var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
            var padded = new Uint8Array(paddedLength);
            var i, j, n;
            for (i = 0; i < length; ++i) {
                padded[i] = data[offset++];
            }
            padded[i++] = 0x80;
            n = paddedLength - 8;
            while (i < n) {
                padded[i++] = 0;
            }
            padded[i++] = (length << 3) & 0xFF;
            padded[i++] = (length >> 5) & 0xFF;
            padded[i++] = (length >> 13) & 0xFF;
            padded[i++] = (length >> 21) & 0xFF;
            padded[i++] = (length >>> 29) & 0xFF;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            var w = new Int32Array(16);
            for (i = 0; i < paddedLength;) {
                for (j = 0; j < 16; ++j, i += 4) {
                    w[j] = (padded[i] | (padded[i + 1] << 8) |
                        (padded[i + 2] << 16) | (padded[i + 3] << 24));
                }
                var a = h0, b = h1, c = h2, d = h3, f, g;
                for (j = 0; j < 64; ++j) {
                    if (j < 16) {
                        f = (b & c) | ((~b) & d);
                        g = j;
                    } else if (j < 32) {
                        f = (d & b) | ((~d) & c);
                        g = (5 * j + 1) & 15;
                    } else if (j < 48) {
                        f = b ^ c ^ d;
                        g = (3 * j + 5) & 15;
                    } else {
                        f = c ^ (b | (~d));
                        g = (7 * j) & 15;
                    }
                    var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
                    d = c;
                    c = b;
                    b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
                    a = tmp;
                }
                h0 = (h0 + a) | 0;
                h1 = (h1 + b) | 0;
                h2 = (h2 + c) | 0;
                h3 = (h3 + d) | 0;
            }
            return new Uint8Array([
                h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
                h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
                h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
                h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
            ]);
        }

        return hash;
    })();
    var Word64 = (function Word64Closure() {
        function Word64(highInteger, lowInteger) {
            this.high = highInteger | 0;
            this.low = lowInteger | 0;
        }

        Word64.prototype = {
            and : function Word64_and(word) {
                this.high &= word.high;
                this.low &= word.low;
            },
            xor : function Word64_xor(word) {
                this.high ^= word.high;
                this.low ^= word.low;
            },

            or : function Word64_or(word) {
                this.high |= word.high;
                this.low |= word.low;
            },

            shiftRight : function Word64_shiftRight(places) {
                if (places >= 32) {
                    this.low = (this.high >>> (places - 32)) | 0;
                    this.high = 0;
                } else {
                    this.low = (this.low >>> places) | (this.high << (32 - places));
                    this.high = (this.high >>> places) | 0;
                }
            },

            shiftLeft : function Word64_shiftLeft(places) {
                if (places >= 32) {
                    this.high = this.low << (places - 32);
                    this.low = 0;
                } else {
                    this.high = (this.high << places) | (this.low >>> (32 - places));
                    this.low = this.low << places;
                }
            },

            rotateRight : function Word64_rotateRight(places) {
                var low, high;
                if (places & 32) {
                    high = this.low;
                    low = this.high;
                } else {
                    low = this.low;
                    high = this.high;
                }
                places &= 31;
                this.low = (low >>> places) | (high << (32 - places));
                this.high = (high >>> places) | (low << (32 - places));
            },

            not : function Word64_not() {
                this.high = ~this.high;
                this.low = ~this.low;
            },

            add : function Word64_add(word) {
                var lowAdd = (this.low >>> 0) + (word.low >>> 0);
                var highAdd = (this.high >>> 0) + (word.high >>> 0);
                if (lowAdd > 0xFFFFFFFF) {
                    highAdd += 1;
                }
                this.low = lowAdd | 0;
                this.high = highAdd | 0;
            },

            copyTo : function Word64_copyTo(bytes, offset) {
                bytes[offset] = (this.high >>> 24) & 0xFF;
                bytes[offset + 1] = (this.high >> 16) & 0xFF;
                bytes[offset + 2] = (this.high >> 8) & 0xFF;
                bytes[offset + 3] = this.high & 0xFF;
                bytes[offset + 4] = (this.low >>> 24) & 0xFF;
                bytes[offset + 5] = (this.low >> 16) & 0xFF;
                bytes[offset + 6] = (this.low >> 8) & 0xFF;
                bytes[offset + 7] = this.low & 0xFF;
            },

            assign : function Word64_assign(word) {
                this.high = word.high;
                this.low = word.low;
            }
        };
        return Word64;
    })();

    var calculateSHA256 = (function calculateSHA256Closure() {
        function rotr(x, n) {
            return (x >>> n) | (x << 32 - n);
        }

        function ch(x, y, z) {
            return (x & y) ^ (~x & z);
        }

        function maj(x, y, z) {
            return (x & y) ^ (x & z) ^ (y & z);
        }

        function sigma(x) {
            return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
        }

        function sigmaPrime(x) {
            return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
        }

        function littleSigma(x) {
            return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
        }

        function littleSigmaPrime(x) {
            return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
        }

        var 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];

        function hash(data, offset, length) {
            // initial hash values
            var h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372,
                h3                                   = 0xa54ff53a, h4 = 0x510e527f, h5 = 0x9b05688c,
                h6                                   = 0x1f83d9ab, h7 = 0x5be0cd19;
            // pre-processing
            var paddedLength = Math.ceil((length + 9) / 64) * 64;
            var padded = new Uint8Array(paddedLength);
            var i, j, n;
            for (i = 0; i < length; ++i) {
                padded[i] = data[offset++];
            }
            padded[i++] = 0x80;
            n = paddedLength - 8;
            while (i < n) {
                padded[i++] = 0;
            }
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = (length >>> 29) & 0xFF;
            padded[i++] = (length >> 21) & 0xFF;
            padded[i++] = (length >> 13) & 0xFF;
            padded[i++] = (length >> 5) & 0xFF;
            padded[i++] = (length << 3) & 0xFF;
            var w = new Uint32Array(64);
            // for each 512 bit block
            for (i = 0; i < paddedLength;) {
                for (j = 0; j < 16; ++j) {
                    w[j] = (padded[i] << 24 | (padded[i + 1] << 16) |
                        (padded[i + 2] << 8) | (padded[i + 3]));
                    i += 4;
                }

                for (j = 16; j < 64; ++j) {
                    w[j] = littleSigmaPrime(w[j - 2]) + w[j - 7] +
                        littleSigma(w[j - 15]) + w[j - 16] | 0;
                }
                var a = h0, b = h1, c = h2, d = h3, e = h4,
                    f                                 = h5, g = h6, h = h7, t1, t2;
                for (j = 0; j < 64; ++j) {
                    t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j];
                    t2 = sigma(a) + maj(a, b, c);
                    h = g;
                    g = f;
                    f = e;
                    e = (d + t1) | 0;
                    d = c;
                    c = b;
                    b = a;
                    a = (t1 + t2) | 0;
                }
                h0 = (h0 + a) | 0;
                h1 = (h1 + b) | 0;
                h2 = (h2 + c) | 0;
                h3 = (h3 + d) | 0;
                h4 = (h4 + e) | 0;
                h5 = (h5 + f) | 0;
                h6 = (h6 + g) | 0;
                h7 = (h7 + h) | 0;
            }
            return new Uint8Array([
                (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, (h0) & 0xFF,
                (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, (h1) & 0xFF,
                (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, (h2) & 0xFF,
                (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, (h3) & 0xFF,
                (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, (h4) & 0xFF,
                (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, (h5) & 0xFF,
                (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, (h6) & 0xFF,
                (h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, (h7) & 0xFF
            ]);
        }

        return hash;
    })();

    var calculateSHA512 = (function calculateSHA512Closure() {
        function ch(result, x, y, z, tmp) {
            result.assign(x);
            result.and(y);
            tmp.assign(x);
            tmp.not();
            tmp.and(z);
            result.xor(tmp);
        }

        function maj(result, x, y, z, tmp) {
            result.assign(x);
            result.and(y);
            tmp.assign(x);
            tmp.and(z);
            result.xor(tmp);
            tmp.assign(y);
            tmp.and(z);
            result.xor(tmp);
        }

        function sigma(result, x, tmp) {
            result.assign(x);
            result.rotateRight(28);
            tmp.assign(x);
            tmp.rotateRight(34);
            result.xor(tmp);
            tmp.assign(x);
            tmp.rotateRight(39);
            result.xor(tmp);
        }

        function sigmaPrime(result, x, tmp) {
            result.assign(x);
            result.rotateRight(14);
            tmp.assign(x);
            tmp.rotateRight(18);
            result.xor(tmp);
            tmp.assign(x);
            tmp.rotateRight(41);
            result.xor(tmp);
        }

        function littleSigma(result, x, tmp) {
            result.assign(x);
            result.rotateRight(1);
            tmp.assign(x);
            tmp.rotateRight(8);
            result.xor(tmp);
            tmp.assign(x);
            tmp.shiftRight(7);
            result.xor(tmp);
        }

        function littleSigmaPrime(result, x, tmp) {
            result.assign(x);
            result.rotateRight(19);
            tmp.assign(x);
            tmp.rotateRight(61);
            result.xor(tmp);
            tmp.assign(x);
            tmp.shiftRight(6);
            result.xor(tmp);
        }

        var k = [
            new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd),
            new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc),
            new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019),
            new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118),
            new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe),
            new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2),
            new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1),
            new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694),
            new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3),
            new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65),
            new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483),
            new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5),
            new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210),
            new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4),
            new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725),
            new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70),
            new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926),
            new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df),
            new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8),
            new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b),
            new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001),
            new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30),
            new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910),
            new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8),
            new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53),
            new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8),
            new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb),
            new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3),
            new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60),
            new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec),
            new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9),
            new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b),
            new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207),
            new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178),
            new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6),
            new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b),
            new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493),
            new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c),
            new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a),
            new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)];

        function hash(data, offset, length, mode384) {
            mode384 = !!mode384;
            // initial hash values
            var h0, h1, h2, h3, h4, h5, h6, h7;
            if (!mode384) {
                h0 = new Word64(0x6a09e667, 0xf3bcc908);
                h1 = new Word64(0xbb67ae85, 0x84caa73b);
                h2 = new Word64(0x3c6ef372, 0xfe94f82b);
                h3 = new Word64(0xa54ff53a, 0x5f1d36f1);
                h4 = new Word64(0x510e527f, 0xade682d1);
                h5 = new Word64(0x9b05688c, 0x2b3e6c1f);
                h6 = new Word64(0x1f83d9ab, 0xfb41bd6b);
                h7 = new Word64(0x5be0cd19, 0x137e2179);
            }
            else {
                // SHA384 is exactly the same
                // except with different starting values and a trimmed result
                h0 = new Word64(0xcbbb9d5d, 0xc1059ed8);
                h1 = new Word64(0x629a292a, 0x367cd507);
                h2 = new Word64(0x9159015a, 0x3070dd17);
                h3 = new Word64(0x152fecd8, 0xf70e5939);
                h4 = new Word64(0x67332667, 0xffc00b31);
                h5 = new Word64(0x8eb44a87, 0x68581511);
                h6 = new Word64(0xdb0c2e0d, 0x64f98fa7);
                h7 = new Word64(0x47b5481d, 0xbefa4fa4);
            }

            // pre-processing
            var paddedLength = Math.ceil((length + 17) / 128) * 128;
            var padded = new Uint8Array(paddedLength);
            var i, j, n;
            for (i = 0; i < length; ++i) {
                padded[i] = data[offset++];
            }
            padded[i++] = 0x80;
            n = paddedLength - 16;
            while (i < n) {
                padded[i++] = 0;
            }
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = 0;
            padded[i++] = (length >>> 29) & 0xFF;
            padded[i++] = (length >> 21) & 0xFF;
            padded[i++] = (length >> 13) & 0xFF;
            padded[i++] = (length >> 5) & 0xFF;
            padded[i++] = (length << 3) & 0xFF;

            var w = new Array(80);
            for (i = 0; i < 80; i++) {
                w[i] = new Word64(0, 0);
            }
            var a = new Word64(0, 0), b = new Word64(0, 0), c = new Word64(0, 0);
            var d = new Word64(0, 0), e = new Word64(0, 0), f = new Word64(0, 0);
            var g = new Word64(0, 0), h = new Word64(0, 0);
            var t1 = new Word64(0, 0), t2 = new Word64(0, 0);
            var tmp1 = new Word64(0, 0), tmp2 = new Word64(0, 0), tmp3;

            // for each 1024 bit block
            for (i = 0; i < paddedLength;) {
                for (j = 0; j < 16; ++j) {
                    w[j].high = (padded[i] << 24) | (padded[i + 1] << 16) |
                        (padded[i + 2] << 8) | (padded[i + 3]);
                    w[j].low = (padded[i + 4]) << 24 | (padded[i + 5]) << 16 |
                        (padded[i + 6]) << 8 | (padded[i + 7]);
                    i += 8;
                }
                for (j = 16; j < 80; ++j) {
                    tmp3 = w[j];
                    littleSigmaPrime(tmp3, w[j - 2], tmp2);
                    tmp3.add(w[j - 7]);
                    littleSigma(tmp1, w[j - 15], tmp2);
                    tmp3.add(tmp1);
                    tmp3.add(w[j - 16]);
                }

                a.assign(h0);
                b.assign(h1);
                c.assign(h2);
                d.assign(h3);
                e.assign(h4);
                f.assign(h5);
                g.assign(h6);
                h.assign(h7);
                for (j = 0; j < 80; ++j) {
                    t1.assign(h);
                    sigmaPrime(tmp1, e, tmp2);
                    t1.add(tmp1);
                    ch(tmp1, e, f, g, tmp2);
                    t1.add(tmp1);
                    t1.add(k[j]);
                    t1.add(w[j]);

                    sigma(t2, a, tmp2);
                    maj(tmp1, a, b, c, tmp2);
                    t2.add(tmp1);

                    tmp3 = h;
                    h = g;
                    g = f;
                    f = e;
                    d.add(t1);
                    e = d;
                    d = c;
                    c = b;
                    b = a;
                    tmp3.assign(t1);
                    tmp3.add(t2);
                    a = tmp3;
                }
                h0.add(a);
                h1.add(b);
                h2.add(c);
                h3.add(d);
                h4.add(e);
                h5.add(f);
                h6.add(g);
                h7.add(h);
            }

            var result;
            if (!mode384) {
                result = new Uint8Array(64);
                h0.copyTo(result, 0);
                h1.copyTo(result, 8);
                h2.copyTo(result, 16);
                h3.copyTo(result, 24);
                h4.copyTo(result, 32);
                h5.copyTo(result, 40);
                h6.copyTo(result, 48);
                h7.copyTo(result, 56);
            }
            else {
                result = new Uint8Array(48);
                h0.copyTo(result, 0);
                h1.copyTo(result, 8);
                h2.copyTo(result, 16);
                h3.copyTo(result, 24);
                h4.copyTo(result, 32);
                h5.copyTo(result, 40);
            }
            return result;
        }

        return hash;
    })();
    var calculateSHA384 = (function calculateSHA384Closure() {
        function hash(data, offset, length) {
            return calculateSHA512(data, offset, length, true);
        }

        return hash;
    })();
    var NullCipher = (function NullCipherClosure() {
        function NullCipher() {
        }

        NullCipher.prototype = {
            decryptBlock : function NullCipher_decryptBlock(data) {
                return data;
            }
        };

        return NullCipher;
    })();

    var AES128Cipher = (function AES128CipherClosure() {
        var rcon = new Uint8Array([
            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
            0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
            0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
            0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
            0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
            0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6,
            0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
            0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
            0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
            0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e,
            0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
            0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
            0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02,
            0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
            0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
            0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
            0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb,
            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
            0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
            0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
            0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
            0x74, 0xe8, 0xcb, 0x8d]);

        var s = new Uint8Array([
            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]);

        var inv_s = new Uint8Array([
            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]);
        var mixCol = new Uint8Array(256);
        for (var i = 0; i < 256; i++) {
            if (i < 128) {
                mixCol[i] = i << 1;
            } else {
                mixCol[i] = (i << 1) ^ 0x1b;
            }
        }
        var mix = new Uint32Array([
            0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927,
            0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45,
            0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb,
            0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381,
            0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf,
            0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66,
            0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28,
            0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012,
            0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec,
            0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e,
            0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd,
            0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7,
            0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89,
            0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b,
            0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815,
            0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f,
            0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa,
            0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8,
            0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36,
            0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c,
            0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742,
            0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea,
            0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4,
            0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e,
            0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360,
            0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502,
            0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87,
            0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd,
            0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3,
            0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621,
            0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f,
            0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55,
            0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26,
            0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844,
            0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba,
            0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480,
            0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce,
            0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67,
            0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929,
            0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713,
            0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed,
            0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f,
            0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);

        function expandKey128(cipherKey) {
            var b = 176, result = new Uint8Array(b);
            result.set(cipherKey);
            for (var j = 16, i = 1; j < b; ++i) {
                // RotWord
                var t1                     = result[j - 3], t2 = result[j - 2],
                    t3 = result[j - 1], t4 = result[j - 4];
                // SubWord
                t1 = s[t1];
                t2 = s[t2];
                t3 = s[t3];
                t4 = s[t4];
                // Rcon
                t1 = t1 ^ rcon[i];
                for (var n = 0; n < 4; ++n) {
                    result[j] = (t1 ^= result[j - 16]);
                    j++;
                    result[j] = (t2 ^= result[j - 16]);
                    j++;
                    result[j] = (t3 ^= result[j - 16]);
                    j++;
                    result[j] = (t4 ^= result[j - 16]);
                    j++;
                }
            }
            return result;
        }

        function decrypt128(input, key) {
            var state = new Uint8Array(16);
            state.set(input);
            var i, j, k;
            var t, u, v;
            // AddRoundKey
            for (j = 0, k = 160; j < 16; ++j, ++k) {
                state[j] ^= key[k];
            }
            for (i = 9; i >= 1; --i) {
                // InvShiftRows
                t = state[13];
                state[13] = state[9];
                state[9] = state[5];
                state[5] = state[1];
                state[1] = t;
                t = state[14];
                u = state[10];
                state[14] = state[6];
                state[10] = state[2];
                state[6] = t;
                state[2] = u;
                t = state[15];
                u = state[11];
                v = state[7];
                state[15] = state[3];
                state[11] = t;
                state[7] = u;
                state[3] = v;
                // InvSubBytes
                for (j = 0; j < 16; ++j) {
                    state[j] = inv_s[state[j]];
                }
                // AddRoundKey
                for (j = 0, k = i * 16; j < 16; ++j, ++k) {
                    state[j] ^= key[k];
                }
                // InvMixColumns
                for (j = 0; j < 16; j += 4) {
                    var s0                         = mix[state[j]], s1 = mix[state[j + 1]],
                        s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
                    t = (s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^
                        (s3 >>> 24) ^ (s3 << 8));
                    state[j] = (t >>> 24) & 0xFF;
                    state[j + 1] = (t >> 16) & 0xFF;
                    state[j + 2] = (t >> 8) & 0xFF;
                    state[j + 3] = t & 0xFF;
                }
            }
            // InvShiftRows
            t = state[13];
            state[13] = state[9];
            state[9] = state[5];
            state[5] = state[1];
            state[1] = t;
            t = state[14];
            u = state[10];
            state[14] = state[6];
            state[10] = state[2];
            state[6] = t;
            state[2] = u;
            t = state[15];
            u = state[11];
            v = state[7];
            state[15] = state[3];
            state[11] = t;
            state[7] = u;
            state[3] = v;
            for (j = 0; j < 16; ++j) {
                // InvSubBytes
                state[j] = inv_s[state[j]];
                // AddRoundKey
                state[j] ^= key[j];
            }
            return state;
        }

        function encrypt128(input, key) {
            var t, u, v, k;
            var state = new Uint8Array(16);
            state.set(input);
            for (j = 0; j < 16; ++j) {
                // AddRoundKey
                state[j] ^= key[j];
            }

            for (i = 1; i < 10; i++) {
                //SubBytes
                for (j = 0; j < 16; ++j) {
                    state[j] = s[state[j]];
                }
                //ShiftRows
                v = state[1];
                state[1] = state[5];
                state[5] = state[9];
                state[9] = state[13];
                state[13] = v;
                v = state[2];
                u = state[6];
                state[2] = state[10];
                state[6] = state[14];
                state[10] = v;
                state[14] = u;
                v = state[3];
                u = state[7];
                t = state[11];
                state[3] = state[15];
                state[7] = v;
                state[11] = u;
                state[15] = t;
                //MixColumns
                for (var j = 0; j < 16; j += 4) {
                    var s0 = state[j + 0], s1 = state[j + 1];
                    var s2 = state[j + 2], s3 = state[j + 3];
                    t = s0 ^ s1 ^ s2 ^ s3;
                    state[j + 0] ^= t ^ mixCol[s0 ^ s1];
                    state[j + 1] ^= t ^ mixCol[s1 ^ s2];
                    state[j + 2] ^= t ^ mixCol[s2 ^ s3];
                    state[j + 3] ^= t ^ mixCol[s3 ^ s0];
                }
                //AddRoundKey
                for (j = 0, k = i * 16; j < 16; ++j, ++k) {
                    state[j] ^= key[k];
                }
            }

            //SubBytes
            for (j = 0; j < 16; ++j) {
                state[j] = s[state[j]];
            }
            //ShiftRows
            v = state[1];
            state[1] = state[5];
            state[5] = state[9];
            state[9] = state[13];
            state[13] = v;
            v = state[2];
            u = state[6];
            state[2] = state[10];
            state[6] = state[14];
            state[10] = v;
            state[14] = u;
            v = state[3];
            u = state[7];
            t = state[11];
            state[3] = state[15];
            state[7] = v;
            state[11] = u;
            state[15] = t;
            //AddRoundKey
            for (j = 0, k = 160; j < 16; ++j, ++k) {
                state[j] ^= key[k];
            }
            return state;
        }

        function AES128Cipher(key) {
            this.key = expandKey128(key);
            this.buffer = new Uint8Array(16);
            this.bufferPosition = 0;
        }

        function decryptBlock2(data, finalize) {
            var i, j, ii, sourceLength             = data.length,
                buffer = this.buffer, bufferLength = this.bufferPosition,
                result                             = [], iv = this.iv;
            for (i = 0; i < sourceLength; ++i) {
                buffer[bufferLength] = data[i];
                ++bufferLength;
                if (bufferLength < 16) {
                    continue;
                }
                // buffer is full, decrypting
                var plain = decrypt128(buffer, this.key);
                // xor-ing the IV vector to get plain text
                for (j = 0; j < 16; ++j) {
                    plain[j] ^= iv[j];
                }
                iv = buffer;
                result.push(plain);
                buffer = new Uint8Array(16);
                bufferLength = 0;
            }
            // saving incomplete buffer
            this.buffer = buffer;
            this.bufferLength = bufferLength;
            this.iv = iv;
            if (result.length === 0) {
                return new Uint8Array([]);
            }
            // combining plain text blocks into one
            var outputLength = 16 * result.length;
            if (finalize) {
                // undo a padding that is described in RFC 2898
                var lastBlock = result[result.length - 1];
                var psLen = lastBlock[15];
                if (psLen <= 16) {
                    for (i = 15, ii = 16 - psLen; i >= ii; --i) {
                        if (lastBlock[i] !== psLen) {
                            // Invalid padding, assume that the block has no padding.
                            psLen = 0;
                            break;
                        }
                    }
                    outputLength -= psLen;
                    result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
                }
            }
            var output = new Uint8Array(outputLength);
            for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
                output.set(result[i], j);
            }
            return output;
        }

        AES128Cipher.prototype = {
            decryptBlock : function AES128Cipher_decryptBlock(data, finalize) {
                var i, sourceLength = data.length;
                var buffer = this.buffer, bufferLength = this.bufferPosition;
                // waiting for IV values -- they are at the start of the stream
                for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
                    buffer[bufferLength] = data[i];
                }
                if (bufferLength < 16) {
                    // need more data
                    this.bufferLength = bufferLength;
                    return new Uint8Array([]);
                }
                this.iv = buffer;
                this.buffer = new Uint8Array(16);
                this.bufferLength = 0;
                // starting decryption
                this.decryptBlock = decryptBlock2;
                return this.decryptBlock(data.subarray(16), finalize);
            },
            encrypt      : function AES128Cipher_encrypt(data, iv) {
                var i, j, ii, sourceLength             = data.length,
                    buffer = this.buffer, bufferLength = this.bufferPosition,
                    result                             = [];
                if (!iv) {
                    iv = new Uint8Array(16);
                }
                for (i = 0; i < sourceLength; ++i) {
                    buffer[bufferLength] = data[i];
                    ++bufferLength;
                    if (bufferLength < 16) {
                        continue;
                    }
                    for (j = 0; j < 16; ++j) {
                        buffer[j] ^= iv[j];
                    }

                    // buffer is full, encrypting
                    var cipher = encrypt128(buffer, this.key);
                    iv = cipher;
                    result.push(cipher);
                    buffer = new Uint8Array(16);
                    bufferLength = 0;
                }
                // saving incomplete buffer
                this.buffer = buffer;
                this.bufferLength = bufferLength;
                this.iv = iv;
                if (result.length === 0) {
                    return new Uint8Array([]);
                }
                // combining plain text blocks into one
                var outputLength = 16 * result.length;
                var output = new Uint8Array(outputLength);
                for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
                    output.set(result[i], j);
                }
                return output;
            }
        };

        return AES128Cipher;
    })();

    var AES256Cipher = (function AES256CipherClosure() {
        var rcon = new Uint8Array([
            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
            0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
            0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
            0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
            0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
            0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6,
            0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
            0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
            0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
            0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e,
            0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
            0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
            0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02,
            0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
            0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
            0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
            0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb,
            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
            0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
            0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
            0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
            0x74, 0xe8, 0xcb, 0x8d]);

        var s = new Uint8Array([
            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]);

        var inv_s = new Uint8Array([
            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]);

        var mixCol = new Uint8Array(256);
        for (var i = 0; i < 256; i++) {
            if (i < 128) {
                mixCol[i] = i << 1;
            } else {
                mixCol[i] = (i << 1) ^ 0x1b;
            }
        }
        var mix = new Uint32Array([
            0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927,
            0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45,
            0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb,
            0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381,
            0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf,
            0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66,
            0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28,
            0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012,
            0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec,
            0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e,
            0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd,
            0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7,
            0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89,
            0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b,
            0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815,
            0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f,
            0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa,
            0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8,
            0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36,
            0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c,
            0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742,
            0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea,
            0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4,
            0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e,
            0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360,
            0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502,
            0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87,
            0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd,
            0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3,
            0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621,
            0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f,
            0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55,
            0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26,
            0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844,
            0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba,
            0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480,
            0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce,
            0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67,
            0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929,
            0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713,
            0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed,
            0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f,
            0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);

        function expandKey256(cipherKey) {
            var b = 240, result = new Uint8Array(b);
            var r = 1;

            result.set(cipherKey);
            for (var j = 32, i = 1; j < b; ++i) {
                if (j % 32 === 16) {
                    t1 = s[t1];
                    t2 = s[t2];
                    t3 = s[t3];
                    t4 = s[t4];
                } else if (j % 32 === 0) {
                    // RotWord
                    var t1 = result[j - 3], t2 = result[j - 2],
                        t3                     = result[j - 1], t4 = result[j - 4];
                    // SubWord
                    t1 = s[t1];
                    t2 = s[t2];
                    t3 = s[t3];
                    t4 = s[t4];
                    // Rcon
                    t1 = t1 ^ r;
                    if ((r <<= 1) >= 256) {
                        r = (r ^ 0x1b) & 0xFF;
                    }
                }

                for (var n = 0; n < 4; ++n) {
                    result[j] = (t1 ^= result[j - 32]);
                    j++;
                    result[j] = (t2 ^= result[j - 32]);
                    j++;
                    result[j] = (t3 ^= result[j - 32]);
                    j++;
                    result[j] = (t4 ^= result[j - 32]);
                    j++;
                }
            }
            return result;
        }

        function decrypt256(input, key) {
            var state = new Uint8Array(16);
            state.set(input);
            var i, j, k;
            var t, u, v;
            // AddRoundKey
            for (j = 0, k = 224; j < 16; ++j, ++k) {
                state[j] ^= key[k];
            }
            for (i = 13; i >= 1; --i) {
                // InvShiftRows
                t = state[13];
                state[13] = state[9];
                state[9] = state[5];
                state[5] = state[1];
                state[1] = t;
                t = state[14];
                u = state[10];
                state[14] = state[6];
                state[10] = state[2];
                state[6] = t;
                state[2] = u;
                t = state[15];
                u = state[11];
                v = state[7];
                state[15] = state[3];
                state[11] = t;
                state[7] = u;
                state[3] = v;
                // InvSubBytes
                for (j = 0; j < 16; ++j) {
                    state[j] = inv_s[state[j]];
                }
                // AddRoundKey
                for (j = 0, k = i * 16; j < 16; ++j, ++k) {
                    state[j] ^= key[k];
                }
                // InvMixColumns
                for (j = 0; j < 16; j += 4) {
                    var s0                         = mix[state[j]], s1 = mix[state[j + 1]],
                        s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
                    t = (s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^
                        (s3 >>> 24) ^ (s3 << 8));
                    state[j] = (t >>> 24) & 0xFF;
                    state[j + 1] = (t >> 16) & 0xFF;
                    state[j + 2] = (t >> 8) & 0xFF;
                    state[j + 3] = t & 0xFF;
                }
            }
            // InvShiftRows
            t = state[13];
            state[13] = state[9];
            state[9] = state[5];
            state[5] = state[1];
            state[1] = t;
            t = state[14];
            u = state[10];
            state[14] = state[6];
            state[10] = state[2];
            state[6] = t;
            state[2] = u;
            t = state[15];
            u = state[11];
            v = state[7];
            state[15] = state[3];
            state[11] = t;
            state[7] = u;
            state[3] = v;
            for (j = 0; j < 16; ++j) {
                // InvSubBytes
                state[j] = inv_s[state[j]];
                // AddRoundKey
                state[j] ^= key[j];
            }
            return state;
        }

        function encrypt256(input, key) {
            var t, u, v, k;
            var state = new Uint8Array(16);
            state.set(input);
            for (j = 0; j < 16; ++j) {
                // AddRoundKey
                state[j] ^= key[j];
            }

            for (i = 1; i < 14; i++) {
                //SubBytes
                for (j = 0; j < 16; ++j) {
                    state[j] = s[state[j]];
                }
                //ShiftRows
                v = state[1];
                state[1] = state[5];
                state[5] = state[9];
                state[9] = state[13];
                state[13] = v;
                v = state[2];
                u = state[6];
                state[2] = state[10];
                state[6] = state[14];
                state[10] = v;
                state[14] = u;
                v = state[3];
                u = state[7];
                t = state[11];
                state[3] = state[15];
                state[7] = v;
                state[11] = u;
                state[15] = t;
                //MixColumns
                for (var j = 0; j < 16; j += 4) {
                    var s0 = state[j + 0], s1 = state[j + 1];
                    var s2 = state[j + 2], s3 = state[j + 3];
                    t = s0 ^ s1 ^ s2 ^ s3;
                    state[j + 0] ^= t ^ mixCol[s0 ^ s1];
                    state[j + 1] ^= t ^ mixCol[s1 ^ s2];
                    state[j + 2] ^= t ^ mixCol[s2 ^ s3];
                    state[j + 3] ^= t ^ mixCol[s3 ^ s0];
                }
                //AddRoundKey
                for (j = 0, k = i * 16; j < 16; ++j, ++k) {
                    state[j] ^= key[k];
                }
            }

            //SubBytes
            for (j = 0; j < 16; ++j) {
                state[j] = s[state[j]];
            }
            //ShiftRows
            v = state[1];
            state[1] = state[5];
            state[5] = state[9];
            state[9] = state[13];
            state[13] = v;
            v = state[2];
            u = state[6];
            state[2] = state[10];
            state[6] = state[14];
            state[10] = v;
            state[14] = u;
            v = state[3];
            u = state[7];
            t = state[11];
            state[3] = state[15];
            state[7] = v;
            state[11] = u;
            state[15] = t;
            //AddRoundKey
            for (j = 0, k = 224; j < 16; ++j, ++k) {
                state[j] ^= key[k];
            }

            return state;

        }

        function AES256Cipher(key) {
            this.key = expandKey256(key);
            this.buffer = new Uint8Array(16);
            this.bufferPosition = 0;
        }

        function decryptBlock2(data, finalize) {
            var i, j, ii, sourceLength             = data.length,
                buffer = this.buffer, bufferLength = this.bufferPosition,
                result                             = [], iv = this.iv;

            for (i = 0; i < sourceLength; ++i) {
                buffer[bufferLength] = data[i];
                ++bufferLength;
                if (bufferLength < 16) {
                    continue;
                }
                // buffer is full, decrypting
                var plain = decrypt256(buffer, this.key);
                // xor-ing the IV vector to get plain text
                for (j = 0; j < 16; ++j) {
                    plain[j] ^= iv[j];
                }
                iv = buffer;
                result.push(plain);
                buffer = new Uint8Array(16);
                bufferLength = 0;
            }
            // saving incomplete buffer
            this.buffer = buffer;
            this.bufferLength = bufferLength;
            this.iv = iv;
            if (result.length === 0) {
                return new Uint8Array([]);
            }
            // combining plain text blocks into one
            var outputLength = 16 * result.length;
            if (finalize) {
                // undo a padding that is described in RFC 2898
                var lastBlock = result[result.length - 1];
                var psLen = lastBlock[15];
                if (psLen <= 16) {
                    for (i = 15, ii = 16 - psLen; i >= ii; --i) {
                        if (lastBlock[i] !== psLen) {
                            // Invalid padding, assume that the block has no padding.
                            psLen = 0;
                            break;
                        }
                    }
                    outputLength -= psLen;
                    result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
                }
            }
            var output = new Uint8Array(outputLength);
            for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
                output.set(result[i], j);
            }
            return output;

        }

        AES256Cipher.prototype = {
            decryptBlock : function AES256Cipher_decryptBlock(data, finalize, iv) {
                var i, sourceLength = data.length;
                var buffer = this.buffer, bufferLength = this.bufferPosition;
                // if not supplied an IV wait for IV values
                // they are at the start of the stream
                if (iv) {
                    this.iv = iv;
                } else {
                    for (i = 0; bufferLength < 16 &&
                    i < sourceLength; ++i, ++bufferLength) {
                        buffer[bufferLength] = data[i];
                    }
                    if (bufferLength < 16) {
                        //need more data
                        this.bufferLength = bufferLength;
                        return new Uint8Array([]);
                    }
                    this.iv = buffer;
                    data = data.subarray(16);
                }
                this.buffer = new Uint8Array(16);
                this.bufferLength = 0;
                // starting decryption
                this.decryptBlock = decryptBlock2;
                return this.decryptBlock(data, finalize);
            },
            encrypt      : function AES256Cipher_encrypt(data, iv) {
                var i, j, ii, sourceLength             = data.length,
                    buffer = this.buffer, bufferLength = this.bufferPosition,
                    result                             = [];
                if (!iv) {
                    iv = new Uint8Array(16);
                }
                for (i = 0; i < sourceLength; ++i) {
                    buffer[bufferLength] = data[i];
                    ++bufferLength;
                    if (bufferLength < 16) {
                        continue;
                    }
                    for (j = 0; j < 16; ++j) {
                        buffer[j] ^= iv[j];
                    }

                    // buffer is full, encrypting
                    var cipher = encrypt256(buffer, this.key);
                    this.iv = cipher;
                    result.push(cipher);
                    buffer = new Uint8Array(16);
                    bufferLength = 0;
                }
                // saving incomplete buffer
                this.buffer = buffer;
                this.bufferLength = bufferLength;
                this.iv = iv;
                if (result.length === 0) {
                    return new Uint8Array([]);
                }
                // combining plain text blocks into one
                var outputLength = 16 * result.length;
                var output = new Uint8Array(outputLength);
                for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
                    output.set(result[i], j);
                }
                return output;
            }
        };

        return AES256Cipher;
    })();

    var PDF17 = (function PDF17Closure() {

        function compareByteArrays(array1, array2) {
            if (array1.length !== array2.length) {
                return false;
            }
            for (var i = 0; i < array1.length; i++) {
                if (array1[i] !== array2[i]) {
                    return false;
                }
            }
            return true;
        }

        function PDF17() {
        }

        PDF17.prototype = {
            checkOwnerPassword : function PDF17_checkOwnerPassword(password,
                                                                   ownerValidationSalt,
                                                                   userBytes,
                                                                   ownerPassword) {
                var hashData = new Uint8Array(password.length + 56);
                hashData.set(password, 0);
                hashData.set(ownerValidationSalt, password.length);
                hashData.set(userBytes, password.length + ownerValidationSalt.length);
                var result = calculateSHA256(hashData, 0, hashData.length);
                return compareByteArrays(result, ownerPassword);
            },
            checkUserPassword  : function PDF17_checkUserPassword(password,
                                                                  userValidationSalt,
                                                                  userPassword) {
                var hashData = new Uint8Array(password.length + 8);
                hashData.set(password, 0);
                hashData.set(userValidationSalt, password.length);
                var result = calculateSHA256(hashData, 0, hashData.length);
                return compareByteArrays(result, userPassword);
            },
            getOwnerKey        : function PDF17_getOwnerKey(password, ownerKeySalt, userBytes,
                                                            ownerEncryption) {
                var hashData = new Uint8Array(password.length + 56);
                hashData.set(password, 0);
                hashData.set(ownerKeySalt, password.length);
                hashData.set(userBytes, password.length + ownerKeySalt.length);
                var key = calculateSHA256(hashData, 0, hashData.length);
                var cipher = new AES256Cipher(key);
                return cipher.decryptBlock(ownerEncryption,
                    false,
                    new Uint8Array(16));

            },
            getUserKey         : function PDF17_getUserKey(password, userKeySalt,
                                                           userEncryption) {
                var hashData = new Uint8Array(password.length + 8);
                hashData.set(password, 0);
                hashData.set(userKeySalt, password.length);
                //key is the decryption key for the UE string
                var key = calculateSHA256(hashData, 0, hashData.length);
                var cipher = new AES256Cipher(key);
                return cipher.decryptBlock(userEncryption,
                    false,
                    new Uint8Array(16));
            }
        };
        return PDF17;
    })();

    var PDF20 = (function PDF20Closure() {

        function concatArrays(array1, array2) {
            var t = new Uint8Array(array1.length + array2.length);
            t.set(array1, 0);
            t.set(array2, array1.length);
            return t;
        }

        function calculatePDF20Hash(password, input, userBytes) {
            //This refers to Algorithm 2.B as defined in ISO 32000-2
            var k = calculateSHA256(input, 0, input.length).subarray(0, 32);
            var e = [0];
            var i = 0;
            while (i < 64 || e[e.length - 1] > i - 32) {
                var arrayLength = password.length + k.length + userBytes.length;

                var k1 = new Uint8Array(arrayLength * 64);
                var array = concatArrays(password, k);
                array = concatArrays(array, userBytes);
                for (var j = 0, pos = 0; j < 64; j++, pos += arrayLength) {
                    k1.set(array, pos);
                }
                //AES128 CBC NO PADDING with
                //first 16 bytes of k as the key and the second 16 as the iv.
                var cipher = new AES128Cipher(k.subarray(0, 16));
                e = cipher.encrypt(k1, k.subarray(16, 32));
                //Now we have to take the first 16 bytes of an unsigned
                //big endian integer... and compute the remainder
                //modulo 3.... That is a fairly large number and
                //JavaScript isn't going to handle that well...
                //So we're using a trick that allows us to perform
                //modulo math byte by byte
                var remainder = 0;
                for (var z = 0; z < 16; z++) {
                    remainder *= (256 % 3);
                    remainder %= 3;
                    remainder += ((e[z] >>> 0) % 3);
                    remainder %= 3;
                }
                if (remainder === 0) {
                    k = calculateSHA256(e, 0, e.length);
                }
                else if (remainder === 1) {
                    k = calculateSHA384(e, 0, e.length);
                }
                else if (remainder === 2) {
                    k = calculateSHA512(e, 0, e.length);
                }
                i++;
            }
            return k.subarray(0, 32);
        }

        function PDF20() {
        }

        function compareByteArrays(array1, array2) {
            if (array1.length !== array2.length) {
                return false;
            }
            for (var i = 0; i < array1.length; i++) {
                if (array1[i] !== array2[i]) {
                    return false;
                }
            }
            return true;
        }

        PDF20.prototype = {
            hash               : function PDF20_hash(password, concatBytes, userBytes) {
                return calculatePDF20Hash(password, concatBytes, userBytes);
            },
            checkOwnerPassword : function PDF20_checkOwnerPassword(password,
                                                                   ownerValidationSalt,
                                                                   userBytes,
                                                                   ownerPassword) {
                var hashData = new Uint8Array(password.length + 56);
                hashData.set(password, 0);
                hashData.set(ownerValidationSalt, password.length);
                hashData.set(userBytes, password.length + ownerValidationSalt.length);
                var result = calculatePDF20Hash(password, hashData, userBytes);
                return compareByteArrays(result, ownerPassword);
            },
            checkUserPassword  : function PDF20_checkUserPassword(password,
                                                                  userValidationSalt,
                                                                  userPassword) {
                var hashData = new Uint8Array(password.length + 8);
                hashData.set(password, 0);
                hashData.set(userValidationSalt, password.length);
                var result = calculatePDF20Hash(password, hashData, []);
                return compareByteArrays(result, userPassword);
            },
            getOwnerKey        : function PDF20_getOwnerKey(password, ownerKeySalt, userBytes,
                                                            ownerEncryption) {
                var hashData = new Uint8Array(password.length + 56);
                hashData.set(password, 0);
                hashData.set(ownerKeySalt, password.length);
                hashData.set(userBytes, password.length + ownerKeySalt.length);
                var key = calculatePDF20Hash(password, hashData, userBytes);
                var cipher = new AES256Cipher(key);
                return cipher.decryptBlock(ownerEncryption,
                    false,
                    new Uint8Array(16));

            },
            getUserKey         : function PDF20_getUserKey(password, userKeySalt,
                                                           userEncryption) {
                var hashData = new Uint8Array(password.length + 8);
                hashData.set(password, 0);
                hashData.set(userKeySalt, password.length);
                //key is the decryption key for the UE string
                var key = calculatePDF20Hash(password, hashData, []);
                var cipher = new AES256Cipher(key);
                return cipher.decryptBlock(userEncryption,
                    false,
                    new Uint8Array(16));
            }
        };
        return PDF20;
    })();

    var CipherTransform = (function CipherTransformClosure() {
        function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
            this.stringCipherConstructor = stringCipherConstructor;
            this.streamCipherConstructor = streamCipherConstructor;
        }

        CipherTransform.prototype = {
            createStream  : function CipherTransform_createStream(stream, length) {
                var cipher = new this.streamCipherConstructor();
                return new DecryptStream(stream, length,
                    function cipherTransformDecryptStream(data, finalize) {
                        return cipher.decryptBlock(data, finalize);
                    }
                );
            },
            decryptString : function CipherTransform_decryptString(s) {
                var cipher = new this.stringCipherConstructor();
                var data = stringToBytes(s);
                data = cipher.decryptBlock(data, true);
                return bytesToString(data);
            }
        };
        return CipherTransform;
    })();

    var CipherTransformFactory = (function CipherTransformFactoryClosure() {
        var defaultPasswordBytes = new Uint8Array([
            0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
            0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
            0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
            0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);

        function createEncryptionKey20(revision, password, ownerPassword,
                                       ownerValidationSalt, ownerKeySalt, uBytes,
                                       userPassword, userValidationSalt, userKeySalt,
                                       ownerEncryption, userEncryption, perms) {
            if (password) {
                var passwordLength = Math.min(127, password.length);
                password = password.subarray(0, passwordLength);
            } else {
                password = [];
            }
            var pdfAlgorithm;
            if (revision === 6) {
                pdfAlgorithm = new PDF20();
            } else {
                pdfAlgorithm = new PDF17();
            }

            if (pdfAlgorithm.checkUserPassword(password, userValidationSalt,
                userPassword)) {
                return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
            } else if (password.length && pdfAlgorithm.checkOwnerPassword(password,
                ownerValidationSalt,
                uBytes,
                ownerPassword)) {
                return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes,
                    ownerEncryption);
            }

            return null;
        }

        function prepareKeyData(fileId, password, ownerPassword, userPassword,
                                flags, revision, keyLength, encryptMetadata) {
            var hashDataSize = 40 + ownerPassword.length + fileId.length;
            var hashData = new Uint8Array(hashDataSize), i = 0, j, n;
            if (password) {
                n = Math.min(32, password.length);
                for (; i < n; ++i) {
                    hashData[i] = password[i];
                }
            }
            j = 0;
            while (i < 32) {
                hashData[i++] = defaultPasswordBytes[j++];
            }
            // as now the padded password in the hashData[0..i]
            for (j = 0, n = ownerPassword.length; j < n; ++j) {
                hashData[i++] = ownerPassword[j];
            }
            hashData[i++] = flags & 0xFF;
            hashData[i++] = (flags >> 8) & 0xFF;
            hashData[i++] = (flags >> 16) & 0xFF;
            hashData[i++] = (flags >>> 24) & 0xFF;
            for (j = 0, n = fileId.length; j < n; ++j) {
                hashData[i++] = fileId[j];
            }
            if (revision >= 4 && !encryptMetadata) {
                hashData[i++] = 0xFF;
                hashData[i++] = 0xFF;
                hashData[i++] = 0xFF;
                hashData[i++] = 0xFF;
            }
            var hash = calculateMD5(hashData, 0, i);
            var keyLengthInBytes = keyLength >> 3;
            if (revision >= 3) {
                for (j = 0; j < 50; ++j) {
                    hash = calculateMD5(hash, 0, keyLengthInBytes);
                }
            }
            var encryptionKey = hash.subarray(0, keyLengthInBytes);
            var cipher, checkData;

            if (revision >= 3) {
                for (i = 0; i < 32; ++i) {
                    hashData[i] = defaultPasswordBytes[i];
                }
                for (j = 0, n = fileId.length; j < n; ++j) {
                    hashData[i++] = fileId[j];
                }
                cipher = new ARCFourCipher(encryptionKey);
                checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
                n = encryptionKey.length;
                var derivedKey = new Uint8Array(n), k;
                for (j = 1; j <= 19; ++j) {
                    for (k = 0; k < n; ++k) {
                        derivedKey[k] = encryptionKey[k] ^ j;
                    }
                    cipher = new ARCFourCipher(derivedKey);
                    checkData = cipher.encryptBlock(checkData);
                }
                for (j = 0, n = checkData.length; j < n; ++j) {
                    if (userPassword[j] !== checkData[j]) {
                        return null;
                    }
                }
            } else {
                cipher = new ARCFourCipher(encryptionKey);
                checkData = cipher.encryptBlock(defaultPasswordBytes);
                for (j = 0, n = checkData.length; j < n; ++j) {
                    if (userPassword[j] !== checkData[j]) {
                        return null;
                    }
                }
            }
            return encryptionKey;
        }

        function decodeUserPassword(password, ownerPassword, revision, keyLength) {
            var hashData = new Uint8Array(32), i = 0, j, n;
            n = Math.min(32, password.length);
            for (; i < n; ++i) {
                hashData[i] = password[i];
            }
            j = 0;
            while (i < 32) {
                hashData[i++] = defaultPasswordBytes[j++];
            }
            var hash = calculateMD5(hashData, 0, i);
            var keyLengthInBytes = keyLength >> 3;
            if (revision >= 3) {
                for (j = 0; j < 50; ++j) {
                    hash = calculateMD5(hash, 0, hash.length);
                }
            }

            var cipher, userPassword;
            if (revision >= 3) {
                userPassword = ownerPassword;
                var derivedKey = new Uint8Array(keyLengthInBytes), k;
                for (j = 19; j >= 0; j--) {
                    for (k = 0; k < keyLengthInBytes; ++k) {
                        derivedKey[k] = hash[k] ^ j;
                    }
                    cipher = new ARCFourCipher(derivedKey);
                    userPassword = cipher.encryptBlock(userPassword);
                }
            } else {
                cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
                userPassword = cipher.encryptBlock(ownerPassword);
            }
            return userPassword;
        }

        var identityName = Name.get('Identity');

        function CipherTransformFactory(dict, fileId, password) {
            var filter = dict.get('Filter');
            if (!isName(filter) || filter.name !== 'Standard') {
                error('unknown encryption method');
            }
            this.dict = dict;
            var algorithm = dict.get('V');
            if (!isInt(algorithm) ||
                (algorithm !== 1 && algorithm !== 2 && algorithm !== 4 &&
                    algorithm !== 5)) {
                error('unsupported encryption algorithm');
            }
            this.algorithm = algorithm;
            var keyLength = dict.get('Length');
            if (!keyLength) {
                // Spec asks to rely on encryption dictionary's Length entry, however
                // some PDFs don't have it. Trying to recover.
                if (algorithm <= 3) {
                    // For 1 and 2 it's fixed to 40-bit, for 3 40-bit is a minimal value.
                    keyLength = 40;
                } else {
                    // Trying to find default handler -- it usually has Length.
                    var cfDict = dict.get('CF');
                    var streamCryptoName = dict.get('StmF');
                    if (isDict(cfDict) && isName(streamCryptoName)) {
                        var handlerDict = cfDict.get(streamCryptoName.name);
                        keyLength = (handlerDict && handlerDict.get('Length')) || 128;
                        if (keyLength < 40) {
                            // Sometimes it's incorrect value of bits, generators specify bytes.
                            keyLength <<= 3;
                        }
                    }
                }
            }
            if (!isInt(keyLength) ||
                keyLength < 40 || (keyLength % 8) !== 0) {
                error('invalid key length');
            }

            // prepare keys
            var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32);
            var userPassword = stringToBytes(dict.get('U')).subarray(0, 32);
            var flags = dict.get('P');
            var revision = dict.get('R');
            // meaningful when V is 4 or 5
            var encryptMetadata = ((algorithm === 4 || algorithm === 5) &&
                dict.get('EncryptMetadata') !== false);
            this.encryptMetadata = encryptMetadata;

            var fileIdBytes = stringToBytes(fileId);
            var passwordBytes;
            if (password) {
                if (revision === 6) {
                    try {
                        password = utf8StringToString(password);
                    } catch (ex) {
                        warn('CipherTransformFactory: ' +
                            'Unable to convert UTF8 encoded password.');
                    }
                }
                passwordBytes = stringToBytes(password);
            }

            var encryptionKey;
            if (algorithm !== 5) {
                encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
                    ownerPassword, userPassword, flags,
                    revision, keyLength, encryptMetadata);
            }
            else {
                var ownerValidationSalt = stringToBytes(dict.get('O')).subarray(32, 40);
                var ownerKeySalt = stringToBytes(dict.get('O')).subarray(40, 48);
                var uBytes = stringToBytes(dict.get('U')).subarray(0, 48);
                var userValidationSalt = stringToBytes(dict.get('U')).subarray(32, 40);
                var userKeySalt = stringToBytes(dict.get('U')).subarray(40, 48);
                var ownerEncryption = stringToBytes(dict.get('OE'));
                var userEncryption = stringToBytes(dict.get('UE'));
                var perms = stringToBytes(dict.get('Perms'));
                encryptionKey =
                    createEncryptionKey20(revision, passwordBytes,
                        ownerPassword, ownerValidationSalt,
                        ownerKeySalt, uBytes,
                        userPassword, userValidationSalt,
                        userKeySalt, ownerEncryption,
                        userEncryption, perms);
            }
            if (!encryptionKey && !password) {
                throw new PasswordException('No password given',
                    PasswordResponses.NEED_PASSWORD);
            } else if (!encryptionKey && password) {
                // Attempting use the password as an owner password
                var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
                    revision, keyLength);
                encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
                    ownerPassword, userPassword, flags,
                    revision, keyLength, encryptMetadata);
            }

            if (!encryptionKey) {
                throw new PasswordException('Incorrect Password',
                    PasswordResponses.INCORRECT_PASSWORD);
            }

            this.encryptionKey = encryptionKey;

            if (algorithm >= 4) {
                this.cf = dict.get('CF');
                this.stmf = dict.get('StmF') || identityName;
                this.strf = dict.get('StrF') || identityName;
                this.eff = dict.get('EFF') || this.stmf;
            }
        }

        function buildObjectKey(num, gen, encryptionKey, isAes) {
            var key = new Uint8Array(encryptionKey.length + 9), i, n;
            for (i = 0, n = encryptionKey.length; i < n; ++i) {
                key[i] = encryptionKey[i];
            }
            key[i++] = num & 0xFF;
            key[i++] = (num >> 8) & 0xFF;
            key[i++] = (num >> 16) & 0xFF;
            key[i++] = gen & 0xFF;
            key[i++] = (gen >> 8) & 0xFF;
            if (isAes) {
                key[i++] = 0x73;
                key[i++] = 0x41;
                key[i++] = 0x6C;
                key[i++] = 0x54;
            }
            var hash = calculateMD5(key, 0, i);
            return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
        }

        function buildCipherConstructor(cf, name, num, gen, key) {
            var cryptFilter = cf.get(name.name);
            var cfm;
            if (cryptFilter !== null && cryptFilter !== undefined) {
                cfm = cryptFilter.get('CFM');
            }
            if (!cfm || cfm.name === 'None') {
                return function cipherTransformFactoryBuildCipherConstructorNone() {
                    return new NullCipher();
                };
            }
            if ('V2' === cfm.name) {
                return function cipherTransformFactoryBuildCipherConstructorV2() {
                    return new ARCFourCipher(buildObjectKey(num, gen, key, false));
                };
            }
            if ('AESV2' === cfm.name) {
                return function cipherTransformFactoryBuildCipherConstructorAESV2() {
                    return new AES128Cipher(buildObjectKey(num, gen, key, true));
                };
            }
            if ('AESV3' === cfm.name) {
                return function cipherTransformFactoryBuildCipherConstructorAESV3() {
                    return new AES256Cipher(key);
                };
            }
            error('Unknown crypto method');
        }

        CipherTransformFactory.prototype = {
            createCipherTransform :
                function CipherTransformFactory_createCipherTransform(num, gen) {
                    if (this.algorithm === 4 || this.algorithm === 5) {
                        return new CipherTransform(
                            buildCipherConstructor(this.cf, this.stmf,
                                num, gen, this.encryptionKey),
                            buildCipherConstructor(this.cf, this.strf,
                                num, gen, this.encryptionKey));
                    }
                    // algorithms 1 and 2
                    var key = buildObjectKey(num, gen, this.encryptionKey, false);
                    var cipherConstructor = function buildCipherCipherConstructor() {
                        return new ARCFourCipher(key);
                    };
                    return new CipherTransform(cipherConstructor, cipherConstructor);
                }
        };

        return CipherTransformFactory;
    })();

    exports.AES128Cipher = AES128Cipher;
    exports.AES256Cipher = AES256Cipher;
    exports.ARCFourCipher = ARCFourCipher;
    exports.CipherTransformFactory = CipherTransformFactory;
    exports.PDF17 = PDF17;
    exports.PDF20 = PDF20;
    exports.calculateMD5 = calculateMD5;
    exports.calculateSHA256 = calculateSHA256;
    exports.calculateSHA384 = calculateSHA384;
    exports.calculateSHA512 = calculateSHA512;
}));
