miscreant
Version:
Misuse resistant symmetric encryption library providing AES-SIV (RFC 5297), AES-PMAC-SIV, and STREAM constructions
147 lines (122 loc) • 4.38 kB
text/typescript
/**
* The STREAM online authenticated encryption construction.
* See <https://eprint.iacr.org/2015/189.pdf> for definition.
*/
// tslint:disable:max-classes-per-file
import { IAEADLike, ICryptoProvider } from "./interfaces";
import { AEAD } from "./aead";
import { WebCryptoProvider } from "./providers/webcrypto";
/** Size of a nonce required by STREAM in bytes */
export const NONCE_SIZE = 8;
/** Byte flag indicating this is the last block in the STREAM (otherwise 0) */
export const LAST_BLOCK_FLAG = 1;
/** Maximum value of the counter STREAM uses internally to identify messages */
export const COUNTER_MAX = 0xFFFFFFFF;
/**
* A STREAM encryptor with a 32-bit counter, generalized for any AEAD algorithm
*
* This corresponds to the ℰ stream encryptor object as defined in the paper
* Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance
*/
export class StreamEncryptor {
/** Create a new StreamEncryptor instance with the given key */
public static async importKey(
keyData: Uint8Array,
nonce: Uint8Array,
alg: string,
provider: ICryptoProvider = new WebCryptoProvider(),
): Promise<StreamEncryptor> {
return new StreamEncryptor(await AEAD.importKey(keyData, alg, provider), nonce);
}
private _aead: IAEADLike;
private _nonce_encoder: NonceEncoder;
constructor(aead: IAEADLike, nonce: Uint8Array) {
this._aead = aead;
this._nonce_encoder = new NonceEncoder(nonce);
}
/** Encrypt and authenticate data using the selected AEAD algorithm */
public async seal(
plaintext: Uint8Array,
lastBlock: boolean = false,
associatedData: Uint8Array = new Uint8Array(0),
): Promise<Uint8Array> {
return this._aead.seal(plaintext, this._nonce_encoder.next(lastBlock), associatedData);
}
/** Make a best effort to wipe memory used by this instance */
public clear(): this {
this._aead.clear();
return this;
}
}
/**
* A STREAM decryptor with a 32-bit counter, generalized for any AEAD algorithm
*
* This corresponds to the 𝒟 stream decryptor object as defined in the paper
* Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance
*/
export class StreamDecryptor {
/** Create a new StreamDecryptor instance with the given key */
public static async importKey(
keyData: Uint8Array,
nonce: Uint8Array,
alg: string,
provider: ICryptoProvider = new WebCryptoProvider(),
): Promise<StreamDecryptor> {
return new StreamDecryptor(await AEAD.importKey(keyData, alg, provider), nonce);
}
private _aead: IAEADLike;
private _nonce_encoder: NonceEncoder;
constructor(aead: IAEADLike, nonce: Uint8Array) {
this._aead = aead;
this._nonce_encoder = new NonceEncoder(nonce);
}
/** Decrypt and authenticate data using the selected AEAD algorithm */
public async open(
ciphertext: Uint8Array,
lastBlock: boolean = false,
associatedData: Uint8Array = new Uint8Array(0),
): Promise<Uint8Array> {
return this._aead.open(ciphertext, this._nonce_encoder.next(lastBlock), associatedData);
}
/** Make a best effort to wipe memory used by this instance */
public clear(): this {
this._aead.clear();
return this;
}
}
/** Computes STREAM nonces based on the current position in the STREAM. */
class NonceEncoder {
private buffer: ArrayBuffer;
private view: DataView;
private array: Uint8Array;
private counter: number;
private finished: boolean;
constructor(noncePrefix: Uint8Array) {
if (noncePrefix.length !== NONCE_SIZE) {
throw new Error(`STREAM: nonce must be 8-bits (got ${noncePrefix.length}`);
}
this.buffer = new ArrayBuffer(NONCE_SIZE + 4 + 1);
this.view = new DataView(this.buffer);
this.array = new Uint8Array(this.buffer);
this.array.set(noncePrefix);
this.counter = 0;
this.finished = false;
}
/** Compute the next nonce value, incrementing the internal counter */
public next(lastBlock: boolean): Uint8Array {
if (this.finished) {
throw new Error("STREAM: already finished");
}
this.view.setInt32(8, this.counter, false);
if (lastBlock) {
this.view.setInt8(12, LAST_BLOCK_FLAG);
this.finished = true;
} else {
this.counter += 1;
if (this.counter > COUNTER_MAX) {
throw new Error("STREAM counter overflowed");
}
}
return this.array;
}
}