UNPKG

@li0ard/magma

Version:

Magma cipher implementation in pure TypeScript

136 lines (135 loc) 5.17 kB
import { BLOCK_SIZE, CipherError, KEY_SIZE, keySequences, sboxes } from "./const.js"; import { concatBytes } from "@li0ard/gost3413/dist/utils.js"; /** Magma core class */ export class Magma { key; sbox; roundKeys = []; /** * Magma core class * @param key Encryption key * @param sbox S-Box */ constructor(key, sbox = sboxes.ID_TC26_GOST_28147_PARAM_Z) { this.key = key; this.sbox = sbox; if (key.length !== KEY_SIZE) throw new CipherError("Invalid key length"); this.roundKeys = this.regenerateRoundKeys(keySequences.ENCRYPT); } /** Regenerate round keys for sequence */ regenerateRoundKeys(sequence) { const keyChunks = []; for (let j = 0; j < 8; j++) keyChunks.push(Magma.bytesToU32(this.key.slice(j * 4, j * 4 + 4))); let roundKeys = new Array(sequence.length); for (let i = 0; i < sequence.length; i++) roundKeys[i] = keyChunks[sequence[i]]; return roundKeys; } /** * Applies substitution transformation (T-transformation) using S-box. * Breaks input value into 4-bit parts, substitutes each part using corresponding S-box row, * and reconstructs transformed value. * @param value Value to be transformed * @returns {number} Transformed 32-bit value after substitution */ transformT(value) { let result = 0; for (let i = 0; i < 8; i++) result |= this.sbox[i][(value >> (4 * i)) & 0x0f] << (4 * i); return result >>> 0; } /** * Applies the G-transformation (Feistel round function) to input value. * Performs addition with round key, applies T-transformation, and performs cyclic left shift. * @param a Input 32-bit value to be transformed * @param k Round key used in the transformation * @returns {number} Transformed 32-bit value after G-transformation */ transformG(a, k) { const substituted = this.transformT((a + k) >>> 0); return ((substituted << 11) | (substituted >>> 21)) >>> 0; } /** * Returns round keys * @returns {number[]} */ getRoundKeys() { return [...this.roundKeys]; } /** * Proceed single block of data using Magma cipher * @param block Block * @param sequence Sequence of `K_i` S-Box applying * @returns {Uint8Array} Proceeded block * @throws {CipherError} Block size is invalid or data is too short */ proceedBlock(block, sequence) { let roundKeys = this.regenerateRoundKeys(sequence); if (block.length !== BLOCK_SIZE) throw new CipherError("Invalid block size"); let a0 = Magma.bytesToU32(block.slice(0, 4)); let a1 = Magma.bytesToU32(block.slice(4, 8)); for (let i = 0; i < roundKeys.length; i++) { const temp = a1; a1 = a0 ^ this.transformG(a1, roundKeys[i]); a0 = temp; } return concatBytes(Magma.u32ToBytes(a1), Magma.u32ToBytes(a0)); } /** * Encrypts single block of data using Magma cipher. * @param block Block to be encrypted */ encryptBlock(block) { return this.proceedBlock(block, keySequences.ENCRYPT); } /** * Decrypts single block of data using Magma cipher. * @param block Block to be decrypted */ decryptBlock(block) { return this.proceedBlock(block, keySequences.DECRYPT); } /** Encrypt single block of data using old Magma (GOST 28147-89) algorithm */ encryptLegacy(block) { return Magma.reverseChunks(this.encryptBlock(Magma.reverseChunks(block))); } /** Decrypt single block of data using old Magma (GOST 28147-89) algorithm */ decryptLegacy(block) { return Magma.reverseChunks(this.decryptBlock(Magma.reverseChunks(block))); } /** Convert bytes to uint32 number */ static bytesToU32(bytes) { return ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0; } /** Convert uint32 number to bytes */ static u32ToBytes(value) { return new Uint8Array([(value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]); } /** Backward compatibility key preparation for 28147-89 key schedule */ static reverseKey(key) { const result = new Uint8Array(KEY_SIZE); for (let i = 0; i < BLOCK_SIZE; i++) result.set(new Uint8Array(key.slice(i * 4, i * 4 + 4)).reverse(), i * 4); return result; } /** Backward compatibility block preparation for 28147-89 */ static reverseChunks(data) { const chunks = []; for (let i = 0; i < data.length; i += BLOCK_SIZE) chunks.push(new Uint8Array(data.slice(i, i + BLOCK_SIZE)).reverse()); return concatBytes(...chunks); } } export * from "./const.js"; export * from "./modes/ecb.js"; export * from "./modes/cbc.js"; export * from "./modes/cfb.js"; export * from "./modes/ctr.js"; export * from "./modes/ofb.js"; export * from "./modes/mac.js"; export * from "./modes/acpkm.js"; export * from "./modes/mgm.js"; export * from "./modes/wrap.js";