UNPKG

@li0ard/gost341194

Version:

GOST R 34.11-94 hash function in pure TypeScript

123 lines (122 loc) 4.33 kB
import { bytesToNumberBE, numberToBytesBE, xor } from "./utils.js"; import { encryptECB } from "@li0ard/magma"; import { BLOCKSIZE, C2, C3, C4, DEFAULT_SBOX } from "./const.js"; import { concatBytes } from "@noble/hashes/utils.js"; const A = (x) => { let x2 = x.slice(16, 24), x1 = x.slice(24, 32); return concatBytes(xor(x1, x2), x.slice(0, 8), x.slice(8, 16), x2); }; const P = (x) => new Uint8Array([x[0], x[8], x[16], x[24], x[1], x[9], x[17], x[25], x[2], x[10], x[18], x[26], x[3], x[11], x[19], x[27], x[4], x[12], x[20], x[28], x[5], x[13], x[21], x[29], x[6], x[14], x[22], x[30], x[7], x[15], x[23], x[31]]); /** Chi function (looks like LFSR) */ const _chi = (Y) => { const byx = new Uint8Array(2); byx[0] = Y[30] ^ Y[28] ^ Y[26] ^ Y[24] ^ Y[6] ^ Y[0]; byx[1] = Y[31] ^ Y[29] ^ Y[27] ^ Y[25] ^ Y[7] ^ Y[1]; const result = new Uint8Array(32); result.set(byx, 0); for (let i = 0; i < 30; i += 2) { result[i + 2] = Y[i]; result[i + 3] = Y[i + 1]; } return result; }; /** Step function */ const _step = (hin, m, sbox) => { // Generate keys let u = hin; let v = m; let w = xor(hin, m); const k1 = P(w); u = xor(A(u), C2); v = A(A(v)); w = xor(u, v); const k2 = P(w); u = xor(A(u), C3); v = A(A(v)); w = xor(u, v); const k3 = P(w); u = xor(A(u), C4); v = A(A(v)); w = xor(u, v); const k4 = P(w); // Encrypt const s = concatBytes(encryptECB(k4.reverse(), hin.slice(0, 8).reverse(), true, sbox).reverse(), encryptECB(k3.reverse(), hin.slice(8, 16).reverse(), true, sbox).reverse(), encryptECB(k2.reverse(), hin.slice(16, 24).reverse(), true, sbox).reverse(), encryptECB(k1.reverse(), hin.slice(24, 32).reverse(), true, sbox).reverse()); // Permute let x = new Uint8Array(s); for (let i = 0; i < 12; i++) x = _chi(x); x = xor(x, m); x = _chi(x); x = xor(hin, x); for (let i = 0; i < 61; i++) x = _chi(x); return x; }; /** GOST R 34.11-94 class */ export class Gost341194 { data; sbox; blockLen = BLOCKSIZE; outputLen = 32; canXOF = false; /** * GOST R 34.11-94 constructor * @param data Data to be hashed (optional, can be updated using `update` method) * @param sbox S-Box (optional, default is `ID_GOSTR_3411_94_CRYPTOPRO_PARAM_SET`) */ constructor(data = new Uint8Array(), sbox = DEFAULT_SBOX) { this.data = data; this.sbox = sbox; } /** Create hash instance */ static create() { return new Gost341194(); } /** Reset hash state */ destroy() { this.data = new Uint8Array(); } /** Clone hash instance */ clone() { return this._cloneInto(); } _cloneInto(to) { to ||= new Gost341194(); to.data = this.data.slice(); to.sbox = this.sbox; return to; } /** Update hash buffer */ update(data) { this.data = concatBytes(this.data, data); return this; } /** * Finalize hash computation and write result into Uint8Array * @param buf Output Uint8Array */ digestInto(buf) { let len = 0n; let checksum = 0n; let h = new Uint8Array(32); let m = new Uint8Array(this.data); for (let i = 0; i < m.length; i += BLOCKSIZE) { let part = m.slice(i, i + BLOCKSIZE).reverse(); len += BigInt(part.length) * 8n; checksum = (checksum + bytesToNumberBE(part)) & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn; if (part.length < BLOCKSIZE) part = numberToBytesBE(bytesToNumberBE(part), BLOCKSIZE); h = _step(h, part, this.sbox); } h = _step(_step(h, numberToBytesBE(len, BLOCKSIZE), this.sbox), numberToBytesBE(checksum, BLOCKSIZE), this.sbox); buf.set(h.reverse()); this.destroy(); } /** Finalize hash computation and return result as Uint8Array */ digest() { const buffer = new Uint8Array(this.outputLen); this.digestInto(buffer); return buffer; } } /** * Compute hash with GOST R 34.11-94 * @param input Input bytes */ export const gost341194 = (input, sbox = DEFAULT_SBOX) => new Gost341194(input, sbox).digest(); export { sboxes } from "./const.js"; export * from "./pbkdf2.js";