UNPKG

kuznyechik_multi

Version:

Шифрование ГОСТ 34.12-2018 Кузнечик. Encryption GOST 34.12-2018 Kuznyechik || Grasshopper. Support || Поддержка ESM/CommonJS/ChildProcess/browser

358 lines (357 loc) 13.6 kB
import { cpus } from "os"; import { L_CONSTANT, PI, ARR_16_ZEROS } from "./constants.mjs"; import { fork } from "child_process"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; const FN_TRIM_START = (b) => { let index = b.findIndex((v) => v !== 0); if (~index) return b.subarray(index, b.length); return b; }; /** * Сложение/вычетание в поле Галуа эквивалент XOR * * XOR (исключающее или) для буферов, возвращает сам буфер */ export const XOR = (a, b) => { if (a.length !== b.length) throw "XOR a.length !== b.length"; return Buffer.from(a.map((x, i) => b[i] ^ x)); }; /** * Умножение в поле Галуа по модулю неприводимого многочлена x^{8}+x^{7}+x^{6}+x+1} */ export const GALOIS_MULT = (a, b) => { let ret = 0; let hiBbit = 0; for (let i = 0; i < 8; i++) { let _tmp = b & 1; if (_tmp === 1) ret ^= a; hiBbit = a & 0x80; a <<= 1; if (hiBbit === 0x80) a ^= 0x1c3; //0x1c3 = x^{8}+x^{7}+x^{6}+x+1} || 0xc3 = x^{7}+x^{6}+x+1 b >>= 1; } return ret; }; /** * S преобразование * * Операция замены байтов путем применения нелинейной биективного преобразования */ export const S_TRANSFORM = (buf) => { let ret = Buffer.from(ARR_16_ZEROS.slice()); buf.forEach((x, i) => ret[i] = PI[x]); return ret; }; /** * R преобразование * * Умножение + сдвиг */ export const R_TRANFORM = (buf) => { let ret = Buffer.from(buf); let a15 = 0; for (let i = 0; i < 16; i++) a15 ^= GALOIS_MULT(buf[i], L_CONSTANT[i]); for (let i = 15; i >= 1; i--) ret[i] = buf[i - 1]; ret[0] = a15; return ret; }; /** * Обратное R преобразование */ export const REV_R_TRANSFORM = (buf) => { let a0 = buf[0]; let ret = Buffer.from(ARR_16_ZEROS.slice()); for (let i = 0; i < 15; i++) { ret[i] = buf[i + 1]; a0 ^= GALOIS_MULT(buf[i + 1], L_CONSTANT[i]); } ret[15] = a0; return ret; }; /** * Обратное S преобразование * * Обратная операция замены байтов путем применения нелинейной биективного преобразования */ export const REV_S_TRANSFORM = (buf) => { let ret = Buffer.from(ARR_16_ZEROS.slice()); buf.forEach((x, i) => ret[i] = PI.indexOf(x)); return ret; }; /**L (линейное) преобразование */ export const L_TRANSFORM = (buf) => { let ret = Buffer.from(buf); for (let i = 0; i < 16; i++) ret = R_TRANFORM(ret); return ret; }; /**Обратное L (линейное) преобразование */ export const REV_L_TRANSFORM = (buf) => { let ret = Buffer.from(buf); for (let i = 0; i < 16; i++) ret = REV_R_TRANSFORM(ret); return ret; }; /** Преобразования ячейки Фейстеля */ export const FEISTEL_NETWORK = (key1, key2, iterC) => { let internal = XOR(key1, iterC); internal = S_TRANSFORM(internal); internal = L_TRANSFORM(internal); return [XOR(internal, key2), key1]; }; /** Расчет раундовых ключей */ export const EXPAND_KEYS = (masterkey) => { if (masterkey.length !== 32) throw "Ключ должен быть 32 байта или 256 бит\n" + "Получил " + masterkey.length + " байт"; let iterC = []; let keyL = Buffer.from(ARR_16_ZEROS.slice()); let keyR = Buffer.from(ARR_16_ZEROS.slice()); for (let i = 0; i < masterkey.length; i++) { let j = i % 16; let m = Buffer.from(ARR_16_ZEROS.slice()); m[15] = i; iterC.push(L_TRANSFORM(m)); if (i < 16) keyL[j] = masterkey[j]; else keyR[j] = masterkey[j]; } let ret = [Buffer.from(keyR), Buffer.from(keyL)]; for (let i = 0; i < 4; i++) { for (let j = 0; j < 8; j++) { let temp = FEISTEL_NETWORK(keyL, keyR, iterC[j + 8 * i]); keyR = Buffer.from(temp[0]); keyL = Buffer.from(temp[1]); } ret[2 * i + 2] = Buffer.from(keyL); ret[2 * i + 3] = Buffer.from(keyR); } return ret; }; function toBuffer(data) { if (typeof data === 'string') { if (/[^0123456789abcdef]/gi.test(data)) throw "Строка не HEX"; let arr = data.match(/.?./g); if (!arr) throw "match errore FN toBuffer"; return Buffer.from(arr.map(x => parseInt(x, 16))); } else return data; } export function ROUNDS_10(block, keys) { let temp = Buffer.from(block); for (let i = 0; i < 9; i++) { temp = XOR(temp, keys[i]); temp = S_TRANSFORM(temp); temp = L_TRANSFORM(temp); } return XOR(temp, keys[9]); } export function REV_ROUNDS_10(block, keys) { let temp = XOR(block, keys[9]); for (let i = 8; i >= 0; i--) { temp = REV_L_TRANSFORM(temp); temp = REV_S_TRANSFORM(temp); temp = XOR(temp, keys[i]); } return temp; } export const encryptSync = (data, masterkey) => { let keys = EXPAND_KEYS(toBuffer(masterkey)); let ret = []; let block = []; if (!!(data.length % 16)) data = Buffer.concat([Buffer.from(Array.from({ length: 16 - data.length % 16 }).map(x => 0)), data]); for (let i = 0; i < data.length; i++) { block.push(data[i]); if (block.length === 16) { ret.push(...ROUNDS_10(block, keys)); block = []; } } if (block.length > 0) ret.push(...ROUNDS_10(block, keys)); return Buffer.from(ret); }; /**Асинхронное шифрование по методу Кузнечик */ export async function encryptAsync(data, masterkey) { let keys = this?.EXPAND_KEYS || EXPAND_KEYS(toBuffer(masterkey)); if (!!(data.length % 16)) data = Buffer.concat([Buffer.from(Array.from({ length: 16 - data.length % 16 }).map(x => 0)), data]); let MAX_ITER = data.length / 16; let ArrPromise = []; for (let i = 0; i < MAX_ITER; i++) { ArrPromise.push(new Promise(function (resolve, reject) { resolve(ROUNDS_10(Buffer.from(data.subarray(i * 16, (i + 1) * 16)), keys)); })); } return Buffer.concat(await Promise.all(ArrPromise)); } /**Асинхронное дешифрование по методу Кузнечик */ export async function decryptAsync(data, masterkey, trimStart = true) { let trs = trimStart; if (this?.trimStart !== undefined) trs = this?.trimStart; let keys = this?.EXPAND_KEYS || EXPAND_KEYS(toBuffer(masterkey)); if (!!(data.length % 16)) data = Buffer.concat([Buffer.from(Array.from({ length: 16 - data.length % 16 }).map(x => 0)), data]); let MAX_ITER = data.length / 16; let ArrPromise = []; for (let i = 0; i < MAX_ITER; i++) { ArrPromise.push(new Promise(function (resolve, reject) { resolve(REV_ROUNDS_10(Buffer.from(data.subarray(i * 16, (i + 1) * 16)), keys)); })); } return trs ? FN_TRIM_START(Buffer.concat(await Promise.all(ArrPromise))) : Buffer.concat(await Promise.all(ArrPromise)); } export const decryptSync = (data, masterkey, trimStart = true) => { let keys = EXPAND_KEYS(toBuffer(masterkey)); let ret = []; let block = []; if (!!(data.length % 16)) data = Buffer.concat([Buffer.from(Array.from({ length: 16 - data.length % 16 }).map(x => 0)), data]); for (let i = 0; i < data.length; i++) { block.push(data[i]); if (block.length === 16) { ret.push(...REV_ROUNDS_10(block, keys)); block = []; } } if (block.length !== 0) ret.push(...REV_ROUNDS_10(block, keys)); return trimStart ? FN_TRIM_START(Buffer.from(ret)) : Buffer.from(ret); }; function countChild(data, MAX_BLOCK) { let max = cpus().length; if (MAX_BLOCK < max) max = MAX_BLOCK; let r = 0; if (data === 'all') r = max; else r = data; return r > max ? max : r; } async function CHILD_E_D_METHOD(i, child) { let sizeOf = i * this.BLOCKS_FOR_CHILD * this.BITES_FROM_CHILD; let MAX_SIZE = this.BLOCKS_FOR_CHILD * this.BITES_FROM_CHILD + sizeOf; if (MAX_SIZE > this.data.length) MAX_SIZE = this.data.length; let END_OF_DATA = this.BITES_FROM_CHILD + 0; for (let j = 0; j < this.BLOCKS_FOR_CHILD; j++) { if ((END_OF_DATA + sizeOf) > MAX_SIZE) END_OF_DATA = MAX_SIZE - sizeOf; await new Promise((res, rej) => { child.send({ type: this.method, data: this.data.subarray(sizeOf, sizeOf + END_OF_DATA), masterkey: this.MASTER_KEY_BUFF }); child.once("message", (resp) => { let bf = Buffer.from(resp); if (bf.length !== END_OF_DATA) throw "Получил данных больше чем нужно"; bf.copy(this.data, sizeOf, 0, bf.length); res(true); }); }); sizeOf += END_OF_DATA; } return this.data; } function SEARCHER_BITES_FROM_CHILD(size, BITES_FROM_CHILD) { if (!!BITES_FROM_CHILD && BITES_FROM_CHILD > 0) { let n = Math.ceil(BITES_FROM_CHILD); return n % 16 + n; } let i = 0; for (; i < 2; i++) if (Math.floor(size / ((1024 ** i))) === 0) break; if (!i) return 16; return 1024 * 0.5; } /**Многопоточное шифрование по методу Кузнечик */ export const encryptChilds = async (data, masterkey, childs, BITES_FROM_CHILD = 0) => { if (data.length > 2147483647) console.warn("encryptChilds не был расчитан на данные более 2 гб"); if (!!(data.length % 16)) data = Buffer.concat([Buffer.from(Array.from({ length: 16 - data.length % 16 }).map(x => 0)), data]); const C_BITES_FROM_CHILD = SEARCHER_BITES_FROM_CHILD(data.length, BITES_FROM_CHILD); const CHILD_MAX = countChild(childs, Math.ceil(data.length / C_BITES_FROM_CHILD)); const BLOCKS_FOR_CHILD = Math.ceil((data.length / C_BITES_FROM_CHILD) / CHILD_MAX); const MASTER_KEY_BUFF = toBuffer(masterkey); const CHILD_PATH = join(dirname(fileURLToPath(import.meta.url)), 'child.mjs'); const CHILDS = Array.from({ length: CHILD_MAX }).map(x => fork(CHILD_PATH)); const MAIN_ENCRYPT_FN = CHILD_E_D_METHOD.bind({ method: "encrypt", BITES_FROM_CHILD: C_BITES_FROM_CHILD, BLOCKS_FOR_CHILD: BLOCKS_FOR_CHILD, data: data, MASTER_KEY_BUFF: MASTER_KEY_BUFF }); let ARR_PROMISE_INIT = CHILDS.map(child => { return new Promise(function (res, rej) { let timer = setTimeout(() => { child.kill(0); res(false); }, 5 * 1000); child.once("message", (data) => { clearTimeout(timer); res(true); }); }); }); await Promise.all(ARR_PROMISE_INIT); ARR_PROMISE_INIT = CHILDS.map((child, i) => MAIN_ENCRYPT_FN(i, child)); await Promise.all(ARR_PROMISE_INIT); await Promise.all(CHILDS.map((child, i) => { return new Promise(function (res, rej) { child.on('exit', () => res(true)); child.kill('SIGKILL'); }); })); return data; }; /**Многопоточное дешифрование по методу Кузнечик */ export const decryptChilds = async (data, masterkey, childs, BITES_FROM_CHILD = 0, trimStart = true) => { if (data.length > 2147483647) console.warn("decryptChilds не был расчитан на данные более 2 гб"); if (!!(data.length % 16)) throw "Не кратно 16 байтам"; const C_BITES_FROM_CHILD = SEARCHER_BITES_FROM_CHILD(data.length, BITES_FROM_CHILD); const CHILD_MAX = countChild(childs, Math.ceil(data.length / C_BITES_FROM_CHILD)); const BLOCKS_FOR_CHILD = Math.ceil((data.length / C_BITES_FROM_CHILD) / CHILD_MAX); const MASTER_KEY_BUFF = toBuffer(masterkey); const CHILD_PATH = join(dirname(fileURLToPath(import.meta.url)), 'child.mjs'); const CHILDS = Array.from({ length: CHILD_MAX }).map(x => fork(CHILD_PATH)); const MAIN_DECRYPT_FN = CHILD_E_D_METHOD.bind({ method: "decrypt", BITES_FROM_CHILD: C_BITES_FROM_CHILD, BLOCKS_FOR_CHILD: BLOCKS_FOR_CHILD, data: data, MASTER_KEY_BUFF: MASTER_KEY_BUFF }); let ARR_PROMISE_INIT = CHILDS.map(child => { return new Promise(function (res, rej) { let timer = setTimeout(() => { child.kill(0); res(false); }, 5 * 1000); child.once("message", (data) => { clearTimeout(timer); res(true); }); }); }); await Promise.all(ARR_PROMISE_INIT); ARR_PROMISE_INIT = CHILDS.map((child, i) => { return MAIN_DECRYPT_FN(i, child); }); await Promise.all(ARR_PROMISE_INIT); await Promise.all(CHILDS.map((child, i) => { return new Promise(function (res, rej) { child.on('exit', () => res(true)); child.kill('SIGKILL'); }); })); return trimStart ? FN_TRIM_START(data) : data; };