blob: 68a6a4acd7de9997717dc0c511f4358f567a0db7 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
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);
}