@li0ard/streebog
Version:
Streebog hash function in pure TypeScript
142 lines (141 loc) • 4.95 kB
JavaScript
import { A, BLOCKSIZE, C, PI, TAU } from "./const";
/** XOR two Uint8Array arrays */
export const xor = (a, b) => {
let mlen = Math.min(a.length, b.length);
let result = new Uint8Array(mlen);
for (let i = 0; i < mlen; i++)
result[i] = a[i] ^ b[i];
return result.slice();
};
/** Addition of two 512 bit numbers */
export const add512 = (a, b) => {
const c = new Uint8Array(64);
const tmpA = new Uint8Array(64);
const tmpB = new Uint8Array(64);
for (let i = 0; i < a.length; i++)
tmpA[63 - i] = a[a.length - i - 1];
for (let i = 0; i < b.length; i++)
tmpB[63 - i] = b[b.length - i - 1];
for (let i = 63, tmp = 0; i >= 0; i--) {
tmp = tmpA[i] + tmpB[i] + (tmp >> 8);
c[i] = tmp & 0xff;
}
return c;
};
/**
* `S`-transformation.
* The `S` function is a regular substitution function. Each byte of the
* 512-bit input sequence is replaced by the corresponding byte from
* the `PI` substitution table.
*/
export const transformS = (input) => {
const result = new Uint8Array(BLOCKSIZE);
for (let i = 0; i < BLOCKSIZE; i++)
result[i] = PI[input[i]];
return result;
};
/**
* `P`-transformation.
* Permutation function. For each pair of bytes from the input sequence,
* one byte is replaced by another.
*/
export const transformP = (input) => {
const result = new Uint8Array(BLOCKSIZE);
for (let i = 0; i < BLOCKSIZE; i++)
result[i] = input[TAU[i]];
return result;
};
/**
* `L`-transformation.
* Represents the multiplication of a 64-bit input vector by a 64x64 binary matrix `A`.
*/
export const transformL = (input) => {
const result = new Uint8Array(BLOCKSIZE);
for (let i = 0; i < 8; i++) {
const parts = new Uint32Array(2);
const tmp = input.slice(i * 8, i * 8 + 8).reverse();
for (let j = 0; j < 8; j++) {
for (let k = 0; k < 8; k++) {
if ((tmp[7 - j] >> 7 - k) & 1) {
parts[0] ^= A[j * 16 + k * 2];
parts[1] ^= A[j * 16 + k * 2 + 1];
}
}
}
result[i * 8] = parts[0] >> 24;
result[i * 8 + 1] = (parts[0] << 8) >> 24;
result[i * 8 + 2] = (parts[0] << 16) >> 24;
result[i * 8 + 3] = (parts[0] << 24) >> 24;
result[i * 8 + 4] = parts[1] >> 24;
result[i * 8 + 5] = (parts[1] << 8) >> 24;
result[i * 8 + 6] = (parts[1] << 16) >> 24;
result[i * 8 + 7] = (parts[1] << 24) >> 24;
}
return result;
};
/**
* `E`-transformation.
* Part of compression function.
*/
export const transformE = (block, keys) => {
let c = xor(block, keys);
for (let i = 0; i < 12; i++) {
block = transformL(transformP(transformS(xor(block, C[i]))));
c = xor(transformL(transformP(transformS(c))), block);
}
return c;
};
/** Compression function `G` aka XSPLEXX-algorithm */
export const transformG = (hash, n, message) => {
return xor(xor(transformE(transformL(transformP(transformS(xor(n, hash)))), message), n), message);
};
const asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
function asciiToBase16(ch) {
if (ch >= asciis._0 && ch <= asciis._9)
return ch - asciis._0; // '2' => 50-48
if (ch >= asciis.A && ch <= asciis.F)
return ch - (asciis.A - 10); // 'B' => 66-(65-10)
if (ch >= asciis.a && ch <= asciis.f)
return ch - (asciis.a - 10); // 'b' => 98-(97-10)
return;
}
/** Convert hex to bytes */
export function hexToBytes(hex) {
if (typeof hex !== 'string')
throw new Error('hex string expected, got ' + typeof hex);
const hl = hex.length;
const al = hl / 2;
if (hl % 2)
throw new Error('hex string expected, got unpadded hex of length ' + hl);
const array = new Uint8Array(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
const n1 = asciiToBase16(hex.charCodeAt(hi));
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
if (n1 === undefined || n2 === undefined) {
const char = hex[hi] + hex[hi + 1];
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
}
array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163
}
return array;
}
/** Convert number to bytes (Big-Endian) */
export function numberToBytesBE(n, len) {
let num = n.toString(16).padStart(len * 2, '0');
while (num.length % 2 != 0)
num = "0" + num;
return hexToBytes(num);
}
const getPadLength = (dataLength) => {
if (dataLength < BLOCKSIZE)
return BLOCKSIZE - dataLength;
if (dataLength % BLOCKSIZE == 0)
return 0;
return BLOCKSIZE - dataLength % BLOCKSIZE;
};
/** Padding for blocks */
export const pad = (data) => {
const padded = new Uint8Array(data.length + getPadLength(data.length));
padded.set(data);
return padded;
};