UNPKG

@hackbg/miscreant-esm

Version:

(ESM port) Misuse resistant symmetric encryption library providing AES-SIV (RFC 5297), AES-PMAC-SIV, and STREAM constructions

108 lines (107 loc) 3.15 kB
import Block from "../internals/block.dist.mjs"; import {select} from "../internals/constant-time.dist.mjs"; import {ctz} from "../internals/ctz.dist.mjs"; import {xor} from "../internals/xor.dist.mjs"; const PRECOMPUTED_BLOCKS = 31; export class PMAC { static async importKey(provider, keyData) { const cipher = await provider.importBlockCipherKey(keyData); const tmp = new Block(); await cipher.encryptBlock(tmp); const l = new Array(PRECOMPUTED_BLOCKS); for (let i = 0; i < PRECOMPUTED_BLOCKS; i++) { l[i] = tmp.clone(); tmp.dbl(); } const lInv = l[0].clone(); const lastBit = lInv.data[Block.SIZE - 1] & 0x01; for (let i = Block.SIZE - 1; i > 0; i--) { const carry = select(lInv.data[i - 1] & 1, 0x80, 0); lInv.data[i] = lInv.data[i] >>> 1 | carry; } lInv.data[0] >>>= 1; lInv.data[0] ^= select(lastBit, 0x80, 0); lInv.data[Block.SIZE - 1] ^= select(lastBit, Block.R >>> 1, 0); return new PMAC(cipher, l, lInv); } _cipher; _L; _LInv; _buffer; _bufferPos; _counter; _offset; _tag; _finished = false; constructor(cipher, l, lInv) { this._cipher = cipher; this._L = l; this._LInv = lInv; this._buffer = new Block(); this._bufferPos = 0; this._counter = 0; this._offset = new Block(); this._tag = new Block(); } reset() { this._buffer.clear(); this._bufferPos = 0; this._counter = 0; this._offset.clear(); this._tag.clear(); this._finished = false; return this; } clear() { this.reset(); this._cipher.clear(); } async update(data) { if (this._finished) { throw new Error("pmac: already finished"); } const left = Block.SIZE - this._bufferPos; let dataPos = 0; let dataLength = data.length; if (dataLength > left) { this._buffer.data.set(data.slice(0, left), this._bufferPos); dataPos += left; dataLength -= left; await this._processBuffer(); } while (dataLength > Block.SIZE) { this._buffer.data.set(data.slice(dataPos, dataPos + Block.SIZE)); dataPos += Block.SIZE; dataLength -= Block.SIZE; await this._processBuffer(); } if (dataLength > 0) { this._buffer.data.set(data.slice(dataPos, dataPos + dataLength), this._bufferPos); this._bufferPos += dataLength; } return this; } async finish() { if (this._finished) { throw new Error("pmac: already finished"); } if (this._bufferPos === Block.SIZE) { xor(this._tag.data, this._buffer.data); xor(this._tag.data, this._LInv.data); } else { xor(this._tag.data, this._buffer.data.slice(0, this._bufferPos)); this._tag.data[this._bufferPos] ^= 0x80; } await this._cipher.encryptBlock(this._tag); this._finished = true; return this._tag.clone().data; } async _processBuffer() { xor(this._offset.data, this._L[ctz(this._counter + 1)].data); xor(this._buffer.data, this._offset.data); this._counter++; await this._cipher.encryptBlock(this._buffer); xor(this._tag.data, this._buffer.data); this._bufferPos = 0; } }