@li0ard/kalyna
Version:
Kalyna (DSTU 7624:2014) cipher implementation in pure TypeScript
203 lines (202 loc) • 7.42 kB
JavaScript
import { IS, IT, KUPYNA_T, S } from "./const";
import { bytesToUint64s, swap_block, uint64sToBytes } from "./utils";
/** Kalyna abstract class */
export class KalynaBase {
N;
/** Round keys for encryption */
erk;
/** Rounds keys for decryption */
drk;
/** Block size */
blockSize;
/** Key size */
keySize;
numRounds;
glOffset;
wordOffsets;
/** Kalyna abstract class */
constructor(key, N, isDouble = false) {
this.N = N;
if (N < 2 || (N & (N - 1)) !== 0)
throw new Error("N must be power of 2 and >= 2");
this.blockSize = N << 3;
this.keySize = this.blockSize;
if (isDouble)
this.keySize *= 2;
if (key.length !== this.keySize)
throw new Error("Invalid key length");
this.wordOffsets = Array.from({ length: 8 }, (_, j) => Math.floor(j * this.N / 8));
const X = 6 + 4 * Math.log2(N) + (isDouble ? 4 : 0);
this.numRounds = X - (N > 2 || isDouble ? 1 : 0);
this.glOffset = X * N;
this.expandKey(key);
}
expandKey(key) {
const log2N = Math.log2(this.N),
// For 128/256 and 256/512 versions
isDoubleKey = (this.keySize === this.blockSize * 2), R = isDoubleKey ? (6 + 2 * log2N) : (4 + 2 * log2N), rk = new BigUint64Array(R * this.N * 2), ks = new BigUint64Array(this.N), ksc = new BigUint64Array(this.N), t1 = new BigUint64Array(this.N), t2 = new BigUint64Array(this.N);
t1[0] = isDoubleKey ? BigInt(2 * this.N + 2 * log2N + 1) : BigInt(2 * this.N + 1);
const keys = bytesToUint64s(key);
let k = new BigUint64Array(isDoubleKey ? this.N * 2 : this.N);
if (isDoubleKey) {
const ka = keys.slice(0, this.N);
this.addkey(t1, t2, ka);
this.G(t2, t1, keys.slice(this.N));
this.GL(t1, t2, ka);
this.G0(t2, ks);
k.set(keys);
}
else {
k.set(keys.slice(0, this.N));
this.addkey(t1, t2, keys);
this.G(t2, t1, keys);
this.GL(t1, t2, keys);
this.G0(t2, ks);
}
let constant = 0x0001000100010001n;
for (let i = 0; i < R; i++) {
const offset = i * (this.N * 2);
if (i > 0) {
if (!isDoubleKey)
swap_block(k, this.N);
else if (i % 2 === 0)
swap_block(k, this.N * 2);
}
const keySource = isDoubleKey ? (i % 2 === 0 ? k.subarray(0, this.N) : k.subarray(this.N)) : k;
this.add_constant(ks, ksc, constant);
this.addkey(keySource, t2, ksc);
this.G(t2, t1, ksc);
this.GL(t1, rk.subarray(offset), ksc);
if (i < R - 1)
this.makeOddKey(rk.subarray(offset), rk.subarray(offset + this.N));
constant <<= 1n;
}
this.erk = rk.slice();
for (let i = ((R * 2 - 3) * this.N); i > 0; i -= this.N)
this.IMC(rk.subarray(i));
this.drk = rk.slice();
}
makeOddKey(evenkey, oddkey) {
const offset = 2 * this.N + 3;
const evenkeys = uint64sToBytes(evenkey);
const oddkeys = uint64sToBytes(oddkey);
oddkeys.set(evenkeys.slice(offset, this.blockSize));
oddkeys.set(evenkeys.slice(0, offset), (this.blockSize - offset));
oddkey.set(bytesToUint64s(oddkeys));
}
addkey(x, y, k) {
for (let i = 0; i < this.N; i++)
y[i] = x[i] + k[i];
}
subkey(x, y, k) {
for (let i = 0; i < this.N; i++)
y[i] = x[i] - k[i];
}
add_constant(src, dst, constant) {
for (let i = 0; i < this.N; i++)
dst[i] = src[i] + constant;
}
byte(a) { return Number(a & 0xffn); }
G0(x, y) {
for (let i = 0; i < this.N; i++) {
y[i] = 0n;
for (let j = 0; j < 8; j++) {
y[i] ^= KUPYNA_T[j][this.byte(x[(i - this.wordOffsets[j] + this.N) % this.N] >> BigInt(j << 3))];
}
}
}
G(x, y, k) {
for (let i = 0; i < this.N; i++) {
y[i] = k[i];
for (let j = 0; j < 8; j++) {
y[i] ^= KUPYNA_T[j][this.byte(x[(i - this.wordOffsets[j] + this.N) % this.N] >> BigInt(j << 3))];
}
}
}
GL(x, y, k) {
for (let i = 0; i < this.N; i++) {
let temp = 0n;
for (let j = 0; j < 8; j++) {
temp ^= KUPYNA_T[j][this.byte(x[(i - this.wordOffsets[j] + this.N) % this.N] >> BigInt(j << 3))];
}
y[i] = k[i] + temp;
}
}
IMC(x) {
for (let i = 0; i < this.N; i++) {
const v = x[i];
x[i] = IT[0][S[0][this.byte(v)]] ^
IT[1][S[1][this.byte(v >> 8n)]] ^
IT[2][S[2][this.byte(v >> 16n)]] ^
IT[3][S[3][this.byte(v >> 24n)]] ^
IT[4][S[0][this.byte(v >> 32n)]] ^
IT[5][S[1][this.byte(v >> 40n)]] ^
IT[6][S[2][this.byte(v >> 48n)]] ^
IT[7][S[3][this.byte(v >> 56n)]];
}
}
IG(x, y, k) {
for (let i = 0; i < this.N; i++) {
let result = k[i];
for (let j = 0; j < 8; j++) {
result ^= IT[j][this.byte(x[(i + this.wordOffsets[j]) % this.N] >> BigInt(j << 3))];
}
y[i] = result;
}
}
IGL(x, y, k) {
for (let i = 0; i < this.N; i++) {
let result = 0n;
for (let j = 0; j < 8; j++) {
const shift = BigInt(j << 3);
result ^= BigInt(IS[j % 4][this.byte(x[(i + this.wordOffsets[j]) % this.N] >> shift)]) << shift;
}
y[i] = result - k[i];
}
}
/**
* Encrypt data
* @param in_ Data to be encrypted
*/
encrypt(in_) {
if (in_.length != this.blockSize)
throw new Error(`Incorrect length (need - ${this.blockSize}, got - ${in_.length})`);
const t1 = new BigUint64Array(this.N);
const t2 = new BigUint64Array(this.N);
const ins = bytesToUint64s(in_);
const rk = this.erk.slice();
this.addkey(ins, t1, rk);
for (let i = 0; i < this.numRounds; i++) {
const roundKey = rk.subarray(this.N + i * this.N);
if (i % 2 === 0)
this.G(t1, t2, roundKey);
else
this.G(t2, t1, roundKey);
}
this.GL(t2, t1, rk.subarray(this.glOffset));
return uint64sToBytes(t1);
}
/**
* Decrypt data
* @param in_ Data to be decrypted
*/
decrypt(in_) {
if (in_.length != this.blockSize)
throw new Error(`Incorrect length (need - ${this.blockSize}, got - ${in_.length})`);
const t1 = new BigUint64Array(this.N);
const t2 = new BigUint64Array(this.N);
const ins = bytesToUint64s(in_);
const rk = this.drk.slice();
this.subkey(ins, t1, rk.subarray(this.glOffset));
this.IMC(t1);
for (let i = 0; i < this.numRounds; i++) {
const roundKey = rk.subarray(this.glOffset - this.N - i * this.N);
if (i % 2 === 0)
this.IG(t1, t2, roundKey);
else
this.IG(t2, t1, roundKey);
}
this.IGL(t2, t1, rk);
return uint64sToBytes(t1);
}
}