UNPKG

node-laravel-encryptor

Version:
279 lines (278 loc) 9.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Serializer_1 = require("./lib/Serializer"); const EncryptorError_1 = require("./lib/EncryptorError"); const phpSerializer_1 = require("./serializers/phpSerializer"); const jsonSerializer_1 = require("./serializers/jsonSerializer"); const valid_aes = [128, 256]; const default_aes = 256; let crypto; try { crypto = require('crypto'); } catch (e) { throw new EncryptorError_1.EncryptorError(e.message); } class Base_encryptor { constructor(options, driver) { this.aes_mode = default_aes; this.valid_aes_modes = valid_aes; this.random_bytes = 8; this.default_serialize_mode = 'php'; this.options = Object.assign({}, { serialize_mode: this.default_serialize_mode }, options); this.setSerializerDriver(driver); this.setAlgorithm(); this.secret = Base_encryptor.prepareAppKey(this.options.key); this.random_bytes = this.options.random_bytes ? this.options.random_bytes : this.random_bytes; } encryptIt(data) { return this .generate_iv() .then(this.createCypherIv()) .then(this.cipherIt(data)) .then(this.generateEncryptedObject()); } encryptItSync(data) { const iv = this.generate_iv_sync(); const cipher = this.createCipher(iv); const value = Base_encryptor.cryptoUpdate(cipher, data); return this.generateEncryptedObject()({ iv, value }); } decryptIt(encrypted) { let payload; try { payload = JSON.parse(encrypted); } catch (e) { Base_encryptor.throwError('Encryptor decryptIt cannot parse json'); } if (!Base_encryptor.validPayload(payload)) Base_encryptor.throwError('The payload is invalid.'); if (!this.validMac(payload)) Base_encryptor.throwError('The MAC is invalid.'); const decipherIv = this.createDecipheriv(payload.iv); const decrypted = Base_encryptor.cryptoDecipher(payload, decipherIv); if (process.env.NODE_ENV === 'test') this.raw_decrypted = decrypted; return this.ifserialized_unserialize(decrypted); } static prepareAppKey(key) { if (!key) Base_encryptor.throwError('no app key given'); return Buffer.from(key, 'base64'); } setSerializerDriver(driver) { if (driver) { if (!Base_encryptor.validateSerializerDriver(driver)) Base_encryptor.throwError('validateSerializerDriver'); this.serialize_driver = new Serializer_1.Serializer(new driver); this.options.serialize_mode = 'custom'; } else { this.serialize_driver = new Serializer_1.Serializer(this.pickSerializeDriver()); } } static validateSerializerDriver(driver) { try { const custom_driver = new driver; return Base_encryptor .validateSerializerImplementsSerializerInterface(custom_driver); } catch (e) { Base_encryptor.throwError('validateSerializerDriver'); } } static validateSerializerImplementsSerializerInterface(driver) { return (typeof driver['serialize'] === 'function') && (typeof driver['unSerialize'] === 'function'); } pickSerializeDriver() { if (!this.options.serialize_mode) this.options.serialize_mode = 'php'; switch (this.options.serialize_mode) { case 'json': { return new jsonSerializer_1.JsonSerializer; } case 'php': { return new phpSerializer_1.PhpSerializer; } default: { throw new EncryptorError_1.EncryptorError(`Serializer Encryptor Class unknown option ${this.options.serialize_mode} serialize_mode`); } } } setAlgorithm() { if (this.options.key_length && this.valid_aes_modes.indexOf(this.options.key_length) < 0) Base_encryptor.throwError('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.'); this.algorithm = this.options.key_length ? `aes-${this.options.key_length}-cbc` : `aes-${this.aes_mode}-cbc`; } prepareDataToCipher(data, force_serialize) { if (force_serialize === true && this.serialize_driver.getDriverName() === 'PhpSerializer') { return this.serialize_driver.serialize(data); } data = Base_encryptor.ifNumberToString(data); return this.ifObjectToString(data); } prepareDataToDecipher(data) { return Base_encryptor.base64ToUtf8(data); } static validPayload(payload) { return payload.hasOwnProperty('iv') && payload.hasOwnProperty('value') && payload.hasOwnProperty('mac') && Buffer.from(payload.iv, 'base64').toString('hex').length === 32; } validMac(payload) { try { const calculated = this.hashIt(payload.iv, payload.value); return crypto.timingSafeEqual(Buffer.from(calculated), Buffer.from(payload.mac)); } catch (e) { return false; } } static cryptoUpdate(cipher, data) { try { return cipher.update(data, 'utf8', 'base64') + cipher.final('base64'); } catch (e) { Base_encryptor.throwError(e.message); } } createCipher(iv) { try { return crypto.createCipheriv(this.algorithm, this.secret, iv); } catch (e) { Base_encryptor.throwError(e.message); } } createCypherIv() { return (iv) => { return { iv, cipher: this.createCipher(iv) }; }; } cipherIt(data) { return ({ iv, cipher }) => { return { iv, value: Base_encryptor.cryptoUpdate(cipher, data) }; }; } generate_iv() { return new Promise((resolve, reject) => { crypto.randomBytes(this.random_bytes, (err, buffer) => { if (err) return reject(err); resolve(buffer.toString('hex')); }); }); } generate_iv_sync() { try { const buf = crypto.randomBytes(this.random_bytes); return buf.toString('hex'); } catch (e) { Base_encryptor.throwError('generate_iv_sync error generating random bytes'); } } generateEncryptedObject() { return ({ iv, value }) => { iv = Base_encryptor.toBase64(iv); return { iv, value, mac: this.hashIt(iv, value) }; }; } createDecipheriv(iv) { try { return crypto.createDecipheriv(this.algorithm, this.secret, Buffer.from(iv, 'base64')); } catch (e) { Base_encryptor.throwError(e.message); } } static cryptoDecipher(payload, decipher) { try { return decipher.update(payload.value, 'base64', 'utf8') + decipher.final('utf8'); } catch (e) { Base_encryptor.throwError(e.message); } } ifserialized_unserialize(decrypted) { return this.serialize_driver.unSerialize(decrypted); } hashIt(iv, encrypted) { try { const hmac = Base_encryptor.createHmac("sha256", this.secret); return hmac .update(Base_encryptor.setHmacPayload(iv, encrypted)) .digest("hex"); } catch (e) { Base_encryptor.throwError(e.message); } } serialize(data) { return this.serialize_driver.serialize(data); } unserialize(data) { return this.serialize_driver.unSerialize(data); } static toBase64(data) { return Buffer.from(data).toString('base64'); } static base64ToUtf8(data) { if (typeof data !== 'string') throw new EncryptorError_1.EncryptorError('base64ToUtf8 Error data arg not a string'); return Buffer.from(data, 'base64').toString('utf8'); } static createHmac(alg, secret) { try { return crypto.createHmac(alg, secret); } catch (e) { Base_encryptor.throwError(e.message); } } static setHmacPayload(iv, encrypted) { return Buffer.from(iv + encrypted, 'utf-8'); } static stringifyAndBase64(encrypted) { const payload = JSON.stringify(encrypted); return Buffer.from(payload).toString('base64'); } ifObjectToString(data) { return (typeof data === 'object') ? this.serialize(data) : data; } static ifNumberToString(data) { return (typeof data === 'number') ? data + '' : data; } static throwError(error) { if (error.name === 'EncryptorError') throw error; throw new EncryptorError_1.EncryptorError(error); } static generateRandomKey(length) { length = length ? length : default_aes; if (valid_aes.indexOf(length) < 0) { console.error('valid options are: ', valid_aes); return; } try { const buf = crypto.randomBytes(length / 8); return buf.toString('base64'); } catch (e) { Base_encryptor.throwError(e.message); } } getRawDecrypted() { return this.raw_decrypted; } } exports.Base_encryptor = Base_encryptor;