/**
 * GOST 28147-89/GOST R 34.12-2015/GOST R 32.13-2015 Encryption Algorithm
 * 1.76
 * 2014-2016, Rudolf Nickolaev. All rights reserved.
 * 
 * Exported for CyberChef by mshwed [m@ttshwed.com]
 */

/*
 * 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.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

import GostRandom from './gostRandom.mjs';

import crypto from 'crypto'

/*
* Initial parameters and common algortithms of GOST 28147-89 
* 
* http://tools.ietf.org/html/rfc5830
* 
*/ // <editor-fold defaultstate="collapsed">

var root = {};
var rootCrypto = crypto;
var CryptoOperationData = ArrayBuffer;
var SyntaxError = Error,
        DataError = Error,
        NotSupportedError = Error;
/*
* Check supported
* This implementation support only Little Endian arhitecture
*/

var littleEndian = (function () {
    var buffer = new CryptoOperationData(2);
    new DataView(buffer).setInt16(0, 256, true);
    return new Int16Array(buffer)[0] === 256;
})();

// Default initial vector
var defaultIV = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);

// Predefined sBox collection
var sBoxes = {
    'E-TEST': [
        0x4, 0x2, 0xF, 0x5, 0x9, 0x1, 0x0, 0x8, 0xE, 0x3, 0xB, 0xC, 0xD, 0x7, 0xA, 0x6,
        0xC, 0x9, 0xF, 0xE, 0x8, 0x1, 0x3, 0xA, 0x2, 0x7, 0x4, 0xD, 0x6, 0x0, 0xB, 0x5,
        0xD, 0x8, 0xE, 0xC, 0x7, 0x3, 0x9, 0xA, 0x1, 0x5, 0x2, 0x4, 0x6, 0xF, 0x0, 0xB,
        0xE, 0x9, 0xB, 0x2, 0x5, 0xF, 0x7, 0x1, 0x0, 0xD, 0xC, 0x6, 0xA, 0x4, 0x3, 0x8,
        0x3, 0xE, 0x5, 0x9, 0x6, 0x8, 0x0, 0xD, 0xA, 0xB, 0x7, 0xC, 0x2, 0x1, 0xF, 0x4,
        0x8, 0xF, 0x6, 0xB, 0x1, 0x9, 0xC, 0x5, 0xD, 0x3, 0x7, 0xA, 0x0, 0xE, 0x2, 0x4,
        0x9, 0xB, 0xC, 0x0, 0x3, 0x6, 0x7, 0x5, 0x4, 0x8, 0xE, 0xF, 0x1, 0xA, 0x2, 0xD,
        0xC, 0x6, 0x5, 0x2, 0xB, 0x0, 0x9, 0xD, 0x3, 0xE, 0x7, 0xA, 0xF, 0x4, 0x1, 0x8
    ],
    'E-A': [
        0x9, 0x6, 0x3, 0x2, 0x8, 0xB, 0x1, 0x7, 0xA, 0x4, 0xE, 0xF, 0xC, 0x0, 0xD, 0x5,
        0x3, 0x7, 0xE, 0x9, 0x8, 0xA, 0xF, 0x0, 0x5, 0x2, 0x6, 0xC, 0xB, 0x4, 0xD, 0x1,
        0xE, 0x4, 0x6, 0x2, 0xB, 0x3, 0xD, 0x8, 0xC, 0xF, 0x5, 0xA, 0x0, 0x7, 0x1, 0x9,
        0xE, 0x7, 0xA, 0xC, 0xD, 0x1, 0x3, 0x9, 0x0, 0x2, 0xB, 0x4, 0xF, 0x8, 0x5, 0x6,
        0xB, 0x5, 0x1, 0x9, 0x8, 0xD, 0xF, 0x0, 0xE, 0x4, 0x2, 0x3, 0xC, 0x7, 0xA, 0x6,
        0x3, 0xA, 0xD, 0xC, 0x1, 0x2, 0x0, 0xB, 0x7, 0x5, 0x9, 0x4, 0x8, 0xF, 0xE, 0x6,
        0x1, 0xD, 0x2, 0x9, 0x7, 0xA, 0x6, 0x0, 0x8, 0xC, 0x4, 0x5, 0xF, 0x3, 0xB, 0xE,
        0xB, 0xA, 0xF, 0x5, 0x0, 0xC, 0xE, 0x8, 0x6, 0x2, 0x3, 0x9, 0x1, 0x7, 0xD, 0x4
    ],
    'E-B': [
        0x8, 0x4, 0xB, 0x1, 0x3, 0x5, 0x0, 0x9, 0x2, 0xE, 0xA, 0xC, 0xD, 0x6, 0x7, 0xF,
        0x0, 0x1, 0x2, 0xA, 0x4, 0xD, 0x5, 0xC, 0x9, 0x7, 0x3, 0xF, 0xB, 0x8, 0x6, 0xE,
        0xE, 0xC, 0x0, 0xA, 0x9, 0x2, 0xD, 0xB, 0x7, 0x5, 0x8, 0xF, 0x3, 0x6, 0x1, 0x4,
        0x7, 0x5, 0x0, 0xD, 0xB, 0x6, 0x1, 0x2, 0x3, 0xA, 0xC, 0xF, 0x4, 0xE, 0x9, 0x8,
        0x2, 0x7, 0xC, 0xF, 0x9, 0x5, 0xA, 0xB, 0x1, 0x4, 0x0, 0xD, 0x6, 0x8, 0xE, 0x3,
        0x8, 0x3, 0x2, 0x6, 0x4, 0xD, 0xE, 0xB, 0xC, 0x1, 0x7, 0xF, 0xA, 0x0, 0x9, 0x5,
        0x5, 0x2, 0xA, 0xB, 0x9, 0x1, 0xC, 0x3, 0x7, 0x4, 0xD, 0x0, 0x6, 0xF, 0x8, 0xE,
        0x0, 0x4, 0xB, 0xE, 0x8, 0x3, 0x7, 0x1, 0xA, 0x2, 0x9, 0x6, 0xF, 0xD, 0x5, 0xC
    ],
    'E-C': [
        0x1, 0xB, 0xC, 0x2, 0x9, 0xD, 0x0, 0xF, 0x4, 0x5, 0x8, 0xE, 0xA, 0x7, 0x6, 0x3,
        0x0, 0x1, 0x7, 0xD, 0xB, 0x4, 0x5, 0x2, 0x8, 0xE, 0xF, 0xC, 0x9, 0xA, 0x6, 0x3,
        0x8, 0x2, 0x5, 0x0, 0x4, 0x9, 0xF, 0xA, 0x3, 0x7, 0xC, 0xD, 0x6, 0xE, 0x1, 0xB,
        0x3, 0x6, 0x0, 0x1, 0x5, 0xD, 0xA, 0x8, 0xB, 0x2, 0x9, 0x7, 0xE, 0xF, 0xC, 0x4,
        0x8, 0xD, 0xB, 0x0, 0x4, 0x5, 0x1, 0x2, 0x9, 0x3, 0xC, 0xE, 0x6, 0xF, 0xA, 0x7,
        0xC, 0x9, 0xB, 0x1, 0x8, 0xE, 0x2, 0x4, 0x7, 0x3, 0x6, 0x5, 0xA, 0x0, 0xF, 0xD,
        0xA, 0x9, 0x6, 0x8, 0xD, 0xE, 0x2, 0x0, 0xF, 0x3, 0x5, 0xB, 0x4, 0x1, 0xC, 0x7,
        0x7, 0x4, 0x0, 0x5, 0xA, 0x2, 0xF, 0xE, 0xC, 0x6, 0x1, 0xB, 0xD, 0x9, 0x3, 0x8
    ],
    'E-D': [
        0xF, 0xC, 0x2, 0xA, 0x6, 0x4, 0x5, 0x0, 0x7, 0x9, 0xE, 0xD, 0x1, 0xB, 0x8, 0x3,
        0xB, 0x6, 0x3, 0x4, 0xC, 0xF, 0xE, 0x2, 0x7, 0xD, 0x8, 0x0, 0x5, 0xA, 0x9, 0x1,
        0x1, 0xC, 0xB, 0x0, 0xF, 0xE, 0x6, 0x5, 0xA, 0xD, 0x4, 0x8, 0x9, 0x3, 0x7, 0x2,
        0x1, 0x5, 0xE, 0xC, 0xA, 0x7, 0x0, 0xD, 0x6, 0x2, 0xB, 0x4, 0x9, 0x3, 0xF, 0x8,
        0x0, 0xC, 0x8, 0x9, 0xD, 0x2, 0xA, 0xB, 0x7, 0x3, 0x6, 0x5, 0x4, 0xE, 0xF, 0x1,
        0x8, 0x0, 0xF, 0x3, 0x2, 0x5, 0xE, 0xB, 0x1, 0xA, 0x4, 0x7, 0xC, 0x9, 0xD, 0x6,
        0x3, 0x0, 0x6, 0xF, 0x1, 0xE, 0x9, 0x2, 0xD, 0x8, 0xC, 0x4, 0xB, 0xA, 0x5, 0x7,
        0x1, 0xA, 0x6, 0x8, 0xF, 0xB, 0x0, 0x4, 0xC, 0x3, 0x5, 0x9, 0x7, 0xD, 0x2, 0xE
    ],
    'E-SC': [
        0x3, 0x6, 0x1, 0x0, 0x5, 0x7, 0xd, 0x9, 0x4, 0xb, 0x8, 0xc, 0xe, 0xf, 0x2, 0xa,
        0x7, 0x1, 0x5, 0x2, 0x8, 0xb, 0x9, 0xc, 0xd, 0x0, 0x3, 0xa, 0xf, 0xe, 0x4, 0x6,
        0xf, 0x1, 0x4, 0x6, 0xc, 0x8, 0x9, 0x2, 0xe, 0x3, 0x7, 0xa, 0xb, 0xd, 0x5, 0x0,
        0x3, 0x4, 0xf, 0xc, 0x5, 0x9, 0xe, 0x0, 0x6, 0x8, 0x7, 0xa, 0x1, 0xb, 0xd, 0x2,
        0x6, 0x9, 0x0, 0x7, 0xb, 0x8, 0x4, 0xc, 0x2, 0xe, 0xa, 0xf, 0x1, 0xd, 0x5, 0x3,
        0x6, 0x1, 0x2, 0xf, 0x0, 0xb, 0x9, 0xc, 0x7, 0xd, 0xa, 0x5, 0x8, 0x4, 0xe, 0x3,
        0x0, 0x2, 0xe, 0xc, 0x9, 0x1, 0x4, 0x7, 0x3, 0xf, 0x6, 0x8, 0xa, 0xd, 0xb, 0x5,
        0x5, 0x2, 0xb, 0x8, 0x4, 0xc, 0x7, 0x1, 0xa, 0x6, 0xe, 0x0, 0x9, 0x3, 0xd, 0xf
    ],
    'E-Z': [// This is default S-box in according to draft of new standard
        0xc, 0x4, 0x6, 0x2, 0xa, 0x5, 0xb, 0x9, 0xe, 0x8, 0xd, 0x7, 0x0, 0x3, 0xf, 0x1,
        0x6, 0x8, 0x2, 0x3, 0x9, 0xa, 0x5, 0xc, 0x1, 0xe, 0x4, 0x7, 0xb, 0xd, 0x0, 0xf,
        0xb, 0x3, 0x5, 0x8, 0x2, 0xf, 0xa, 0xd, 0xe, 0x1, 0x7, 0x4, 0xc, 0x9, 0x6, 0x0,
        0xc, 0x8, 0x2, 0x1, 0xd, 0x4, 0xf, 0x6, 0x7, 0x0, 0xa, 0x5, 0x3, 0xe, 0x9, 0xb,
        0x7, 0xf, 0x5, 0xa, 0x8, 0x1, 0x6, 0xd, 0x0, 0x9, 0x3, 0xe, 0xb, 0x4, 0x2, 0xc,
        0x5, 0xd, 0xf, 0x6, 0x9, 0x2, 0xc, 0xa, 0xb, 0x7, 0x8, 0x1, 0x4, 0x3, 0xe, 0x0,
        0x8, 0xe, 0x2, 0x5, 0x6, 0x9, 0x1, 0xc, 0xf, 0x4, 0xb, 0x0, 0xd, 0xa, 0x3, 0x7,
        0x1, 0x7, 0xe, 0xd, 0x0, 0x5, 0x8, 0x3, 0x4, 0xf, 0xa, 0x6, 0x9, 0xc, 0xb, 0x2
    ],
    //S-box for digest
    'D-TEST': [
        0x4, 0xA, 0x9, 0x2, 0xD, 0x8, 0x0, 0xE, 0x6, 0xB, 0x1, 0xC, 0x7, 0xF, 0x5, 0x3,
        0xE, 0xB, 0x4, 0xC, 0x6, 0xD, 0xF, 0xA, 0x2, 0x3, 0x8, 0x1, 0x0, 0x7, 0x5, 0x9,
        0x5, 0x8, 0x1, 0xD, 0xA, 0x3, 0x4, 0x2, 0xE, 0xF, 0xC, 0x7, 0x6, 0x0, 0x9, 0xB,
        0x7, 0xD, 0xA, 0x1, 0x0, 0x8, 0x9, 0xF, 0xE, 0x4, 0x6, 0xC, 0xB, 0x2, 0x5, 0x3,
        0x6, 0xC, 0x7, 0x1, 0x5, 0xF, 0xD, 0x8, 0x4, 0xA, 0x9, 0xE, 0x0, 0x3, 0xB, 0x2,
        0x4, 0xB, 0xA, 0x0, 0x7, 0x2, 0x1, 0xD, 0x3, 0x6, 0x8, 0x5, 0x9, 0xC, 0xF, 0xE,
        0xD, 0xB, 0x4, 0x1, 0x3, 0xF, 0x5, 0x9, 0x0, 0xA, 0xE, 0x7, 0x6, 0x8, 0x2, 0xC,
        0x1, 0xF, 0xD, 0x0, 0x5, 0x7, 0xA, 0x4, 0x9, 0x2, 0x3, 0xE, 0x6, 0xB, 0x8, 0xC
    ],
    'D-A': [
        0xA, 0x4, 0x5, 0x6, 0x8, 0x1, 0x3, 0x7, 0xD, 0xC, 0xE, 0x0, 0x9, 0x2, 0xB, 0xF,
        0x5, 0xF, 0x4, 0x0, 0x2, 0xD, 0xB, 0x9, 0x1, 0x7, 0x6, 0x3, 0xC, 0xE, 0xA, 0x8,
        0x7, 0xF, 0xC, 0xE, 0x9, 0x4, 0x1, 0x0, 0x3, 0xB, 0x5, 0x2, 0x6, 0xA, 0x8, 0xD,
        0x4, 0xA, 0x7, 0xC, 0x0, 0xF, 0x2, 0x8, 0xE, 0x1, 0x6, 0x5, 0xD, 0xB, 0x9, 0x3,
        0x7, 0x6, 0x4, 0xB, 0x9, 0xC, 0x2, 0xA, 0x1, 0x8, 0x0, 0xE, 0xF, 0xD, 0x3, 0x5,
        0x7, 0x6, 0x2, 0x4, 0xD, 0x9, 0xF, 0x0, 0xA, 0x1, 0x5, 0xB, 0x8, 0xE, 0xC, 0x3,
        0xD, 0xE, 0x4, 0x1, 0x7, 0x0, 0x5, 0xA, 0x3, 0xC, 0x8, 0xF, 0x6, 0x2, 0x9, 0xB,
        0x1, 0x3, 0xA, 0x9, 0x5, 0xB, 0x4, 0xF, 0x8, 0x6, 0x7, 0xE, 0xD, 0x0, 0x2, 0xC
    ],
    'D-SC': [
        0xb, 0xd, 0x7, 0x0, 0x5, 0x4, 0x1, 0xf, 0x9, 0xe, 0x6, 0xa, 0x3, 0xc, 0x8, 0x2,
        0x1, 0x2, 0x7, 0x9, 0xd, 0xb, 0xf, 0x8, 0xe, 0xc, 0x4, 0x0, 0x5, 0x6, 0xa, 0x3,
        0x5, 0x1, 0xd, 0x3, 0xf, 0x6, 0xc, 0x7, 0x9, 0x8, 0xb, 0x2, 0x4, 0xe, 0x0, 0xa,
        0xd, 0x1, 0xb, 0x4, 0x9, 0xc, 0xe, 0x0, 0x7, 0x5, 0x8, 0xf, 0x6, 0x2, 0xa, 0x3,
        0x2, 0xd, 0xa, 0xf, 0x9, 0xb, 0x3, 0x7, 0x8, 0xc, 0x5, 0xe, 0x6, 0x0, 0x1, 0x4,
        0x0, 0x4, 0x6, 0xc, 0x5, 0x3, 0x8, 0xd, 0xa, 0xb, 0xf, 0x2, 0x1, 0x9, 0x7, 0xe,
        0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xb, 0x0, 0x2, 0xe, 0x7, 0x9, 0xf, 0x4, 0x5, 0xd,
        0xa, 0xb, 0x6, 0x0, 0x1, 0x3, 0x4, 0x7, 0xe, 0xd, 0x5, 0xf, 0x8, 0x2, 0x9, 0xc
    ]
};

