@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
JavaScript
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;
}
}