// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { Buffer } from 'buffer'; import type { Decipher } from 'crypto'; import crypto from 'crypto'; import { strictAssert } from '../util/assert'; import type { HashType } from '../types/Crypto'; import { CipherType } from '../types/Crypto'; const AUTH_TAG_SIZE = 16; export class Crypto { public sign(key: Uint8Array, data: Uint8Array): Uint8Array { return crypto .createHmac('sha256', Buffer.from(key)) .update(Buffer.from(data)) .digest(); } public hash(type: HashType, data: Uint8Array): Uint8Array { return crypto.createHash(type).update(Buffer.from(data)).digest(); } public encrypt( cipherType: CipherType, { key, plaintext, iv, aad, }: Readonly<{ key: Uint8Array; plaintext: Uint8Array; iv: Uint8Array; aad?: Uint8Array; }> ): Uint8Array { if (cipherType === CipherType.AES256GCM) { const gcm = crypto.createCipheriv( cipherType, Buffer.from(key), Buffer.from(iv) ); if (aad) { gcm.setAAD(aad); } const first = gcm.update(Buffer.from(plaintext)); const last = gcm.final(); const tag = gcm.getAuthTag(); strictAssert(tag.length === AUTH_TAG_SIZE, 'Invalid auth tag size'); return Buffer.concat([first, last, tag]); } strictAssert(aad === undefined, `AAD is not supported for: ${cipherType}`); const cipher = crypto.createCipheriv( cipherType, Buffer.from(key), Buffer.from(iv) ); return Buffer.concat([ cipher.update(Buffer.from(plaintext)), cipher.final(), ]); } public decrypt( cipherType: CipherType, { key, ciphertext, iv, aad, }: Readonly<{ key: Uint8Array; ciphertext: Uint8Array; iv: Uint8Array; aad?: Uint8Array; }> ): Uint8Array { let decipher: Decipher; let input = Buffer.from(ciphertext); if (cipherType === CipherType.AES256GCM) { const gcm = crypto.createDecipheriv( cipherType, Buffer.from(key), Buffer.from(iv) ); if (input.length < AUTH_TAG_SIZE) { throw new Error('Invalid GCM ciphertext'); } const tag = input.slice(input.length - AUTH_TAG_SIZE); input = input.slice(0, input.length - AUTH_TAG_SIZE); gcm.setAuthTag(tag); if (aad) { gcm.setAAD(aad); } decipher = gcm; } else { strictAssert( aad === undefined, `AAD is not supported for: ${cipherType}` ); decipher = crypto.createDecipheriv( cipherType, Buffer.from(key), Buffer.from(iv) ); } return Buffer.concat([decipher.update(input), decipher.final()]); } public randomInt(min: number, max: number): number { return crypto.randomInt(min, max); } public getRandomBytes(size: number): Uint8Array { return crypto.randomBytes(size); } public constantTimeEqual(left: Uint8Array, right: Uint8Array): boolean { return crypto.timingSafeEqual(Buffer.from(left), Buffer.from(right)); } }