blob: 6d9bc4b6c2f64676af00ffe5e597d35e3733331d [file] [log] [blame]
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {SecurityException} from '../exception/security_exception';
import * as Bytes from './bytes';
import {IndCpaCipher} from './ind_cpa_cipher';
import * as Random from './random';
import * as Validators from './validators';
/**
* The minimum IV size.
*
*/
const MIN_IV_SIZE_IN_BYTES: number = 12;
/**
* AES block size.
*
*/
const AES_BLOCK_SIZE_IN_BYTES: number = 16;
/**
* Implementation of AES-CTR.
*
* @final
*/
export class AesCtr implements IndCpaCipher {
/**
* @param ivSize the size of the IV
*/
constructor(
private readonly key: CryptoKey, private readonly ivSize: number) {}
/**
* @override
*/
async encrypt(plaintext: Uint8Array): Promise<Uint8Array> {
Validators.requireUint8Array(plaintext);
const iv = Random.randBytes(this.ivSize);
const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
counter.set(iv);
const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
const ciphertext =
await self.crypto.subtle.encrypt(alg, this.key, plaintext);
return Bytes.concat(iv, new Uint8Array(ciphertext));
}
/**
* @override
*/
async decrypt(ciphertext: Uint8Array): Promise<Uint8Array> {
Validators.requireUint8Array(ciphertext);
if (ciphertext.length < this.ivSize) {
throw new SecurityException('ciphertext too short');
}
const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
counter.set(ciphertext.subarray(0, this.ivSize));
const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
return new Uint8Array(await self.crypto.subtle.decrypt(
alg, this.key, new Uint8Array(ciphertext.subarray(this.ivSize))));
}
}
/**
* @param ivSize the size of the IV, must be larger than or equal to
* {@link MIN_IV_SIZE_IN_BYTES}
*/
export async function fromRawKey(
key: Uint8Array, ivSize: number): Promise<IndCpaCipher> {
if (!Number.isInteger(ivSize)) {
throw new SecurityException('invalid IV length, must be an integer');
}
if (ivSize < MIN_IV_SIZE_IN_BYTES || ivSize > AES_BLOCK_SIZE_IN_BYTES) {
throw new SecurityException(
'invalid IV length, must be at least ' + MIN_IV_SIZE_IN_BYTES +
' and at most ' + AES_BLOCK_SIZE_IN_BYTES);
}
Validators.requireUint8Array(key);
Validators.validateAesKeySize(key.length);
const cryptoKey = await self.crypto.subtle.importKey(
'raw', key, {'name': 'AES-CTR', 'length': key.length}, false,
['encrypt', 'decrypt']);
return new AesCtr(cryptoKey, ivSize);
}