cross-crypto-ts
Version:
Cifrado híbrido AES-GCM + RSA-OAEP con interoperabilidad entre TypeScript y Python, con diseño compatible para Rust.
122 lines (121 loc) • 4.74 kB
JavaScript
;
// src/file_crypto.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.encryptFileHybrid = encryptFileHybrid;
exports.decryptFileHybrid = decryptFileHybrid;
const fs_1 = __importDefault(require("fs"));
const encrypt_1 = require("./encrypt");
const core_1 = require("./core");
const decrypt_1 = require("./decrypt");
const STREAM_ENVELOPE_MAGIC = Buffer.from("CCRYPT2\n", "utf8");
function isStreamEnvelopeFile(filePath) {
try {
if (!fs_1.default.existsSync(filePath) || !fs_1.default.statSync(filePath).isFile()) {
return false;
}
const fd = fs_1.default.openSync(filePath, "r");
const magic = Buffer.alloc(STREAM_ENVELOPE_MAGIC.length);
try {
const n = fs_1.default.readSync(fd, magic, 0, magic.length, 0);
return n === magic.length && magic.equals(STREAM_ENVELOPE_MAGIC);
}
finally {
fs_1.default.closeSync(fd);
}
}
catch {
return false;
}
}
function outputBase(encPath) {
if (encPath.endsWith(".enc.json"))
return encPath.slice(0, -9);
if (encPath.endsWith(".ccenc"))
return encPath.slice(0, -6);
if (encPath.endsWith(".enc"))
return encPath.slice(0, -4);
return encPath;
}
function encryptFileHybrid(paths, publicKey, options = {}) {
if (!paths.length || !paths.every(fs_1.default.existsSync)) {
throw new Error("Una o más rutas no existen o la lista está vacía.");
}
const zipName = options.zipOutput || `temp_${Date.now()}.zip`;
const zipPath = (0, core_1.createZipFromPaths)(paths, zipName);
try {
const useStream = options.useStream ?? false;
if (useStream) {
const outPath = options.outputEnc || zipPath + ".ccenc";
const encrypted = (0, encrypt_1.encryptHybrid)(zipPath, publicKey, "stream", {
oaepHash: options.oaepHash,
aad: options.aad,
outputPath: outPath,
contentMode: "binary",
streamChunkSize: options.streamChunkSize,
});
encrypted.originalPaths = paths;
encrypted.originalPathsRel = paths.map((p) => p.split(/[\\/]/).pop() ?? p);
if (options.attachMetadata) {
encrypted.meta = (0, core_1.collectMetadata)(zipPath);
}
return encrypted;
}
const binaryData = (0, core_1.readBinaryFile)(zipPath);
const encrypted = (0, encrypt_1.encryptHybrid)(binaryData, publicKey, "binary", {
oaepHash: options.oaepHash,
aad: options.aad,
});
encrypted.originalPaths = paths;
encrypted.originalPathsRel = paths.map((p) => p.split(/[\\/]/).pop() ?? p);
if (options.attachMetadata) {
encrypted.meta = (0, core_1.collectMetadata)(zipPath);
}
if (options.saveFile) {
const outPath = options.outputEnc || zipPath + ".enc.json";
(0, core_1.saveEncryptedJson)(outPath, encrypted);
}
return encrypted;
}
finally {
if (fs_1.default.existsSync(zipPath)) {
fs_1.default.unlinkSync(zipPath);
}
}
}
function decryptFileHybrid(encPath, privateKey, extractTo, cleanupZip = true, options = {}) {
if (!fs_1.default.existsSync(encPath)) {
throw new Error(`Archivo cifrado no encontrado: ${encPath}`);
}
const base = outputBase(encPath);
const tempZipPath = base + ".zip";
try {
if (isStreamEnvelopeFile(encPath)) {
const decryptedPath = (0, decrypt_1.decryptHybrid)(encPath, privateKey, tempZipPath, {
oaepHash: options.oaepHash,
aad: options.aad,
});
if (typeof decryptedPath !== "string") {
throw new Error("El descifrado stream no devolvió una ruta de archivo.");
}
}
else {
const encryptedObj = (0, core_1.loadEncryptedJson)(encPath);
const decryptedBinary = (0, decrypt_1.decryptHybrid)(encryptedObj, privateKey, {
oaepHash: options.oaepHash,
aad: options.aad,
});
(0, core_1.writeBinaryFile)(tempZipPath, Buffer.from(decryptedBinary));
}
const outputDir = extractTo || base + "_output";
(0, core_1.extractZipToDir)(tempZipPath, outputDir);
return outputDir;
}
finally {
if (cleanupZip && fs_1.default.existsSync(tempZipPath)) {
fs_1.default.unlinkSync(tempZipPath);
}
}
}