@li0ard/kuznyechik
Version:
Kuznyechik cipher implementation in pure TypeScript
240 lines (239 loc) • 8.53 kB
JavaScript
import { BLOCK_SIZE, KEY_SIZE, L, PI, PI_REV, ROUNDS, CipherError } from "./const";
/** Kuznyechik core class */
export class Kuznyechik {
roundKeys;
/**
* Kuznyechik core class
* @param key Encryption key
*/
constructor(key) {
if (key.length !== KEY_SIZE)
throw new CipherError("Invalid key length");
if (key.every(byte => byte === 0))
throw new CipherError("Invalid key format");
let iter_constants = Array(32).fill(null).map(() => new Uint8Array(BLOCK_SIZE).fill(0));
for (let i = 0; i < 32; i++) {
iter_constants[i][15] = i + 1;
iter_constants[i] = this.transformL(iter_constants[i]);
}
const roundKeys = Array(ROUNDS).fill(null).map(() => new Uint8Array(BLOCK_SIZE));
roundKeys[0] = key.slice(0, BLOCK_SIZE);
roundKeys[1] = key.slice(BLOCK_SIZE);
let temp1 = roundKeys[0].slice();
let temp2 = roundKeys[1].slice();
let temp3 = new Uint8Array(16);
let temp4 = new Uint8Array(16);
for (let i = 0; i < 4; i++) {
const baseIndex = i * 8;
let res = this.transformF(temp1, temp2, iter_constants[baseIndex]);
temp3 = res[0];
temp4 = res[1];
res = this.transformF(temp3, temp4, iter_constants[baseIndex + 1]);
temp1 = res[0];
temp2 = res[1];
res = this.transformF(temp1, temp2, iter_constants[baseIndex + 2]);
temp3 = res[0];
temp4 = res[1];
res = this.transformF(temp3, temp4, iter_constants[baseIndex + 3]);
temp1 = res[0];
temp2 = res[1];
res = this.transformF(temp1, temp2, iter_constants[baseIndex + 4]);
temp3 = res[0];
temp4 = res[1];
res = this.transformF(temp3, temp4, iter_constants[baseIndex + 5]);
temp1 = res[0];
temp2 = res[1];
res = this.transformF(temp1, temp2, iter_constants[baseIndex + 6]);
temp3 = res[0];
temp4 = res[1];
res = this.transformF(temp3, temp4, iter_constants[baseIndex + 7]);
temp1 = res[0];
temp2 = res[1];
roundKeys[2 + 2 * i] = new Uint8Array(temp1);
roundKeys[3 + 2 * i] = new Uint8Array(temp2);
}
this.roundKeys = roundKeys;
}
/**
* Returns round keys
*/
getRoundKeys() {
return this.roundKeys.slice();
}
/**
* `X`-transformation.
*
* The input of the `X` function is two sequences, and the output of the function is the XOR of these two sequences.
*/
transformX(a, b) {
const result = new Uint8Array(BLOCK_SIZE);
for (let i = 0; i < BLOCK_SIZE; i++) {
result[i] = a[i] ^ b[i];
}
return result;
}
/**
* `S`-transformation.
*
* The `S` function is a regular substitution function.
* Each byte of the input sequence is replaced by the corresponding byte from
* the `PI` substitution table.
*/
transformS(input) {
const result = new Uint8Array(BLOCK_SIZE);
for (let i = 0; i < BLOCK_SIZE; i++) {
result[i] = PI[input[i]];
}
return result;
}
/**
* `Srev`-transformation
*
* The `Srev` function is a regular substitution function.
* Each byte of the input sequence is replaced by the corresponding byte from
* the `PI_REV` substitution table.
*/
transformS_rev(input) {
const result = new Uint8Array(BLOCK_SIZE);
for (let i = 0; i < BLOCK_SIZE; i++) {
result[i] = PI_REV[input[i]];
}
return result;
}
/**
* Performs Galois Field (GF(2)) multiplication.
*
* This method multiplies two bytes in the Galois Field using bitwise operations,
* applying the irreducible polynomial x^8 + x^7 + x^6 + x + 1 for modular reduction.
*/
gfMultiply(a, b) {
let result = 0;
let high_bit;
for (let i = 0; i < 8; i++) {
if ((b & 0b00000001) === 0b00000001) {
result ^= a;
}
high_bit = a & 0b10000000;
a <<= 1;
if (high_bit == 0b10000000) {
a ^= 0b11000011;
}
b >>= 1;
}
return result & 0xFF;
}
/**
* `R`-transformation
*
* Performs a linear transformation on the input block by cyclically shifting bytes
* and applying Galois Field multiplication with a predefined linear transformation matrix (`L`).
*/
transformR(input) {
const result = new Uint8Array(BLOCK_SIZE);
result.set(input.slice(0, 15), 1);
result[0] = input[15];
let temp = 0;
for (let i = 0; i < 16; i++) {
temp ^= this.gfMultiply(result[i], L[i]);
}
result[0] = temp;
return result;
}
/**
* `Rrev`-transformation
*
* Performs a linear transformation on the input block by applying Galois Field multiplication
* with a predefined linear transformation matrix (`L`) and cyclically shifting bytes.
*/
transformR_rev(input) {
const result = new Uint8Array(BLOCK_SIZE);
let temp = 0;
for (let i = 0; i < BLOCK_SIZE; i++) {
temp ^= this.gfMultiply(input[i], L[i]);
}
result.set(input.slice(1));
result[15] = temp;
return result;
}
/**
* `L`-transformation
*
* Performs a linear transformation on the input block by repeatedly applying the `R`-transformation
* a fixed number of times (equal to the block size).
*/
transformL(input) {
let result = input.slice();
for (let i = 0; i < BLOCK_SIZE; i++) {
result = this.transformR(result);
}
return result;
}
/**
* `Lrev`-transformation
*
* Performs a linear transformation on the input block by repeatedly applying the `Rrev`-transformation
* a fixed number of times (equal to the block size).
*/
transformL_rev(input) {
let result = input.slice();
for (let i = 0; i < BLOCK_SIZE; i++) {
result = this.transformR_rev(result);
}
return result;
}
/**
* `F`-transformation aka `XSLX`-algorithm
*
* Performs a key transformation using a series of linear and substitution transformations.
*/
transformF(in_key1, in_key2, iter_constant) {
return [
this.transformX(this.transformL(this.transformS(this.transformX(in_key1, iter_constant))), in_key2),
in_key1.slice()
];
}
/**
* Encrypts single block of data using Kuznyechik encryption algorithm.
* @param block Block to be encrypted
* @returns {Uint8Array} Encrypted block
* @throws {CipherError} Block size is invalid or data is too short
*/
encryptBlock(block) {
if (block.length === 0 || block.length !== BLOCK_SIZE)
throw new CipherError("Invalid block size");
let currentBlock = block.slice();
for (let i = 0; i < 9; i++) {
currentBlock = this.transformL(this.transformS(this.transformX(this.roundKeys[i], currentBlock)));
}
currentBlock = this.transformX(this.roundKeys[9], currentBlock);
return currentBlock;
}
/**
* Decrypts single block of data using Kuznyechik encryption algorithm.
* @param block Block to be decrypted
* @returns {Uint8Array} Decrypted block
* @throws {CipherError} Block size is invalid or data is too short
*/
decryptBlock(block) {
if (block.length === 0 || block.length !== BLOCK_SIZE)
throw new CipherError("Invalid block size");
let currentBlock = block.slice();
currentBlock = this.transformX(this.roundKeys[9], currentBlock);
const reversedKeys = this.roundKeys.slice(0, 9).reverse();
for (let i = 0; i < 9; i++) {
const key = reversedKeys[i];
currentBlock = this.transformX(key, this.transformS_rev(this.transformL_rev(currentBlock)));
}
return currentBlock;
}
}
export * from "./const";
export * from "./modes/acpkm";
export * from "./modes/cbc";
export * from "./modes/cfb";
export * from "./modes/ctr";
export * from "./modes/ecb";
export * from "./modes/mac";
export * from "./modes/mgm";
export * from "./modes/ofb";
export * from "./modes/kexp";