@fbl-plugins/crypto
Version:
FBL file encryption/decryption plugin
156 lines • 6.13 kB
JavaScript
;
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 });
exports.CryptoService = void 0;
const crypto_1 = require("crypto");
const fs_1 = require("fs");
const util_1 = require("util");
const fbl_1 = require("fbl");
const path_1 = require("path");
class CryptoService {
constructor() { }
static get instance() {
if (!this.pInstance) {
this.pInstance = new CryptoService();
}
return this.pInstance;
}
/* istanbul ignore next */
static reset() {
this.pInstance = null;
}
/**
* Create pbkdf2 hash with 100k iterations with provided password and optionally salt
* If salt is not provided - it will be generated and returned back
* @param password
* @param salt
*/
getPasswordHash(password, salt) {
return __awaiter(this, void 0, void 0, function* () {
salt = salt || crypto_1.randomBytes(CryptoService.saltSize);
const hash = yield util_1.promisify(crypto_1.pbkdf2)(password, salt, CryptoService.hashIterations, CryptoService.keySize, CryptoService.hashAlgorithm);
return {
salt: salt,
hash: hash,
};
});
}
/**
* Encrypt file with provided password
* @param source
* @param destination
* @param password
*/
encrypt(source, destination, password) {
return __awaiter(this, void 0, void 0, function* () {
const passwordHash = yield this.getPasswordHash(password);
const iv = crypto_1.randomBytes(CryptoService.ivSize);
const cipher = crypto_1.createCipheriv(CryptoService.encryptionAlgorithm, passwordHash.hash, iv);
let tmpFile;
let ws;
if (source === destination) {
tmpFile = yield fbl_1.TempPathsRegistry.instance.createTempFile(true);
ws = fs_1.createWriteStream(tmpFile);
}
else {
yield fbl_1.FSUtil.mkdirp(path_1.dirname(destination));
ws = fs_1.createWriteStream(destination);
}
const rs = fs_1.createReadStream(source);
const writeAsync = (chunk) => {
return new Promise((resolve) => {
ws.write(chunk, () => resolve());
});
};
yield writeAsync(CryptoService.version);
yield writeAsync(passwordHash.salt);
yield writeAsync(iv);
yield new Promise((resolve) => {
rs.pipe(cipher).pipe(ws);
rs.on('close', () => {
ws.on('finish', () => {
resolve();
});
ws.end();
});
});
if (source === destination) {
yield util_1.promisify(fs_1.rename)(tmpFile, destination);
}
});
}
/**
* Decrypt file
* @param source
* @param destination
* @param password
*/
decrypt(source, destination, password) {
return __awaiter(this, void 0, void 0, function* () {
const headerSize = 2 + CryptoService.saltSize + CryptoService.ivSize;
let rs = fs_1.createReadStream(source, {
end: headerSize,
});
const header = yield this.streamToBuffer(rs);
const salt = header.slice(2, 2 + CryptoService.saltSize);
const iv = header.slice(2 + CryptoService.saltSize, headerSize);
rs = fs_1.createReadStream(source, {
start: headerSize,
});
const passwordHash = yield this.getPasswordHash(password, salt);
const decipher = crypto_1.createDecipheriv(CryptoService.encryptionAlgorithm, passwordHash.hash, iv);
let ws;
let tmpFile;
if (source === destination) {
tmpFile = yield fbl_1.TempPathsRegistry.instance.createTempFile(true);
ws = fs_1.createWriteStream(tmpFile);
}
else {
yield fbl_1.FSUtil.mkdirp(path_1.dirname(destination));
ws = fs_1.createWriteStream(destination);
}
yield new Promise((resolve) => {
rs.pipe(decipher).pipe(ws);
rs.on('close', () => {
ws.on('finish', () => {
resolve();
});
ws.end();
});
});
if (source === destination) {
yield util_1.promisify(fs_1.rename)(tmpFile, destination);
}
});
}
/**
* Read stream into buffer
* @param stream
*/
streamToBuffer(stream) {
return new Promise((resolve, reject) => {
const buffers = [];
stream.on('error', reject);
stream.on('data', (data) => buffers.push(data));
stream.on('end', () => resolve(Buffer.concat(buffers)));
});
}
}
exports.CryptoService = CryptoService;
CryptoService.encryptionAlgorithm = 'aes-256-cbc';
CryptoService.hashAlgorithm = 'sha512';
CryptoService.hashIterations = 100000;
CryptoService.keySize = 32;
CryptoService.ivSize = 16;
CryptoService.saltSize = 24;
// encryption logic version, reserved for future use
CryptoService.version = Buffer.alloc(2, '0001', 'hex');
//# sourceMappingURL=CryptoService.js.map