var C = new Uint8Array([
    0x69, 0x00, 0x72, 0x22, 0x64, 0xC9, 0x04, 0x23,
    0x8D, 0x3A, 0xDB, 0x96, 0x46, 0xE9, 0x2A, 0xC4,
    0x18, 0xFE, 0xAC, 0x94, 0x00, 0xED, 0x07, 0x12,
    0xC0, 0x86, 0xDC, 0xC2, 0xEF, 0x4C, 0xA9, 0x2B
]);

function signed(x) {
    return x >= 0x80000000 ? x - 0x100000000 : x;
}

function unsigned(x) {
    return x < 0 ? x + 0x100000000 : x;
}

// Set random values into Uint8Arry
// Random generator
function randomSeed(e) {
    GostRandom = GostRandom || root.GostRandom;
    var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto;
    if (randomSource.getRandomValues)
        randomSource.getRandomValues(e);
    else
        throw new NotSupportedError('Random generator not found');
}

// Get buffer
function buffer(d) {
    if (d instanceof CryptoOperationData)
        return d;
    else if (d && d.buffer && d.buffer instanceof CryptoOperationData)
        return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?
                d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;
    else
        throw new DataError('CryptoOperationData required');
}

// Get byte array
function byteArray(d) {
    return new Uint8Array(buffer(d));
}

// Clone byte array
function cloneArray(d) {
    return new Uint8Array(byteArray(d));
}


// Get int32 array
function intArray(d) {
    return new Int32Array(buffer(d));
}

// Swap bytes for version 2015
function swap32(b) {
    return ((b & 0xff) << 24)
            | ((b & 0xff00) << 8)
            | ((b >> 8) & 0xff00)
            | ((b >> 24) & 0xff);
}

// </editor-fold>

/*
    * Initial parameters and common algortithms of GOST R 34.12-15 
    * Algorithm "Kuznechik" 128bit
    * 
    */ // <editor-fold defaultstate="collapsed">

// Default initial vector
var defaultIV128 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

// Mult table for R function
var multTable = (function () {

    // Multiply two numbers in the GF(2^8) finite field defined 
    // by the polynomial x^8 + x^7 + x^6 + x + 1 = 0 */
    function gmul(a, b) {
        var p = 0, counter, carry;
        for (counter = 0; counter < 8; counter++) {
            if (b & 1)
                p ^= a;
            carry = a & 0x80; // detect if x^8 term is about to be generated 
            a = (a << 1) & 0xff;
            if (carry)
                a ^= 0xc3; // replace x^8 with x^7 + x^6 + x + 1 
            b >>= 1;
        }
        return p & 0xff;
    }

    // It is required only this values for R function
    //       0   1   2    3    4    5    6    7
    var x = [1, 16, 32, 133, 148, 192, 194, 251];
    var m = [];
    for (var i = 0; i < 8; i++) {
        m[i] = [];
        for (var j = 0; j < 256; j++)
            m[i][j] = gmul(x[i], j);
    }
    return m;
})();

