@robotti.io/escrypt
Version:
Enterprise Secret Encryptor
115 lines (103 loc) • 3.61 kB
JavaScript
const crypto = require('crypto');
const fs = require('fs');
const { Validator } = require('jsonschema');
const keySchema = {
id: '/keySchema',
type: 'object',
additionalFields: false,
properties: {
publicKey: { type: 'string' },
privateKey: { type: 'string' }
}
};
class Cryptor {
/**
* Private static field declarations
*/
#privateKeyFile;
#publicKeyFile;
#passphrase;
/**
* Secret encryption / decryption class constructor
* @param {Object} keys - Object containing private and/or public key paths
* @param {string} keys.publicKey - Relative or absolute path to the public key file
* @param {string} keys.privateKey - Relative or absolute path to the private key file
* @param {string} passphrase - Passphrase for the private key
*/
constructor(keys, passphrase = false) {
/**
* Parameter Validation
*/
// Key Validation
const validator = new Validator();
const validationResults = validator.validate(keys, keySchema);
if (!validationResults.valid) throw new Error(validationResults);
if (typeof keys !== 'object') throw new Error('Expected keys to be an object');
const { privateKey, publicKey } = keys;
if (privateKey) this.#privateKeyFile = privateKey;
if (publicKey) this.#publicKeyFile = publicKey;
// Passphrase Validation
if (passphrase && typeof passphrase !== 'string') throw new Error('Passphrase must be a string');
if (passphrase) this.#passphrase = passphrase;
}
/**
* Encrypt single secret using public key
* @param {string} secret - UTF8 Secret to encrypt
* @returns {string} - BASE64 Encrypted secret
*/
encrypt(secret) {
if (!fs.existsSync(this.#publicKeyFile)) throw new Error('Public key file provided does not exist');
if (typeof secret !== 'string') throw new Error('Secret must be a string');
const key = fs.readFileSync(this.#publicKeyFile, 'utf8');
const buffer = Buffer.from(secret, 'utf8');
const encrypted = crypto.publicEncrypt(key, buffer);
return encrypted.toString('base64');
}
/**
* Secrypt secret using private key
* @param {string} secret - BASE64 Encrypted secret
* @returns {string} - UTF8 Decrypted secret
*/
decrypt(secret) {
if (!fs.existsSync(this.#privateKeyFile)) throw new Error('Private key file provided does not exists');
if (typeof secret !== 'string') throw new Error('Encrypted secret must be a string');
const key = {
key: fs.readFileSync(this.#privateKeyFile, 'utf8').toString()
};
if (this.#passphrase) {
key.passphrase = this.#passphrase;
}
const buffer = Buffer.from(secret, 'base64');
const decrypted = crypto.privateDecrypt(key, buffer);
return decrypted.toString();
}
/**
* Generate a public / private key pair
* @returns {Object} - Object containing the key file paths
*/
generateKeys() {
const parameters = {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
};
if (this.#passphrase) {
parameters.privateKeyEncoding.passphrase = this.#passphrase;
parameters.privateKeyEncoding.cipher = 'aes-256-cbc';
}
const newKeys = crypto.generateKeyPairSync('rsa', parameters);
fs.writeFileSync(this.#publicKeyFile, newKeys.publicKey);
fs.writeFileSync(this.#privateKeyFile, newKeys.privateKey);
return {
privateKeyFile: this.#privateKeyFile,
publicKeyFile: this.#publicKeyFile
};
}
}
module.exports = Cryptor;