UNPKG

@li0ard/gost3413

Version:

Cipher modes and padding's according to GOST R 34.13-2015 in pure TypeScript

129 lines (128 loc) 5.08 kB
import { pad1 } from "./index.js"; import { bytesToNumberBE, concatBytes, equalBytes, numberToBytesBE, xor } from "./utils.js"; const _incr = (data, blockSize) => numberToBytesBE(bytesToNumberBE(data) + 1n, (blockSize / 2) | 0); const incr_r = (data, blockSize) => concatBytes(data.slice(0, ((blockSize / 2) | 0)), _incr(data.slice(((blockSize / 2) | 0)), blockSize)); const incr_l = (data, blockSize) => concatBytes(_incr(data.slice(0, ((blockSize / 2) | 0)), blockSize), data.slice(((blockSize / 2) | 0))); // based on go.cypherpunks.su/gogost/mgm /** Multilinear Galois Mode (MGM) class */ export class MGM { tag_size; encrypter; blockSize; max_size; r; /** * Prepare nonce * * Just clear MSB bit * @param nonce Nonce */ static nonce_prepare(nonce) { let n = nonce.slice(); n[0] &= 0x7F; return n; } constructor(encrypter, blockSize, tagSize) { if (blockSize != 8 && blockSize != 16) throw new Error("Only 64/128-bit block size"); this.tag_size = tagSize ?? blockSize; if (this.tag_size < 4 || this.tag_size > blockSize) throw new Error("Invalid tagSize"); this.encrypter = encrypter; this.blockSize = blockSize; // (1n << BigInt((blockSize * 8 / 2) | 0)) - 1n this.max_size = (1n << BigInt(blockSize * 4)) - 1n; this.r = (blockSize == 8 ? 0x1B : 0x87); } // Seems to be broken validateNonce(nonce) { if (nonce.length != this.blockSize) throw new Error("Invalid nonce length"); if ((nonce[0] & 0x80) > 0) throw new Error("Invalid nonce"); } validateSizes(plaintext, additional) { if (plaintext.length == 0 && additional.length == 0) throw new Error("At least one of plaintext or additional_data required"); if ((plaintext.length + additional.length) > this.max_size) throw new Error("plaintext+additional_data are too big"); } mul(a, b) { let x = bytesToNumberBE(a); let y = bytesToNumberBE(b); let z = 0n; let max_bit = 1n << (BigInt(this.blockSize) * 8n - 1n); while (y > 0n) { if ((y & 1n) == 1n) z ^= x; if ((x & max_bit) > 0n) x = ((x ^ max_bit) << 1n) ^ BigInt(this.r); else x <<= 1n; y >>= 1n; } return numberToBytesBE(z, this.blockSize); } crypt(icn, data) { icn[0] &= 0x7F; let enc = this.encrypter(icn); let res = []; while (data.length > 0) { res.push(xor(this.encrypter(enc), data)); enc = incr_r(enc, this.blockSize); data = data.slice(this.blockSize); } return concatBytes(...res); } auth(icn, text, ad) { icn[0] |= 0x80; let enc = this.encrypter(icn); let _sum = new Uint8Array(this.blockSize); let ad_len = ad.length; let text_len = text.length; while (ad.length > 0) { _sum = xor(_sum, this.mul(this.encrypter(enc), pad1(ad.slice(0, this.blockSize), this.blockSize))); enc = incr_l(enc, this.blockSize); ad = ad.slice(this.blockSize); } while (text.length > 0) { _sum = xor(_sum, this.mul(this.encrypter(enc), pad1(text.slice(0, this.blockSize), this.blockSize))); enc = incr_l(enc, this.blockSize); text = text.slice(this.blockSize); } const halfbs = (this.blockSize / 2) | 0; _sum = xor(_sum, this.mul(this.encrypter(enc), concatBytes(numberToBytesBE(ad_len * 8, halfbs), numberToBytesBE(text_len * 8, halfbs)))); return this.encrypter(_sum).slice(0, this.tag_size); } /** * Seal plaintext * @param nonce Nonce (blocksized) * @param plaintext Data to be encrypted and authenticated * @param additional_data Additional data to be authenticated */ seal(nonce, plaintext, additional_data) { //this.validateNonce(nonce) this.validateSizes(plaintext, additional_data); let icn = nonce.slice(); let ciphertext = this.crypt(icn, plaintext); let tag = this.auth(icn, ciphertext, additional_data); return concatBytes(ciphertext, tag); } /** * Open ciphertext * @param nonce Nonce (blocksized) * @param ciphertext Data to be decrypted and authenticated * @param additional_data Additional data to be authenticated */ open(nonce, ciphertext, additional_data) { //this.validateNonce(nonce) this.validateSizes(ciphertext, additional_data); let icn = nonce.slice(); let ct = ciphertext.slice(0, (ciphertext.length - this.tag_size)); let tag_expected = ciphertext.slice((ciphertext.length - this.tag_size)); let tag = this.auth(icn, ct, additional_data); if (!equalBytes(tag_expected, tag)) throw new Error("Invalid authentication tag"); return this.crypt(icn, ct); } }