UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

172 lines (144 loc) 4.89 kB
/** * AES-CBC cryptor module. */ import cryptoJS from '../../../core/components/cryptography/hmac-sha256'; import { decode } from '../../../core/components/base64_codec'; import { ICryptor, EncryptedDataType } from './ICryptor'; /** * AES-CBC cryptor. * * AES-CBC cryptor with enhanced cipher strength. */ export default class AesCbcCryptor implements ICryptor { /** * Cryptor block size. */ static BLOCK_SIZE = 16; /** * {@link string|String} to {@link ArrayBuffer} response decoder. */ static encoder = new TextEncoder(); /** * {@link ArrayBuffer} to {@link string} decoder. */ static decoder = new TextDecoder(); /** * Data encryption / decryption cipher key. */ cipherKey: string; /* eslint-disable @typescript-eslint/no-explicit-any */ encryptedKey: any; /* eslint-disable @typescript-eslint/no-explicit-any */ CryptoJS: any; constructor({ cipherKey }: { cipherKey: string }) { this.cipherKey = cipherKey; this.CryptoJS = cryptoJS; this.encryptedKey = this.CryptoJS.SHA256(cipherKey); } // -------------------------------------------------------- // --------------------- Encryption ----------------------- // -------------------------------------------------------- // region Encryption encrypt(data: ArrayBuffer | string): EncryptedDataType { const stringData = typeof data === 'string' ? data : AesCbcCryptor.decoder.decode(data); if (stringData.length === 0) throw new Error('encryption error. empty content'); const abIv = this.getIv(); return { metadata: abIv, data: decode( this.CryptoJS.AES.encrypt(data, this.encryptedKey, { iv: this.bufferToWordArray(abIv), mode: this.CryptoJS.mode.CBC, }).ciphertext.toString(this.CryptoJS.enc.Base64), ), }; } async encryptFileData(data: ArrayBuffer): Promise<EncryptedDataType> { const key = await this.getKey(); const iv = this.getIv(); return { data: await crypto.subtle.encrypt({ name: this.algo, iv: iv }, key, data), metadata: iv, }; } // endregion // -------------------------------------------------------- // --------------------- Decryption ----------------------- // -------------------------------------------------------- // region Decryption decrypt(encryptedData: EncryptedDataType) { if (typeof encryptedData.data === 'string') throw new Error('Decryption error: data for decryption should be ArrayBuffed.'); const iv = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.metadata!)); const data = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.data)); return AesCbcCryptor.encoder.encode( this.CryptoJS.AES.decrypt({ ciphertext: data }, this.encryptedKey, { iv, mode: this.CryptoJS.mode.CBC, }).toString(this.CryptoJS.enc.Utf8), ).buffer; } async decryptFileData(encryptedData: EncryptedDataType): Promise<ArrayBuffer> { if (typeof encryptedData.data === 'string') throw new Error('Decryption error: data for decryption should be ArrayBuffed.'); const key = await this.getKey(); return crypto.subtle.decrypt({ name: this.algo, iv: encryptedData.metadata! }, key, encryptedData.data); } // endregion // -------------------------------------------------------- // ----------------------- Helpers ------------------------ // -------------------------------------------------------- // region Helpers get identifier() { return 'ACRH'; } /** * Cryptor algorithm. * * @returns Cryptor module algorithm. */ private get algo() { return 'AES-CBC'; } /** * Generate random initialization vector. * * @returns Random initialization vector. */ private getIv() { return crypto.getRandomValues(new Uint8Array(AesCbcCryptor.BLOCK_SIZE)); } /** * Convert cipher key to the {@link Buffer}. * * @returns SHA256 encoded cipher key {@link Buffer}. */ private async getKey() { const bKey = AesCbcCryptor.encoder.encode(this.cipherKey); const abHash = await crypto.subtle.digest('SHA-256', bKey.buffer); return crypto.subtle.importKey('raw', abHash, this.algo, true, ['encrypt', 'decrypt']); } /** * Convert bytes array to words array. * * @param b - Bytes array (buffer) which should be converted. * * @returns Word sized array. */ private bufferToWordArray(b: string | any[] | Uint8Array | Uint8ClampedArray) { const wa: number[] = []; let i; for (i = 0; i < b.length; i += 1) { wa[(i / 4) | 0] |= b[i] << (24 - 8 * i); } return this.CryptoJS.lib.WordArray.create(wa, b.length); } /** * Serialize cryptor information to string. * * @returns Serialized cryptor information. */ toString() { return `AesCbcCryptor { cipherKey: ${this.cipherKey} }`; } }