// 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1
var kB = [4, 2, 3, 1, 6, 5, 0, 7, 0, 5, 6, 1, 3, 2, 4, 0];

// R - function
function funcR(d) {
    var sum = 0;
    for (var i = 0; i < 16; i++)
        sum ^= multTable[kB[i]][d[i]];

    for (var i = 16; i > 0; --i)
        d[i] = d[i - 1];
    d[0] = sum;
}

function funcReverseR(d) {
    var tmp = d[0];
    for (var i = 0; i < 15; i++)
        d[i] = d[i + 1];
    d[15] = tmp;

    var sum = 0;
    for (i = 0; i < 16; i++)
        sum ^= multTable[kB[i]][d[i]];
    d[15] = sum;
}

// Nonlinear transformation
var kPi = [
    252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77,
    233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193,
    249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79,
    5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31,
    235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204,
    181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135,
    21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177,
    50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87,
    223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3,
    224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74,
    167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65,
    173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59,
    7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137,
    225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97,
    32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82,
    89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182
];

var kReversePi = (function () {
    var m = [];
    for (var i = 0, n = kPi.length; i < n; i++)
        m[kPi[i]] = i;
    return m;
})();

function funcS(d) {
    for (var i = 0; i < 16; ++i)
        d[i] = kPi[d[i]];
}

function funcReverseS(d) {
    for (var i = 0; i < 16; ++i)
        d[i] = kReversePi[d[i]];
}

function funcX(a, b) {
    for (var i = 0; i < 16; ++i)
        a[i] ^= b[i];
}

function funcL(d) {
    for (var i = 0; i < 16; ++i)
        funcR(d);
}

function funcReverseL(d) {
    for (var i = 0; i < 16; ++i)
        funcReverseR(d);
}

function funcLSX(a, b) {
    funcX(a, b);
    funcS(a);
    funcL(a);
}

function funcReverseLSX(a, b) {
    funcX(a, b);
    funcReverseL(a);
    funcReverseS(a);
}

function funcF(inputKey, inputKeySecond, iterationConst) {
    var tmp = new Uint8Array(inputKey);
    funcLSX(inputKey, iterationConst);
    funcX(inputKey, inputKeySecond);
    inputKeySecond.set(tmp);
}

function funcC(number, d) {
    for (var i = 0; i < 15; i++)
        d[i] = 0;
    d[15] = number;
    funcL(d);
}

// </editor-fold>

/**
 * Key schedule for GOST R 34.12-15 128bits
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method keySchedule
 * @param {type} k
 * @returns {Uint8Array}
 */
function keySchedule128(k) // <editor-fold defaultstate="collapsed">
{
    var keys = new Uint8Array(160), c = new Uint8Array(16);
    keys.set(byteArray(k));
    for (var j = 0; j < 4; j++) {
        var j0 = 32 * j, j1 = 32 * (j + 1);
        keys.set(new Uint8Array(keys.buffer, j0, 32), j1);
        for (var i = 1; i < 9; i++) {
            funcC(j * 8 + i, c);
            funcF(new Uint8Array(keys.buffer, j1, 16),
                    new Uint8Array(keys.buffer, j1 + 16, 16), c);
        }
    }
    return keys;
} // </editor-fold>

/**
 * GOST R 34.12-15 128 bits encrypt/decrypt process 
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method round
 * @param {Uint8Array} k Scheduled key
 * @param {Uint8Array} d Data
 * @param {number} ofs Offsec
 * @param {number} e true - decrypt
 */
function process128(k, d, ofs, e) // <editor-fold defaultstate="collapsed">
{
    ofs = ofs || d.byteOffset;
    var r = new Uint8Array(d.buffer, ofs, 16);
    if (e) {
        for (var i = 0; i < 9; i++)
            funcReverseLSX(r, new Uint8Array(k.buffer, (9 - i) * 16, 16));

        funcX(r, new Uint8Array(k.buffer, 0, 16));
    } else {
        for (var i = 0; i < 9; i++)
            funcLSX(r, new Uint8Array(k.buffer, 16 * i, 16));

        funcX(r, new Uint8Array(k.buffer, 16 * 9, 16));
    }
} // </editor-fold>

/**
 * One GOST encryption round
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method round
 * @param {Int8Array} S sBox
 * @param {Int32Array} m 2x32 bits cipher block 
 * @param {Int32Array} k 32 bits key[i] 
 */
function round(S, m, k) // <editor-fold defaultstate="collapsed">
{
    var cm = (m[0] + k) & 0xffffffff;

    var om = S[  0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4);
    om |= S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4);
    om |= S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4);
    om |= S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4);
    om |= S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4);
    om |= S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4);
    om |= S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4);
    om |= S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4);
    cm = om << 11 | om >>> (32 - 11);

    cm ^= m[1];
    m[1] = m[0];
    m[0] = cm;

} // </editor-fold>

/**
 * Process encrypt/decrypt block with key K using GOST 28147-89
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method process
 * @param k {Int32Array} 8x32 bits key 
 * @param d {Int32Array} 8x8 bits cipher block 
 * @param ofs {number} offset
 */
function process89(k, d, ofs) // <editor-fold defaultstate="collapsed">
{
    ofs = ofs || d.byteOffset;
    var s = this.sBox,
            m = new Int32Array(d.buffer, ofs, 2);

    for (var i = 0; i < 32; i++)
        round(s, m, k[i]);

    var r = m[0];
    m[0] = m[1];
    m[1] = r;
} // </editor-fold>

/**
 * Process encrypt/decrypt block with key K using GOST R 34.12-15 64bit block
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method process
 * @param k {Int32Array} 8x32 bits key 
 * @param d {Int32Array} 8x8 bits cipher block 
 * @param ofs {number} offset
 */
function process15(k, d, ofs) // <editor-fold defaultstate="collapsed">
{
    ofs = ofs || d.byteOffset;
    var s = this.sBox,
            m = new Int32Array(d.buffer, ofs, 2),
            r = swap32(m[0]);
    m[0] = swap32(m[1]);
    m[1] = r;

    for (var i = 0; i < 32; i++)
        round(s, m, k[i]);

    m[0] = swap32(m[0]);
    m[1] = swap32(m[1]);
} // </editor-fold>

/**
 * Key keySchedule algorithm for GOST 28147-89 64bit cipher
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method process
 * @param k {Uint8Array} 8 bit key array
 * @param e {boolean}  true - decrypt
 * @returns {Int32Array} keyScheduled 32-bit key
 */
function keySchedule89(k, e) // <editor-fold defaultstate="collapsed">
{
    var sch = new Int32Array(32),
            key = new Int32Array(buffer(k));

    for (var i = 0; i < 8; i++)
        sch[i] = key[i];

    if (e) {
        for (var i = 0; i < 8; i++)
            sch[i + 8] = sch[7 - i];

        for (var i = 0; i < 8; i++)
            sch[i + 16] = sch[7 - i];
    } else {
        for (var i = 0; i < 8; i++)
            sch[i + 8] = sch[i];

        for (var i = 0; i < 8; i++)
            sch[i + 16] = sch[i];
    }

    for (var i = 0; i < 8; i++)
        sch[i + 24] = sch[7 - i];

    return sch;
} // </editor-fold>

/**
 * Key keySchedule algorithm for GOST R 34.12-15 64bit cipher
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method process
 * @param k {Uint8Array} 8 bit key array
 * @param e {boolean}  true - decrypt
 * @returns {Int32Array} keyScheduled 32-bit key
 */
function keySchedule15(k, e) // <editor-fold defaultstate="collapsed">
{
    var sch = new Int32Array(32),
            key = new Int32Array(buffer(k));

    for (var i = 0; i < 8; i++)
        sch[i] = swap32(key[i]);

    if (e) {
        for (var i = 0; i < 8; i++)
            sch[i + 8] = sch[7 - i];

        for (var i = 0; i < 8; i++)
            sch[i + 16] = sch[7 - i];
    } else {
        for (var i = 0; i < 8; i++)
            sch[i + 8] = sch[i];

        for (var i = 0; i < 8; i++)
            sch[i + 16] = sch[i];
    }

    for (var i = 0; i < 8; i++)
        sch[i + 24] = sch[7 - i];

    return sch;
} // </editor-fold>

/**
 * Key schedule for RC2
 * 
 * https://tools.ietf.org/html/rfc2268
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method keySchedule
 * @param {Uint8Array} k
 * @returns {Uint16Array}
 */
var keyScheduleRC2 = (function () // <editor-fold defaultstate="collapsed">
{
    // an array of "random" bytes based on the digits of PI = 3.14159...
    var PITABLE = new Uint8Array([
        0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
        0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
        0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
        0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
        0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
        0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
        0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
        0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
        0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
        0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
        0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
        0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
        0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
        0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
        0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
        0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
    ]);

    return function (k)
    {
        var key = new Uint8Array(buffer(k)),
                T = Math.min(key.length, 128),
                T1 = this.effectiveLength,
                T8 = Math.floor((T1 + 7) / 8),
                TM = 0xff % Math.pow(2, 8 + T1 - 8 * T8);

        var L = new Uint8Array(128), K = new Uint16Array(L.buffer);
        for (var i = 0; i < T; i++)
            L[i] = key[i];
        for (var i = T; i < 128; i++)
            L[i] = PITABLE[(L[i - 1] + L[i - T]) % 256];
        L[128 - T8] = PITABLE[L[128 - T8] & TM];
        for (var i = 127 - T8; i >= 0; --i)
            L[i] = PITABLE[L[i + 1] ^ L[i + T8]];
        return K;
    };
} // </editor-fold>
)();

/**
 * RC2 encrypt/decrypt process 
 * 
 * https://tools.ietf.org/html/rfc2268
 * 
 * @memberOf GostCipher
 * @private
 * @instance
 * @method round
 * @param {CryptoOperationData} k Scheduled key
 * @param {CryptoOperationData} d Data
 * @param {number} ofs Offsec
 * @param {number} e true - decrypt
 */
