UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

161 lines (160 loc) 5.99 kB
"use strict"; /** * AES-CBC cryptor module. * * @internal */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = require("crypto"); const stream_1 = require("stream"); /** * AES-CBC cryptor. * * AES-CBC cryptor with enhanced cipher strength. * * @internal */ class AesCbcCryptor { constructor({ cipherKey }) { this.cipherKey = cipherKey; } // -------------------------------------------------------- // --------------------- Encryption ----------------------- // -------------------------------------------------------- // region Encryption encrypt(data) { const iv = this.getIv(); const key = this.getKey(); const plainData = typeof data === 'string' ? AesCbcCryptor.encoder.encode(data) : data; const bPlain = Buffer.from(plainData); if (bPlain.byteLength === 0) throw new Error('Encryption error: empty content'); const aes = (0, crypto_1.createCipheriv)(this.algo, key, iv); return { metadata: iv, data: Buffer.concat([aes.update(bPlain), aes.final()]), }; } encryptStream(stream) { return __awaiter(this, void 0, void 0, function* () { if (!stream.readable) throw new Error('Encryption error: empty stream'); const output = new stream_1.PassThrough(); const bIv = this.getIv(); const aes = (0, crypto_1.createCipheriv)(this.algo, this.getKey(), bIv); stream.pipe(aes).pipe(output); return { stream: output, metadata: bIv, metadataLength: AesCbcCryptor.BLOCK_SIZE, }; }); } // endregion // -------------------------------------------------------- // --------------------- Decryption ----------------------- // -------------------------------------------------------- // region Decryption decrypt(input) { const data = typeof input.data === 'string' ? new TextEncoder().encode(input.data) : input.data; if (data.byteLength <= 0) throw new Error('Decryption error: empty content'); const aes = (0, crypto_1.createDecipheriv)(this.algo, this.getKey(), input.metadata); const decryptedDataBuffer = Buffer.concat([aes.update(data), aes.final()]); return decryptedDataBuffer.buffer.slice(decryptedDataBuffer.byteOffset, decryptedDataBuffer.byteOffset + decryptedDataBuffer.length); } decryptStream(stream) { return __awaiter(this, void 0, void 0, function* () { const decryptedStream = new stream_1.PassThrough(); let bIv = Buffer.alloc(0); let aes = null; const onReadable = () => { let data = stream.stream.read(); while (data !== null) { if (data) { const bChunk = typeof data === 'string' ? Buffer.from(data) : data; const sliceLen = stream.metadataLength - bIv.byteLength; if (bChunk.byteLength < sliceLen) { bIv = Buffer.concat([bIv, bChunk]); } else { bIv = Buffer.concat([bIv, bChunk.slice(0, sliceLen)]); aes = (0, crypto_1.createDecipheriv)(this.algo, this.getKey(), bIv); aes.pipe(decryptedStream); aes.write(bChunk.slice(sliceLen)); } } data = stream.stream.read(); } }; stream.stream.on('readable', onReadable); stream.stream.on('end', () => { if (aes) aes.end(); decryptedStream.end(); }); return decryptedStream; }); } // endregion // -------------------------------------------------------- // ----------------------- Helpers ------------------------ // -------------------------------------------------------- // region Helpers get identifier() { return 'ACRH'; } /** * Cryptor algorithm. * * @returns Cryptor module algorithm. */ get algo() { return 'aes-256-cbc'; } /** * Generate random initialization vector. * * @returns Random initialization vector. */ getIv() { return (0, crypto_1.randomBytes)(AesCbcCryptor.BLOCK_SIZE); } /** * Convert cipher key to the {@link Buffer}. * * @returns SHA256 encoded cipher key {@link Buffer}. */ getKey() { const sha = (0, crypto_1.createHash)('sha256'); sha.update(Buffer.from(this.cipherKey, 'utf8')); return Buffer.from(sha.digest()); } // endregion /** * Serialize cryptor information to string. * * @returns Serialized cryptor information. */ toString() { return `AesCbcCryptor { cipherKey: ${this.cipherKey} }`; } } /** * Cryptor block size. */ AesCbcCryptor.BLOCK_SIZE = 16; /** * {@link string|String} to {@link ArrayBuffer} response decoder. */ AesCbcCryptor.encoder = new TextEncoder(); exports.default = AesCbcCryptor;