var processRC2 = (function () // <editor-fold defaultstate="collapsed">
{
    var K, j, R = new Uint16Array(4),
            s = new Uint16Array([1, 2, 3, 5]), reverse;

    function rol(R, s) {
        return (R << s | R >>> (16 - s)) & 0xffff;
    }

    function ror(R, s) {
        return (R >>> s | R << (16 - s)) & 0xffff;
    }

    function mix(i) {
        if (reverse) {
            R[i] = ror(R[i], s[i]);
            R[i] = R[i] - K[j] - (R[(i + 3) % 4] & R[(i + 2) % 4]) - ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
            j = j - 1;
        } else {
            R[i] = R[i] + K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) + ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
            j = j + 1;
            R[i] = rol(R[i], s[i]);
        }
    }

    function mash(i) {
        if (reverse) {
            R[i] = R[i] - K[R[(i + 3) % 4] & 63];
        } else {
            R[i] = R[i] + K[R[(i + 3) % 4] & 63];
        }
    }

    function perform(method, count) {
        count = count || 1;
        for (var j = 0; j < count; j++) {
            if (reverse) {
                for (var i = 3; i >= 0; --i)
                    method(i);
            } else {
                for (var i = 0; i < 4; i++)
                    method(i);
            }
        }
    }

    return function (k, d, ofs, e) {
        reverse = e;
        //  1. Initialize words R[0], ..., R[3] to contain the 64-bit
        //     ciphertext value.
        R = new Uint16Array(d.buffer, ofs || d.byteOffset, 4);
        //  2. Expand the key, so that words K[0], ..., K[63] become
        //     defined.
        K = k;
        //  3. Initialize j to zero (enc) j to 63 (dec).
        j = e ? 63 : 0;
        //  4. Perform five mixing rounds.
        perform(mix, 5);
        //  5. Perform one mashing round.
        perform(mash);
        //  6. Perform six mixing rounds.
        perform(mix, 6);
        //  7. Perform one mashing round.
        perform(mash);
        //  8. Perform five mixing rounds.
        perform(mix, 5);
    };
} // </editor-fold>
)();

/**
 * Algorithm name GOST 28147-ECB<br><br>
 * 
 * encryptECB (K, D) is D, encrypted with key k using GOST 28147/GOST R 34.13 in 
 * "prostaya zamena" (Electronic Codebook, ECB) mode. 
 * @memberOf GostCipher
 * @method encrypt
 * @instance
 * @param k {CryptoOperationData} 8x32 bit key 
 * @param d {CryptoOperationData} 8 bits message
 * @return {CryptoOperationData} result
 */
function encryptECB(k, d) // <editor-fold defaultstate="collapsed">
{
    var p = this.pad(byteArray(d)),
            n = this.blockSize,
            b = p.byteLength / n,
            key = this.keySchedule(k);

    for (var i = 0; i < b; i++)
        this.process(key, p, n * i);

    return p.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-ECB<br><br>
 * 
 * decryptECB (K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 in   
 * "prostaya zamena"  (Electronic Codebook, ECB) mode.
 * 
 * @memberOf GostCipher
 * @method decrypt
 * @instance
 * @param k {CryptoOperationData} 8x32 bits key 
 * @param d {CryptoOperationData} 8 bits message
 * @return {CryptoOperationData} result
 */
function decryptECB(k, d) // <editor-fold defaultstate="collapsed">
{
    var p = cloneArray(d),
            n = this.blockSize,
            b = p.byteLength / n,
            key = this.keySchedule(k, 1);

    for (var i = 0; i < b; i++)
        this.process(key, p, n * i, 1);

    return this.unpad(p).buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CFB<br><br>
 * 
 * encryptCFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie s obratnoj svyaziyu" (Cipher Feedback, CFB) mode, and IV is   
 * used as the initialization vector.
 * 
 * @memberOf GostCipher
 * @method encrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function encryptCFB(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            c = cloneArray(d),
            m = s.length,
            t = new Uint8Array(m),
            b = this.shiftBits >> 3,
            cb = c.length, r = cb % b, q = (cb - r) / b,
            key = this.keySchedule(k);

    for (var i = 0; i < q; i++) {

        for (var j = 0; j < m; j++)
            t[j] = s[j];

        this.process(key, s);

        for (var j = 0; j < b; j++)
            c[i * b + j] ^= s[j];

        for (var j = 0; j < m - b; j++)
            s[j] = t[b + j];

        for (var j = 0; j < b; j++)
            s[m - b + j] = c[i * b + j];

        k = this.keyMeshing(k, s, i, key);
    }

    if (r > 0) {
        this.process(key, s);

        for (var i = 0; i < r; i++)
            c[q * b + i] ^= s[i];
    }
    return c.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CFB<br><br>
 * 
 * decryptCFB (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie s obratnoj svyaziyu po shifrotekstu" (Cipher Feedback, CFB) mode, and IV is   
 * used as the initialization vector.
 * 
 * @memberOf GostCipher
 * @method decrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function decryptCFB(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            c = cloneArray(d),
            m = s.length,
            t = new Uint8Array(m),
            b = this.shiftBits >> 3,
            cb = c.length, r = cb % b, q = (cb - r) / b,
            key = this.keySchedule(k);

    for (var i = 0; i < q; i++) {

        for (var j = 0; j < m; j++)
            t[j] = s[j];

        this.process(key, s);

        for (var j = 0; j < b; j++) {
            t[j] = c[i * b + j];
            c[i * b + j] ^= s[j];
        }

        for (var j = 0; j < m - b; j++)
            s[j] = t[b + j];

        for (var j = 0; j < b; j++)
            s[m - b + j] = t[j];

        k = this.keyMeshing(k, s, i, key);
    }

    if (r > 0) {
        this.process(key, s);

        for (var i = 0; i < r; i++)
            c[q * b + i] ^= s[i];
    }
    return c.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-OFB<br><br>
 * 
 * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is   
 * used as the initialization vector.
 * 
 * @memberOf GostCipher
 * @method encrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv 8x8 optional bits initial vector
 * @return {CryptoOperationData} result
 */
/**
 * Algorithm name GOST 28147-OFB<br><br>
 * 
 * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is   
 * used as the initialization vector.
 * 
 * @memberOf GostCipher
 * @method decrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function processOFB(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            c = cloneArray(d),
            m = s.length,
            t = new Uint8Array(m),
            b = this.shiftBits >> 3,
            p = new Uint8Array(b),
            cb = c.length, r = cb % b, q = (cb - r) / b,
            key = this.keySchedule(k);

    for (var i = 0; i < q; i++) {

        for (var j = 0; j < m; j++)
            t[j] = s[j];

        this.process(key, s);

        for (var j = 0; j < b; j++)
            p[j] = s[j];

        for (var j = 0; j < b; j++)
            c[i * b + j] ^= s[j];

        for (var j = 0; j < m - b; j++)
            s[j] = t[b + j];

        for (var j = 0; j < b; j++)
            s[m - b + j] = p[j];

        k = this.keyMeshing(k, s, i, key);
    }

    if (r > 0) {
        this.process(key, s);

        for (var i = 0; i < r; i++)
            c[q * b + i] ^= s[i];
    }
    return c.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CTR<br><br>
 * 
 * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the   
 * initialization vector.
 * @memberOf GostCipher
 * @method encrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv 8x8 optional bits initial vector
 * @return {CryptoOperationData} result
 */
/**
 * Algorithm name GOST 28147-CTR<br><br>
 * 
 * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the   
 * initialization vector.
 * @memberOf GostCipher
 * @method decrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function processCTR89(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            c = cloneArray(d),
            b = this.blockSize,
            t = new Int8Array(b),
            cb = c.length, r = cb % b, q = (cb - r) / b,
            key = this.keySchedule(k),
            syn = new Int32Array(s.buffer);

    this.process(key, s);

    for (var i = 0; i < q; i++) {
        syn[0] = (syn[0] + 0x1010101) & 0xffffffff;
        // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff);
        var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov
        syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff);

        for (var j = 0; j < b; j++)
            t[j] = s[j];

        this.process(key, syn);

        for (var j = 0; j < b; j++)
            c[i * b + j] ^= s[j];

        for (var j = 0; j < b; j++)
            s[j] = t[j];

        k = this.keyMeshing(k, s, i, key);
    }
    if (r > 0) {
        syn[0] = (syn[0] + 0x1010101) & 0xffffffff;
        // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff);
        var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov
        syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff);

        this.process(key, syn);

        for (var i = 0; i < r; i++)
            c[q * b + i] ^= s[i];
    }
    return c.buffer;
} // </editor-fold>

function processCTR15(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var c = cloneArray(d),
            n = this.blockSize,
            b = this.shiftBits >> 3,
            cb = c.length, r = cb % b, q = (cb - r) / b,
            s = new Uint8Array(n),
            t = new Int32Array(n),
            key = this.keySchedule(k);

    s.set(iv || this.iv);
    for (var i = 0; i < q; i++) {

        for (var j = 0; j < n; j++)
            t[j] = s[j];

        this.process(key, s);

        for (var j = 0; j < b; j++)
            c[b * i + j] ^= s[j];

        for (var j = 0; j < n; j++)
            s[j] = t[j];

        for (var j = n - 1; i >= 0; --i) {
            if (s[j] > 0xfe) {
                s[j] -= 0xfe;
            } else {
                s[j]++;
                break;
            }
        }
    }

    if (r > 0) {
        this.process(key, s);
        for (var j = 0; j < r; j++)
            c[b * q + j] ^= s[j];
    }

    return c.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CBC<br><br>
 * 
 * encryptCBC (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13   
 * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization 
 * vector.
 * 
 * @memberOf GostCipher
 * @method encrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function encryptCBC(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            n = this.blockSize,
            m = s.length,
            c = this.pad(byteArray(d)),
            key = this.keySchedule(k);

    for (var i = 0, b = c.length / n; i < b; i++) {

        for (var j = 0; j < n; j++)
            s[j] ^= c[i * n + j];

        this.process(key, s);

        for (var j = 0; j < n; j++)
            c[i * n + j] = s[j];

        if (m !== n) {
            for (var j = 0; j < m - n; j++)
                s[j] = s[n + j];

            for (var j = 0; j < n; j++)
                s[j + m - n] = c[i * n + j];
        }

        k = this.keyMeshing(k, s, i, key);
    }

    return c.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CBC<br><br>
 * 
 * decryptCBC (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13   
 * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization 
 * vector.
 * 
 * @memberOf GostCipher
 * @method decrypt
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function decryptCBC(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var s = new Uint8Array(iv || this.iv),
            n = this.blockSize,
            m = s.length,
            c = cloneArray(d),
            next = new Uint8Array(n),
            key = this.keySchedule(k, 1);

    for (var i = 0, b = c.length / n; i < b; i++) {

        for (var j = 0; j < n; j++)
            next[j] = c[i * n + j];

        this.process(key, c, i * n, 1);

        for (var j = 0; j < n; j++)
            c[i * n + j] ^= s[j];

        if (m !== n) {
            for (var j = 0; j < m - n; j++)
                s[j] = s[n + j];
        }

        for (var j = 0; j < n; j++)
            s[j + m - n] = next[j];

        k = this.keyMeshing(k, s, i, key, 1);
    }

    return this.unpad(c).buffer;
} // </editor-fold>

/**
 * The generateKey method returns a new generated key.
 * 
 * @memberOf GostCipher
 * @method generateKey
 * @instance
 * @return {CryptoOperationData} result
 */

function generateKey() // <editor-fold defaultstate="collapsed">
{
    // Simple generate 256 bit random seed
    var k = new Uint8Array(this.keySize);
    randomSeed(k);
    return k.buffer;
} // </editor-fold>


/**
 * makeIMIT (K, D) is the 32-bit result of the GOST 28147/GOST R 34.13 in   
 * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV   
 * as initialization vector.  Note that the standard specifies its use   
 * in this mode only with an initialization vector of zero.
 * 
 * @memberOf GostCipher
 * @method processMAC
 * @private
 * @instance
 * @param {Int32Array} key 8x32 bits key 
 * @param {Int32Array} s 8x8 sum array
 * @param {Uint8Array} d 8 bits array with data 
 * @return {Uint8Array} result
 */
function processMAC89(key, s, d) // <editor-fold defaultstate="collapsed">
{
    var c = zeroPad.call(this, byteArray(d)),
            n = this.blockSize,
            q = c.length / n,
            sBox = this.sBox,
            sum = new Int32Array(s.buffer);

    for (var i = 0; i < q; i++) {

        for (var j = 0; j < n; j++)
            s[j] ^= c[i * n + j];

        for (var j = 0; j < 16; j++) // 1-16 steps
            round(sBox, sum, key[j]);
    }
} // </editor-fold>

function processKeyMAC15(s) // <editor-fold defaultstate="collapsed">
{
    var t = 0, n = s.length;
    for (var i = n - 1; i >= 0; --i) {
        var t1 = s[i] >>> 7;
        s[i] = (s[i] << 1) & 0xff | t;
        t = t1;
    }
    if (t !== 0) {
        if (n === 16)
            s[15] ^= 0x87;
        else
            s[7] ^= 0x1b;
    }
} // </editor-fold>

function processMAC15(key, s, d) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize,
            sBox = this.sBox, c = byteArray(d),
            r = new Uint8Array(n);
    // R
    this.process(key, r);
    // K1
    processKeyMAC15(r);
    if (d.byteLength % n !== 0) {
        c = bitPad.call(this, byteArray(d));
        // K2
        processKeyMAC15(r);
    }

    for (var i = 0, q = c.length / n; i < q; i++) {

        for (var j = 0; j < n; j++)
            s[j] ^= c[i * n + j];

        if (i === q - 1) {// Last block
            for (var j = 0; j < n; j++)
                s[j] ^= r[j];
        }

        this.process(key, s);
    }
} // </editor-fold>

/**
 * signMAC (K, D, IV) is the 32-bit result of the GOST 28147/GOST R 34.13 in   
 * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV   
 * as initialization vector.  Note that the standard specifies its use   
 * in this mode only with an initialization vector of zero.
 * 
 * @memberOf GostCipher
 * @method sign
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv initial vector
 * @return {CryptoOperationData} result
 */
function signMAC(k, d, iv) // <editor-fold defaultstate="collapsed">
{
    var key = this.keySchedule(k),
            s = new Uint8Array(iv || this.iv),
            m = Math.ceil(this.macLength >> 3) || this.blockSize >> 1;

    this.processMAC(key, s, d);

    var mac = new Uint8Array(m); // mac size
    mac.set(new Uint8Array(s.buffer, 0, m));
    return mac.buffer;
} // </editor-fold>

/**
 * verifyMAC (K, M, D, IV) the 32-bit result verification of the GOST 28147/GOST R 34.13 in   
 * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV   
 * as initialization vector.  Note that the standard specifies its use   
 * in this mode only with an initialization vector of zero.
 * 
 * @memberOf GostCipher
 * @method verify
 * @instance
 * @param {CryptoOperationData} k 8x32 bits key 
 * @param {CryptoOperationData} m 8 bits array with signature
 * @param {CryptoOperationData} d 8 bits array with data 
 * @param {CryptoOperationData} iv 8x8 optional bits initial vector
 * @return {boolen} MAC verified = true
 */
function verifyMAC(k, m, d, iv) // <editor-fold defaultstate="collapsed">
{
    var mac = new Uint8Array(signMAC.call(this, k, d, iv)),
            test = byteArray(m);
    if (mac.length !== test.length)
        return false;
    for (var i = 0, n = mac.length; i < n; i++)
        if (mac[i] !== test[i])
            return false;
    return true;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-KW<br><br>
 * 
 * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147/GOST R 34.13 KEK.
 * Ref. rfc4357 6.1 GOST 28147-89 Key Wrap
 * Note: This algorithm MUST NOT be used with a KEK produced by VKO GOST   
 * R 34.10-94, because such a KEK is constant for every sender-recipient   
 * pair.  Encrypting many different content encryption keys on the same   
 * constant KEK may reveal that KEK.
 * 
 * @memberOf GostCipher
 * @method wrapKey
 * @instance
 * @param {CryptoOperationData} kek Key encryption key
 * @param {CryptoOperationData} cek Content encryption key
 * @returns {CryptoOperationData} Encrypted cek
 */
function wrapKeyGOST(kek, cek) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);
    // 1) For a unique symmetric KEK, generate 8 octets at random and call 
    // the result UKM.  For a KEK, produced by VKO GOST R 34.10-2001, use 
    // the UKM that was used for key derivation.    
    if (!this.ukm) 
        throw new DataError('UKM must be defined');
    var ukm = new Uint8Array(this.ukm);
    // 2) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK).       
    // Call the result CEK_MAC. 
    var mac = signMAC.call(this, kek, cek, ukm);
    // 3) Encrypt the CEK in ECB mode using the KEK.  Call the ciphertext CEK_ENC.
    var enc = encryptECB.call(this, kek, cek);
    // 4) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC).
    var r = new Uint8Array(len);
    r.set(new Uint8Array(enc), 0);
    r.set(new Uint8Array(mac), k);
    return r.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-KW<br><br>
 * 
 *  This algorithm decrypts GOST 28147-89 CEK with a GOST 28147 KEK.
 *  Ref. rfc4357 6.2 GOST 28147-89 Key Unwrap
 *  
 * @memberOf GostCipher
 * @method unwrapKey
 * @instance
 * @param {type} kek Key encryption key
 * @param {type} data Content encryption key
 * @return {CryptoOperationData} result
 */
function unwrapKeyGOST(kek, data) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);
    // 1) If the wrapped content-encryption key is not 44 octets, then error.
    var d = buffer(data);
    if (d.byteLength !== len)
        throw new DataError('Wrapping key size must be ' + len + ' bytes');
    // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, and CEK_MAC.  
    // UKM is the most significant (first) 8 octets. CEK_ENC is next 32 octets, 
    // and CEK_MAC is the least significant (last) 4 octets.    
    if (!this.ukm)
        throw new DataError('UKM must be defined');
    var ukm = new Uint8Array(this.ukm),
            enc = new Uint8Array(d, 0, k),
            mac = new Uint8Array(d, k, n >> 1);
    // 3) Decrypt CEK_ENC in ECB mode using the KEK.  Call the output CEK.
    var cek = decryptECB.call(this, kek, enc);
    // 4) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK), 
    // compare the result with CEK_MAC.  If they are not equal, then error.
    var check = verifyMAC.call(this, kek, mac, cek, ukm);
    if (!check)
        throw new DataError('Error verify MAC of wrapping key');
    return cek;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CPKW<br><br>
 * 
 * Given a random 64-bit UKM and a GOST 28147 key K, this algorithm   
 * creates a new GOST 28147-89 key K(UKM).
 * Ref. rfc4357 6.3 CryptoPro KEK Diversification Algorithm
 * 
 * @memberOf GostCipher
 * @method diversify
 * @instance
 * @private
 * @param {CryptoOperationData} kek Key encryption key
 * @param {CryptoOperationData} ukm Random generated value
 * @returns {CryptoOperationData} Diversified kek
 */
function diversifyKEK(kek, ukm) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize;

    // 1) Let K[0] = K;    
    var k = intArray(kek);
    // 2) UKM is split into components a[i,j]:       
    //    UKM = a[0]|..|a[7] (a[i] - byte, a[i,0]..a[i,7] - it’s bits) 
    var a = [];
    for (var i = 0; i < n; i++) {
        a[i] = [];
        for (var j = 0; j < 8; j++) {
            a[i][j] = (ukm[i] >>> j) & 0x1;
        }
    }
    // 3) Let i be 0.    
    // 4) K[1]..K[8] are calculated by repeating the following algorithm       
    //    eight times:     
    for (var i = 0; i < n; i++) {
        //     A) K[i] is split into components k[i,j]:
        //        K[i] = k[i,0]|k[i,1]|..|k[i,7] (k[i,j] - 32-bit integer)
        //     B) Vector S[i] is calculated:        
        //        S[i] = ((a[i,0]*k[i,0] + ... + a[i,7]*k[i,7]) mod 2^32) |        
        //         (((~a[i,0])*k[i,0] + ... + (~a[i,7])*k[i,7]) mod 2^32);     
        var s = new Int32Array(2);
        for (var j = 0; j < 8; j++) {
            if (a[i][j])
                s[0] = (s[0] + k[j]) & 0xffffffff;
            else
                s[1] = (s[1] + k[j]) & 0xffffffff;
        }
        //     C) K[i+1] = encryptCFB (S[i], K[i], K[i])
        var iv = new Uint8Array(s.buffer);
        k = new Int32Array(encryptCFB.call(this, k, k, iv));
        //     D) i = i + 1
    }
    // 5) Let K(UKM) be K[8].
    return k;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CPKW<br><br>
 * 
 * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK.   
 * It can be used with any KEK (e.g., produced by VKO GOST R 34.10-94 or   
 * VKO GOST R 34.10-2001) because a unique UKM is used to diversify the KEK.
 * Ref. rfc4357 6.3  CryptoPro Key Wrap
 * 
 * @memberOf GostCipher
 * @method wrapKey
 * @instance
 * @param {CryptoOperationData} kek Key encryption key
 * @param {CryptoOperationData} cek Content encryption key
 * @returns {CryptoOperationData} Encrypted cek
 */
function wrapKeyCP(kek, cek) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);
    // 1) For a unique symmetric KEK or a KEK produced by VKO GOST R       
    // 34.10-94, generate 8 octets at random.  Call the result UKM.  For       
    // a KEK, produced by VKO GOST R 34.10-2001, use the UKM that was       
    // used for key derivation.
    if (!this.ukm) 
        throw new DataError('UKM must be defined');
    var ukm = new Uint8Array(this.ukm);
    // 2) Diversify KEK, using the CryptoPro KEK Diversification Algorithm,       
    // described in Section 6.5.  Call the result KEK(UKM).
    var dek = diversifyKEK.call(this, kek, ukm);
    // 3) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM),       
    // CEK).  Call the result CEK_MAC.    
    var mac = signMAC.call(this, dek, cek, ukm);
    // 4) Encrypt CEK in ECB mode using KEK(UKM).  Call the ciphertext       
    // CEK_ENC.    
    var enc = encryptECB.call(this, dek, cek);
    // 5) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC).
    var r = new Uint8Array(len);
    r.set(new Uint8Array(enc), 0);
    r.set(new Uint8Array(mac), k);
    return r.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CPKW<br><br>
 * 
 * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. 
 * Ref. rfc4357 6.4 CryptoPro Key Unwrap
 *
 * @memberOf GostCipher
 * @method unwrapKey
 * @instance
 * @param {CryptoOperationData} kek Key encryption key
 * @param {CryptoOperationData} data Encrypted content encryption keu
 * @return {CryptoOperationData} result Decrypted content encryption keu
 */
function unwrapKeyCP(kek, data) // <editor-fold defaultstate="collapsed">
{
    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);
    // 1) If the wrapped content-encryption key is not 44 octets, then error.    
    var d = buffer(data);
    if (d.byteLength !== len)
        throw new DataError('Wrapping key size must be ' + len + ' bytes');
    // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC,       
    // and CEK_MAC.  UKM is the most significant (first) 8 octets.       
    // CEK_ENC is next 32 octets, and CEK_MAC is the least significant       
    // (last) 4 octets.    
    if (!this.ukm)
        throw new DataError('UKM must be defined');
    var ukm = new Uint8Array(this.ukm),
            enc = new Uint8Array(d, 0, k),
            mac = new Uint8Array(d, k, n >> 1);
    // 3) Diversify KEK using the CryptoPro KEK Diversification Algorithm,       
    // described in section 6.5.  Call the result KEK(UKM).    
    var dek = diversifyKEK.call(this, kek, ukm);
    // 4) Decrypt CEK_ENC in ECB mode using KEK(UKM).  Call the output CEK.    
    var cek = decryptECB.call(this, dek, enc);
    // 5) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM),       
    // CEK), compare the result with CEK_MAC.  If they are not equal, 
    // then it is an error.
    var check = verifyMAC.call(this, dek, mac, cek, ukm);
    if (!check)
        throw new DataError('Error verify MAC of wrapping key');
    return cek;
} // </editor-fold>

/**
 * SignalCom master key packing algorithm
 * 
 * kek stored in 3 files - kek.opq, mk.db3, masks.db3
 * kek.opq - always 36 bytes length = 32 bytes encrypted kek + 4 bytes mac of decrypted kek
 * mk.db3 - 6 bytes header (1 byte magic code 0x22 + 1 byte count of masks + 4 bytes mac of 
 * xor summarizing masks value) + attached masks 
 * masks.db3 - detached masks. 
 * Total length  of attached + detached masks = 32 bits * count of masks
 * Default value of count 8 = (7 attached + 1 detached). But really no reason for such 
 * separation - all masks xor summarizing - order is not matter.
 * Content of file rand.opq can used as ukm. Don't forget change file content after using. 
 * 
 * For usb-token files has names: 
 * a001 - mk.db3, b001 - masks.db3, c001 - kek.opq, d001 - rand.opq
 * For windows registry
 * 00000001 - mk.db3, 00000002 - masks.db3, 00000003 - key.opq, 00000004 - rand.opq, 
 * 00000006 - keys\00000001.key, 0000000A - certificate
 * 
 * @memberOf GostCipher
 * @method packKey
 * @instance
 * @private
 * @param {CryptoOperationData} unpacked - clear main key 32 bytes
 * @param {CryptoOperationData} ukm - random vector for packing - 32 bytes * (count of masks - 1)
 * @returns {CryptoOperationData} packed master key - concatination of mk.db3 + masks.db3
 */
function packKeySC(unpacked, ukm) // <editor-fold defaultstate="collapsed">
{
    var m = this.blockSize >> 1, k = this.keySize;
    var mcount = 8;
    var key = new Uint8Array(buffer(unpacked));
    if (key.byteLength !== k)
        throw new DataError('Wrong cleartext size ' + key.byteLength + ' bytes');
    // Check or generate UKM
    ukm = ukm || this.ukm;
    if (ukm) {
        ukm = new Uint8Array(buffer(ukm));
        if (ukm.byteLength > 0 && ukm.byteLength % k === 0)
            mcount = ukm.byteLength / k + 1;
        else
            throw new DataError('Wrong rand size ' + ukm.byteLength + ' bytes');
    } else
        randomSeed(ukm = new Uint8Array((mcount - 1) * k));
    // Output array
    var d = new Uint8Array(mcount * k + m + 2), b = d.buffer;
    // Calculate MAC
    var zero32 = new Uint8Array(k);
    var mac = signMAC.call(this, key, zero32);
    d[0] = 0x22; // Magic code
    d[1] = mcount; // Count of masks
    d.set(new Uint8Array(mac), 2);
    d.set(ukm, k + m + 2);
    for (var i = 1; i < mcount; i++) {
        var mask = new Uint8Array(b, 2 + m + k * i);
        for (var j = 0; j < k; j++)
            key[j] ^= mask[j];
    }
    d.set(key, m + 2);
    return d.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-SCKW<br><br>
 * 
 * SignalCom master key unpacking algorithm
 * 
 * @memberOf GostCipher
 * @method unpackKey
 * @instance
 * @private
 * @param {CryptoOperationData} packed - concatination of mk.db3 + masks.db3
 * @returns {CryptoOperationData} unpacked master key
 */
function unpackKeySC(packed) // <editor-fold defaultstate="collapsed">
{
    var m = this.blockSize >> 1, k = this.keySize;
    var b = buffer(packed);
    // Unpack master key
    var magic = new Uint8Array(b, 0, 1)[0];
    if (magic !== 0x22)
        throw new DataError('Invalid magic number');
    var mcount = new Uint8Array(b, 1, 1)[0];
    var mac = new Uint8Array(b, 2, m); // MAC for summarized mask
    // Compute packKey xor summing for all masks
    var key = new Uint8Array(k);
    for (var i = 0; i < mcount; i++) {
        var mask = new Uint8Array(b, 2 + m + k * i, k);
        for (var j = 0; j < k; j++)
            key[j] ^= mask[j];
    }
    // Test MAC for packKey with default sBox on zero 32 bytes array
    var zero32 = new Uint8Array(k);
    var test = verifyMAC.call(this, key, mac, zero32);
    if (!test) {
        // Try to use different sBoxes
        var names = ['E-A', 'E-B', 'E-C', 'E-D', 'E-SC'];
        for (var i = 0, n = names.length; i < n; i++) {
            this.sBox = sBoxes[names[i]];
            test = verifyMAC.call(this, key, mac, zero32);
            if (test)
                break;
        }
    }
    if (!test)
        throw new DataError('Invalid main key MAC');
    return key.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-SCKW<br><br>
 * 
 * SignalCom Key Wrapping algorithm
 * 
 * @memberOf GostCipher
 * @method wrapKey
 * @instance
 * @param {CryptoOperationData} kek - clear kek or concatination of mk.db3 + masks.db3
 * @param {CryptoOperationData} cek - key for wrapping 
 * @returns {CryptoOperationData} wrapped key - file kek.opq
 */
function wrapKeySC(kek, cek) // <editor-fold defaultstate="collapsed">
{
    var m = this.blockSize >> 1, n = this.keySize;
    var k = buffer(kek);
    var c = buffer(cek);
    if (k.byteLength !== n)
        k = unpackKeySC.call(this, k);
    var enc = encryptECB.call(this, k, c);
    var mac = signMAC.call(this, k, c);
    var d = new Uint8Array(m + n);
    d.set(new Uint8Array(enc), 0);
    d.set(new Uint8Array(mac), n);
    return d.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-SCKW<br><br>
 * 
 * SignalCom Key UnWrapping algorithm
 * 
 * @memberOf GostCipher
 * @method unwrapKey
 * @instance
 * @param {CryptoOperationData} kek - concatination of files mk.db3 + masks.db3 or clear kek 
 * @param {CryptoOperationData} cek - wrapping key - file kek.opq
 * @return {CryptoOperationData} result
 */
function unwrapKeySC(kek, cek) // <editor-fold defaultstate="collapsed">
{
    var m = this.blockSize >> 1, n = this.keySize;
    var k = buffer(kek);
    var c = buffer(cek);
    if (k.byteLength !== n)
        k = unpackKeySC.call(this, k);
    var enc = new Uint8Array(c, 0, n); // Encrypted kek
    var mac = new Uint8Array(c, n, m); // MAC for clear kek
    var d = decryptECB.call(this, k, enc);
    if (!verifyMAC.call(this, k, mac, d))
        throw new DataError('Invalid key MAC');
    return d;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-SCKW<br><br>
 * 
 * SignalCom master key generation for wrapping
 * 
 * @memberOf GostCipher
 * @method generateKey
 * @instance
 * @return {CryptoOperationData} result
 */
function generateWrappingKeySC() // <editor-fold defaultstate="collapsed">
{
    return packKeySC.call(this, generateKey.call(this));
} // </editor-fold>

function maskKey(mask, key, inverse, keySize) // <editor-fold defaultstate="collapsed">
{
    var k = keySize / 4, 
            m32 = new Int32Array(buffer(mask)),
            k32 = new Int32Array(buffer(key)), 
            r32 = new Int32Array(k);
    if (inverse)
        for (var i = 0; i < k; i++) 
            r32[i] = (k32[i] + m32[i]) & 0xffffffff;
    else
        for (var i = 0; i < k; i++) 
            r32[i] = (k32[i] - m32[i]) & 0xffffffff;
    return r32.buffer;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-MASK<br><br>
 * 
 * This algorithm wrap key mask
 * 
 * @memberOf GostCipher
 * @method wrapKey
 * @instance
 * @param {CryptoOperationData} mask The mask
 * @param {CryptoOperationData} key The key
 * @returns {CryptoOperationData} The masked key
 */
function wrapKeyMask(mask, key) // <editor-fold defaultstate="collapsed">
{
    return maskKey(mask, key, this.procreator === 'VN', this.keySize);
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CPKW<br><br>
 * 
 * This algorithm unwrap key mask
 *
 * @memberOf GostCipher
 * @method unwrapKey
 * @instance
 * @param {CryptoOperationData} mask The mask
 * @param {CryptoOperationData} key The masked key
 * @return {CryptoOperationData} result The key
 */
function unwrapKeyMask(mask, key) // <editor-fold defaultstate="collapsed">
{
    return maskKey(mask, key, this.procreator !== 'VN', this.keySize);
} // </editor-fold>

/**
 * Algorithm name GOST 28147-CPKM<br><br>
 * 
 * Key meshing in according to rfc4357 2.3.2. CryptoPro Key Meshing
 * 
 * @memberOf GostCipher
 * @method keyMeshing
 * @instance
 * @private
 * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key 
 * @param {Uint8Array} s 8x8 bit sync (iv)
 * @param {Integer} i block index
 * @param {Int32Array} key 8x32 bit key schedule 
 * @param {boolean} e true - decrypt
 * @returns CryptoOperationData next 8x8 bit key
 */
function keyMeshingCP(k, s, i, key, e) // <editor-fold defaultstate="collapsed">
{
    if ((i + 1) * this.blockSize % 1024 === 0) { // every 1024 octets
        // K[i+1] = decryptECB (K[i], C);
        k = decryptECB.call(this, k, C);
        // IV0[i+1] = encryptECB (K[i+1],IVn[i])
        s.set(new Uint8Array(encryptECB.call(this, k, s)));
        // restore key schedule
        key.set(this.keySchedule(k, e));
    }
    return k;
} // </editor-fold>

/**
 *  Null Key Meshing in according to rfc4357 2.3.1
 * 
 * @memberOf GostCipher
 * @method keyMeshing
 * @instance
 * @private
 * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key 
 */
function noKeyMeshing(k) // <editor-fold defaultstate="collapsed">
{
    return k;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-NoPadding<br><br>
 * 
 * No padding.
 * 
 * @memberOf GostCipher
 * @method padding
 * @instance
 * @private
 * @param {Uint8Array} d array with source data
 * @returns {Uint8Array} result
 */
function noPad(d) // <editor-fold defaultstate="collapsed">
{
    return new Uint8Array(d);
} // </editor-fold>

/**
 * Algorithm name GOST 28147-PKCS5Padding<br><br>
 * 
 *  PKCS#5 padding: 8-x remaining bytes are filled with the value of      
 *  8-x.  If there’s no incomplete block, one extra block filled with      
 *  value 8 is added
 * 
 * @memberOf GostCipher
 * @method padding
 * @instance
 * @private
 * @param {Uint8Array} d array with source data
 * @returns {Uint8Array} result
 */
function pkcs5Pad(d) // <editor-fold defaultstate="collapsed">
{
    var n = d.byteLength,
            nb = this.blockSize,
            q = nb - n % nb,
            m = Math.ceil((n + 1) / nb) * nb,
            r = new Uint8Array(m);
    r.set(d);
    for (var i = n; i < m; i++)
        r[i] = q;
    return r;
} // </editor-fold>

function pkcs5Unpad(d) // <editor-fold defaultstate="collapsed">
{
    var m = d.byteLength,
            nb = this.blockSize,
            q = d[m - 1],
            n = m - q;
    if (q > nb)
        throw DataError('Invalid padding');
    var r = new Uint8Array(n);
    if (n > 0)
        r.set(new Uint8Array(d.buffer, 0, n));
    return r;
} // </editor-fold>


/**
 * Algorithm name GOST 28147-ZeroPadding<br><br>
 * 
 * Zero padding: 8-x remaining bytes are filled with zero
 * 
 * @memberOf GostCipher
 * @method padding
 * @instance
 * @private
 * @param {Uint8Array} d array with source data
 * @returns {Uint8Array} result
 */
function zeroPad(d) // <editor-fold defaultstate="collapsed">
{
    var n = d.byteLength,
            nb = this.blockSize,
            m = Math.ceil(n / nb) * nb,
            r = new Uint8Array(m);
    r.set(d);
    for (var i = n; i < m; i++)
        r[i] = 0;
    return r;
} // </editor-fold>


/**
 * Algorithm name GOST 28147-BitPadding<br><br>
 * 
 * Bit padding: P* = P || 1 || 000...0 If there’s no incomplete block, 
 * one extra block filled with 1 || 000...0
 * 
 * @memberOf GostCipher
 * @method padding
 * @instance
 * @private
 * @param {Uint8Array} d array with source data
 * @returns {Uint8Array} result
 */
function bitPad(d) // <editor-fold defaultstate="collapsed"> 
{
    var n = d.byteLength,
            nb = this.blockSize,
            m = Math.ceil((n + 1) / nb) * nb,
            r = new Uint8Array(m);
    r.set(d);
    r[n] = 1;
    for (var i = n + 1; i < m; i++)
        r[i] = 0;
    return r;
} // </editor-fold>

function bitUnpad(d) // <editor-fold defaultstate="collapsed"> 
{
    var m = d.byteLength,
            n = m;
    while (n > 1 && d[n - 1] === 0)
        n--;
    if (d[n - 1] !== 1)
        throw DataError('Invalid padding');
    n--;
    var r = new Uint8Array(n);
    if (n > 0)
        r.set(new Uint8Array(d.buffer, 0, n));
    return r;
} // </editor-fold>

/**
 * Algorithm name GOST 28147-RandomPadding<br><br>
 * 
 * Random padding: 8-x remaining bytes of the last block are set to      
 * random.
 * 
 * @memberOf GostCipher
 * @method padding
 * @instance
 * @private
 * @param {Uint8Array} d array with source data
 * @returns {Uint8Array} result
 */
function randomPad(d) // <editor-fold defaultstate="collapsed">
{
    var n = d.byteLength,
            nb = this.blockSize,
            q = nb - n % nb,
            m = Math.ceil(n / nb) * nb,
            r = new Uint8Array(m), e = new Uint8Array(r.buffer, n, q);
    r.set(d);
    randomSeed(e);
    return r;
} // </editor-fold>

/**
 * GOST 28147-89 Encryption Algorithm<br><br> 
 * 
 * References {@link http://tools.ietf.org/html/rfc5830}<br><br>
 * 
 * When keys and initialization vectors are converted to/from byte arrays, 
 * little-endian byte order is assumed.<br><br>
 * 
 * Normalized algorithm identifier common parameters:
 * 
 *  <ul>
 *      <li><b>name</b> Algorithm name 'GOST 28147' or 'GOST R 34.12'</li>
 *      <li><b>version</b> Algorithm version, number
 *          <ul>
 *              <li><b>1989</b> Current version of standard</li>
 *              <li><b>2015</b> New draft version of standard</li>
 *          </ul>
 *      </li>
 *      <li><b>length</b> Block length
 *          <ul>
 *              <li><b>64</b> 64 bits length (default)</li>
 *              <li><b>128</b> 128 bits length (only for version 2015)</li>
 *          </ul>
 *      </li>
 *      <li><b>mode</b> Algorithm mode, string
 *          <ul>
 *              <li><b>ES</b> Encryption mode (default)</li>
 *              <li><b>MAC</b> "imitovstavka" (MAC) mode</li>
 *              <li><b>KW</b> Key wrapping mode</li>
 *          </ul>
 *      </li>
 *      <li><b>sBox</b> Paramset sBox for GOST 28147-89, string. Used only if version = 1989</li>
 *  </ul>
 *  
 * Supported algorithms, modes and parameters:
 * 
 *  <ul>
 *      <li>Encript/Decrypt mode (ES)
 *          <ul>
 *              <li><b>block</b> Block mode, string. Default ECB</li>
 *              <li><b>keyMeshing</b> Key meshing mode, string. Default NO</li>
 *              <li><b>padding</b> Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others</li>
 *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>
 *          </ul>
 *      </li>
 *      <li>Sign/Verify mode (MAC)
 *          <ul>
 *              <li><b>macLength</b> Length of mac in bits (default - 32 bits)</li>
 *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>
 *          </ul>
 *      </li>
 *      <li>Wrap/Unwrap key mode (KW)
 *          <ul>
 *              <li><b>keyWrapping</b> Mode of keywrapping, string. Default NO - standard GOST key wrapping</li>
 *              <li><b>ukm</b> {@link CryptoOperationData} User key material. Default - random generated value</li>
 *          </ul>
 *      </li>
 *  </ul>
 *      
 * Supported paramters values:
 *      
 *  <ul>
 *      <li>Block modes (parameter 'block')
 *          <ul>
 *              <li><b>ECB</b> "prostaya zamena" (ECB) mode (default)</li>
 *              <li><b>CFB</b> "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode</li>
 *              <li><b>OFB</b> "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode</li>
 *              <li><b>CTR</b> "gammirovanie" (counter) mode</li>
 *              <li><b>CBC</b> Cipher-Block-Chaining (CBC) mode</li>
 *          </ul>
 *      </li>
 *      <li>Key meshing modes (parameter 'keyMeshing')
 *          <ul>
 *              <li><b>NO</b> No key wrapping (default)</li>
 *              <li><b>CP</b> CryptoPor Key key meshing</li>
 *          </ul>
 *      </li>
 *      <li>Padding modes (parameter 'padding')
 *          <ul>
 *              <li><b>NO</b> No padding only for CFB, OFB and CTR modes</li>
 *              <li><b>PKCS5</b> PKCS#5 padding mode</li>
 *              <li><b>ZERO</b> Zero bits padding mode</li>
 *              <li><b>RANDOM</b> Random bits padding mode</li>
 *              <li><b>BIT</b> One bit padding mode</li>
 *          </ul>
 *      </li>
 *      <li>Wrapping key modes (parameter 'keyWrapping')
 *          <ul>
 *              <li><b>NO</b> Ref. rfc4357 6.1 GOST 28147-89 Key wrapping</li>
 *              <li><b>CP</b> CryptoPro Key wrapping mode</li>
 *              <li><b>SC</b> SignalCom Key wrapping mode</li>
 *          </ul>
 *      </li>
 *  </ul>
 * 
 * @class GostCipher
 * @param {AlgorithmIndentifier} algorithm WebCryptoAPI algorithm identifier
 */
function GostCipher(algorithm) // <editor-fold defaultstate="collapsed">
{
    // Check little endian support
    if (!littleEndian)
        throw new NotSupportedError('Big endian platform not supported');
    algorithm = algorithm || {};
    this.keySize = 32;
    this.blockLength = algorithm.length || 64;
    this.blockSize = this.blockLength >> 3;

    this.name = (algorithm.name || (algorithm.version === 1 ? 'RC2' :
            algorithm.version === 1989 ? 'GOST 28147' : 'GOST R 34.12')) +
            (algorithm.version > 4 ? '-' + ((algorithm.version || 1989) % 100) : '') + '-' +
            (this.blockLength === 64 ? '' : this.blockLength + '-') +
            ((algorithm.mode === 'MAC') ? 'MAC-' + (algorithm.macLength || this.blockLength >> 1) :
                    (algorithm.mode === 'KW' || algorithm.keyWrapping) ?
                    ((algorithm.keyWrapping || 'NO') !== 'NO' ? algorithm.keyWrapping : '') + 'KW' :
                    (algorithm.block || 'ECB') + ((algorithm.block === 'CFB' || algorithm.block === 'OFB' ||
                    (algorithm.block === 'CTR' && algorithm.version === 2015)) &&
                    algorithm.shiftBits && algorithm.shiftBits !== this.blockLength ? '-' + algorithm.shiftBits : '') +
                    (algorithm.padding ? '-' + (algorithm.padding || (algorithm.block === 'CTR' ||
                            algorithm.block === 'CFB' || algorithm.block === 'OFB' ? 'NO' : 'ZERO')) + 'PADDING' : '') +
                    ((algorithm.keyMeshing || 'NO') !== 'NO' ? '-CPKEYMESHING' : '')) +
            (algorithm.procreator ? '/' + algorithm.procreator : '') +
            (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : '');

    // Algorithm procreator
    this.procreator = algorithm.procreator;
    
    switch (algorithm.version || 1989) {
        case 1:
            this.process = processRC2;
            this.keySchedule = keyScheduleRC2;
            this.blockLength = 64;
            this.effectiveLength = algorithm.length || 32;
            this.keySize = 8 * Math.ceil(this.effectiveLength / 8); // Max 128
            this.blockSize = this.blockLength >> 3;
            break;
        case 2015:
            this.version = 2015;
            if (this.blockLength === 64) {
                this.process = process15;
                this.keySchedule = keySchedule15;
            } else if (this.blockLength === 128) {
                this.process = process128;
                this.keySchedule = keySchedule128;
            } else
                throw new DataError('Invalid block length');
            this.processMAC = processMAC15;
            break;
        case 1989:
            this.version = 1989;
            this.process = process89;
            this.processMAC = processMAC89;
            this.keySchedule = keySchedule89;
            if (this.blockLength !== 64)
                throw new DataError('Invalid block length');
            break;
        default:
            throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported');
    }

    switch (algorithm.mode || (algorithm.keyWrapping && 'KW') || 'ES') {

        case 'ES':
            switch (algorithm.block || 'ECB') {
                case 'ECB':
                    this.encrypt = encryptECB;
                    this.decrypt = decryptECB;
                    break;
                case 'CTR':
                    if (this.version === 1989) {
                        this.encrypt = processCTR89;
                        this.decrypt = processCTR89;
                    } else {
                        this.encrypt = processCTR15;
                        this.decrypt = processCTR15;
                        this.shiftBits = algorithm.shiftBits || this.blockLength;
                    }
                    break
                case 'CBC':
                    this.encrypt = encryptCBC;
                    this.decrypt = decryptCBC;
                    break
                case 'CFB':
                    this.encrypt = encryptCFB;
                    this.decrypt = decryptCFB;
                    this.shiftBits = algorithm.shiftBits || this.blockLength;
                    break;
                case 'OFB':
                    this.encrypt = processOFB;
                    this.decrypt = processOFB;
                    this.shiftBits = algorithm.shiftBits || this.blockLength;
                    break;
                default:
                    throw new NotSupportedError('Block mode ' + algorithm.block + ' not supported');
            }
            switch (algorithm.keyMeshing) {
                case 'CP':
                    this.keyMeshing = keyMeshingCP;
                    break;
                default:
                    this.keyMeshing = noKeyMeshing;
            }
            if (this.encrypt === encryptECB || this.encrypt === encryptCBC) {
                switch (algorithm.padding) {
                    case 'PKCS5P':
                        this.pad = pkcs5Pad;
                        this.unpad = pkcs5Unpad;
                        break;
                    case 'RANDOM':
                        this.pad = randomPad;
                        this.unpad = noPad;
                        break;
                    case 'BIT':
                        this.pad = bitPad;
                        this.unpad = bitUnpad;
                        break;
                    default:
                        this.pad = zeroPad;
                        this.unpad = noPad;
                }
            } else {
                this.pad = noPad;
                this.unpad = noPad;
            }
            this.generateKey = generateKey;
            break;
        case 'MAC':
            this.sign = signMAC;
            this.verify = verifyMAC;
            this.generateKey = generateKey;
            this.macLength = algorithm.macLength || (this.blockLength >> 1);
            this.pad = noPad;
            this.unpad = noPad;
            this.keyMeshing = noKeyMeshing;
            break;
        case 'KW':
            this.pad = noPad;
            this.unpad = noPad;
            this.keyMeshing = noKeyMeshing;
            switch (algorithm.keyWrapping) {
                case 'CP':
                    this.wrapKey = wrapKeyCP;
                    this.unwrapKey = unwrapKeyCP;
                    this.generateKey = generateKey;
                    this.shiftBits = algorithm.shiftBits || this.blockLength;
                    break;
                case 'SC':
                    this.wrapKey = wrapKeySC;
                    this.unwrapKey = unwrapKeySC;
                    this.generateKey = generateWrappingKeySC;
                    break;
                default:
                    this.wrapKey = wrapKeyGOST;
                    this.unwrapKey = unwrapKeyGOST;
                    this.generateKey = generateKey;
            }
            break;
        case 'MASK':
            this.wrapKey = wrapKeyMask;
            this.unwrapKey = unwrapKeyMask;
            this.generateKey = generateKey;
            break;
        default:
            throw new NotSupportedError('Mode ' + algorithm.mode + ' not supported');
    }

    // Define sBox parameter
    var sBox = algorithm.sBox, sBoxName;
    if (!sBox)
        sBox = this.version === 2015 ? sBoxes['E-Z'] : this.procreator === 'SC' ? sBoxes['E-SC'] : sBoxes['E-A'];
    else if (typeof sBox === 'string') {
        sBoxName = sBox.toUpperCase();
        sBox = sBoxes[sBoxName];
        if (!sBox)
            throw new SyntaxError('Unknown sBox name: ' + algorithm.sBox);
    } else if (!sBox.length || sBox.length !== sBoxes['E-Z'].length)
        throw new SyntaxError('Length of sBox must be ' + sBoxes['E-Z'].length);
    this.sBox = sBox;
    // Initial vector
    if (algorithm.iv) {
        this.iv = new Uint8Array(algorithm.iv);
        if (this.iv.byteLength !== this.blockSize && this.version === 1989)
            throw new SyntaxError('Length of iv must be ' + this.blockLength + ' bits');
        else if (this.iv.byteLength !== this.blockSize >> 1 && this.encrypt === processCTR15)
            throw new SyntaxError('Length of iv must be ' + this.blockLength >> 1 + ' bits');
        else if (this.iv.byteLength % this.blockSize !== 0 && this.encrypt !== processCTR15)
            throw new SyntaxError('Length of iv must be a multiple of ' + this.blockLength + ' bits');
    } else
        this.iv = this.blockLength === 128 ? defaultIV128 : defaultIV;
    // User key material
    if (algorithm.ukm) {
        this.ukm = new Uint8Array(algorithm.ukm);
        if (this.ukm.byteLength * 8 !== this.blockLength)
            throw new SyntaxError('Length of ukm must be ' + this.blockLength + ' bits');
    }
} // </editor-fold>

export default GostCipher;
