npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cross-crypto-ts

v2.0.0

Published

Cifrado híbrido AES-GCM + RSA-OAEP con interoperabilidad entre TypeScript y Python, con diseño compatible para Rust.

Readme

Cross Crypto TS

NPM Version License Build TypeScript

Cifrado híbrido interoperable entre TypeScript y Python, con diseño compatible para Rust.

Cross Crypto TS combina:

  • AES-256-GCM para cifrado autenticado.
  • RSA-OAEP para envolver la clave simétrica.
  • RSA-OAEP SHA-256 por defecto desde v2.0.0.
  • Compatibilidad legacy con RSA-OAEP SHA-1.
  • Soporte opcional para AAD.
  • Firmas Ed25519 para autenticidad de payloads JSON.
  • Soporte para JSON, binario, archivos, carpetas ZIP y stream portable .ccenc.

Introducción

Cross Crypto TS es una librería de cifrado híbrido diseñada para interoperar entre distintos lenguajes, especialmente TypeScript ↔ Python, con un contrato de formato pensado para extenderse a Rust.

Permite cifrar datos en un lenguaje y descifrarlos en otro manteniendo un formato de sobre estable.

Para datos en memoria, el sobre cifrado usa JSON:

{
  "encryptedKey": "...",
  "encryptedData": "...",
  "nonce": "...",
  "tag": "...",
  "mode": "json",
  "aad": "none",
  "oaepHash": "sha256"
}

Desde la versión 2.0.0, los paquetes cifrados incluyen el campo oaepHash:

{
  "oaepHash": "sha256"
}

Esto permite que el receptor sepa con qué hash OAEP debe descifrar la clave simétrica.

Para stream, desde 2.0.0, se usa un archivo binario portable .ccenc con header embebido.

Nota importante sobre seguridad

Esta librería ofrece cifrado autenticado con AES-GCM y envoltura de clave con RSA-OAEP.

Eso significa:

  • El contenido viaja cifrado.
  • Cualquier modificación del ciphertext, tag, AAD o clave cifrada debe fallar.
  • Solo quien tenga la clave privada RSA puede descifrar.
  • Con AAD puedes autenticar metadatos externos sin cifrarlos.
  • Con Ed25519 puedes firmar payloads JSON para verificar identidad/autenticidad del emisor.

Pero:

  • No es “seguridad bidireccional” automáticamente si ambas partes no gestionan sus propias claves correctamente.
  • No es un protocolo completo de mensajería E2E con doble ratchet, forward secrecy o rotación automática de claves.
  • El modo v8 es específico de Node.js y no debe asumirse interoperable con Python/Rust.
  • Si usas AAD como objeto JSON entre lenguajes, ambos lados deben producir exactamente los mismos bytes. Para máxima interoperabilidad, usa AAD como string o bytes cuando sea crítico.

Instalación

npm install cross-crypto-ts

Requisitos

  • Node.js >= 18
  • TypeScript >= 5 recomendado
  • Entorno Node.js CommonJS

Uso básico: JSON en memoria

import {
  generateRSAKeys,
  encryptHybrid,
  decryptHybrid,
} from "cross-crypto-ts";

const { publicKey, privateKey } = generateRSAKeys(4096);

const payload = {
  mensaje: "Hola desde TypeScript",
  ok: true,
};

const encrypted = encryptHybrid(payload, publicKey);

console.log(encrypted.mode);     // json
console.log(encrypted.oaepHash); // sha256

const decrypted = decryptHybrid(encrypted, privateKey);

console.log(decrypted);

Salida esperada:

{
  "mensaje": "Hola desde TypeScript",
  "ok": true
}

RSA-OAEP SHA-256 por defecto

Desde 2.0.0, el default es:

const encrypted = encryptHybrid(data, publicKey, "json", {
  oaepHash: "sha256",
});

Y el descifrado detecta automáticamente el campo oaepHash si está presente:

const decrypted = decryptHybrid(encrypted, privateKey);

No necesitas pasar oaepHash manualmente cuando el paquete trae:

{
  "oaepHash": "sha256"
}

Compatibilidad legacy con SHA-1

Para cifrar en modo legacy:

const encrypted = encryptHybrid(
  { legacy: true },
  publicKey,
  "json",
  {
    oaepHash: "sha1",
  }
);

Si el paquete trae:

{
  "oaepHash": "sha1"
}

entonces decryptHybrid(...) lo detecta automáticamente.

Para paquetes viejos sin oaepHash, puedes forzar el hash al descifrar:

const decrypted = decryptHybrid(
  encryptedLegacy,
  privateKey,
  {
    oaepHash: "sha1",
  }
);

Modos soportados

JSON

const encrypted = encryptHybrid(
  { hello: "world" },
  publicKey,
  "json"
);

const decrypted = decryptHybrid(
  encrypted,
  privateKey
);

Binario

import fs from "fs";
import { encryptHybrid, decryptHybrid } from "cross-crypto-ts";

const data = fs.readFileSync("foto.png");

const encrypted = encryptHybrid(
  data,
  publicKey,
  "binary"
);

const decrypted = decryptHybrid(
  encrypted,
  privateKey
);

fs.writeFileSync("foto_restaurada.png", decrypted);

V8

const encrypted = encryptHybrid(
  { complex: true, items: [1, 2, 3] },
  publicKey,
  "v8"
);

const decrypted = decryptHybrid(
  encrypted,
  privateKey
);

mode="v8" usa serialización interna de Node.js. No está pensado como formato interoperable con Python/Rust.

AAD: datos autenticados no cifrados

Puedes pasar AAD para autenticar metadatos externos.

El AAD no se cifra, pero sí queda protegido por el tag AES-GCM. Si el receptor usa un AAD diferente, el descifrado falla.

const aad = {
  tenant: "acadyne",
  purpose: "test",
};

const encrypted = encryptHybrid(
  { msg: "hola" },
  publicKey,
  "json",
  { aad }
);

const decrypted = decryptHybrid(
  encrypted,
  privateKey,
  { aad }
);

AAD incorrecto:

decryptHybrid(
  encrypted,
  privateKey,
  { aad: { tenant: "otro" } }
);

Debe fallar con error de autenticación.

Para interoperabilidad TypeScript ↔ Python, usa preferentemente AAD como string estable:

const aad = "tenant=acadyne;purpose=test";

Cifrado híbrido de archivos y carpetas

encryptFileHybrid empaqueta archivos/carpetas en un ZIP y cifra ese ZIP.

Por defecto usa modo no-stream: el ZIP se cifra como binario y se puede guardar en JSON .enc.json.

import {
  encryptFileHybrid,
  decryptFileHybrid,
} from "cross-crypto-ts";

const encrypted = encryptFileHybrid(
  ["datos/", "documento.pdf"],
  publicKey,
  {
    saveFile: true,
    outputEnc: "datos.enc.json",
    attachMetadata: true,
  }
);

const outputDir = decryptFileHybrid(
  "datos.enc.json",
  privateKey,
  "datos_descifrados"
);

console.log("Archivos restaurados en:", outputDir);

Cifrado de archivos con OAEP SHA-256

Por defecto:

const encrypted = encryptFileHybrid(
  ["datos/"],
  publicKey,
  {
    saveFile: true,
    outputEnc: "datos.enc.json",
  }
);

equivale a:

const encrypted = encryptFileHybrid(
  ["datos/"],
  publicKey,
  {
    saveFile: true,
    outputEnc: "datos.enc.json",
    oaepHash: "sha256",
  }
);

Para compatibilidad legacy:

const encrypted = encryptFileHybrid(
  ["datos/"],
  publicKey,
  {
    saveFile: true,
    outputEnc: "datos_legacy.enc.json",
    oaepHash: "sha1",
  }
);

Modo streaming portable .ccenc para archivos grandes

Desde 2.0.0, el modo stream produce un archivo binario portable .ccenc.

El archivo contiene:

  • Magic header CCRYPT2\n.
  • Longitud del header en 4 bytes big-endian.
  • Header JSON embebido con encryptedKey, nonce, tag, oaepHash, streamFormat, aad y contentMode.
  • Ciphertext AES-GCM después del header.

Esto permite que Python y TypeScript puedan descifrar el mismo archivo stream sin depender de un JSON externo.

const encrypted = encryptHybrid(
  "video.mp4",
  publicKey,
  "stream",
  {
    outputPath: "video.mp4.ccenc",
    contentMode: "binary",
  }
);

console.log(encrypted.encryptedPath); // video.mp4.ccenc

const outputPath = decryptHybrid(
  encrypted,
  privateKey,
  "video_restaurado.mp4"
);

console.log("Restaurado en:", outputPath);

También puedes descifrar pasando directamente la ruta .ccenc:

const outputPath = decryptHybrid(
  "video.mp4.ccenc",
  privateKey,
  "video_restaurado.mp4"
);

También puedes devolver bytes en memoria para stream:

const bytes = decryptHybrid(
  "video.mp4.ccenc",
  privateKey,
  {
    returnBytes: true,
  }
);

console.log(Buffer.isBuffer(bytes)); // true

Stream en archivos/carpetas con encryptFileHybrid

Para archivos/carpetas, encryptFileHybrid primero crea un ZIP temporal y luego puede cifrar ese ZIP en modo stream .ccenc.

const encrypted = encryptFileHybrid(
  ["datos/"],
  publicKey,
  {
    useStream: true,
    outputEnc: "datos.ccenc",
    attachMetadata: true,
    streamChunkSize: 64 * 1024,
  }
);

const outputDir = decryptFileHybrid(
  "datos.ccenc",
  privateKey,
  "datos_descifrados"
);

En este modo:

  • outputEnc debe apuntar normalmente a .ccenc.
  • No necesitas saveFile: true, porque el stream escribe directamente el archivo binario.
  • El resultado de encryptFileHybrid(...) sigue devolviendo metadata del sobre para inspección.

Firmas Ed25519 para payloads JSON

Además del cifrado, puedes firmar payloads JSON con Ed25519.

Esto sirve para verificar que un payload fue emitido por quien posee la clave privada de firma.

import {
  generateEd25519Keys,
  signPayload,
  verifyPayload,
} from "cross-crypto-ts";

const keys = generateEd25519Keys();

const payload = {
  user: "fabian",
  scope: "admin",
};

const signature = await signPayload(
  payload,
  keys.privateKey,
  {
    keyId: "v1",
  }
);

const ok = await verifyPayload(
  payload,
  signature,
  keys.publicKey
);

console.log(ok); // true

Verificación con expiración

Puedes limitar la edad aceptada de una firma:

const ok = await verifyPayload(
  payload,
  signature,
  keys.publicKey,
  {
    maxAgeSeconds: 300,
  }
);

Esto rechaza firmas demasiado antiguas o con timestamps demasiado adelantados.

Fingerprint de claves públicas

import { fingerprintPublicKey } from "cross-crypto-ts";

const fp = fingerprintPublicKey(keys.publicKey);

console.log(fp);

El fingerprint se calcula sobre los bytes DER contenidos en la clave pública PEM.

Interoperabilidad TypeScript ↔ Python

El subconjunto interoperable entre TypeScript y Python es:

  • mode="json"
  • mode="binary"
  • archivos/carpetas empaquetados como ZIP
  • stream portable .ccenc
  • AAD cuando ambos lados usan exactamente los mismos bytes
  • RSA-OAEP SHA-256 / SHA-1 según oaepHash
  • firmas Ed25519 sobre JSON canónico

mode="v8" es solo Node.js.

Sobre JSON para datos en memoria

{
  "encryptedKey": "base64",
  "encryptedData": "base64",
  "nonce": "base64",
  "tag": "base64",
  "mode": "json | binary | v8",
  "aad": "present | none",
  "oaepHash": "sha1 | sha256"
}

Para interoperabilidad TypeScript ↔ Python, usa normalmente:

{
  "mode": "json | binary"
}

Stream portable .ccenc

Para stream portable .ccenc, el contrato va embebido dentro del archivo:

{
  "version": 2,
  "format": "cross-crypto-stream",
  "streamFormat": "envelope",
  "cipher": "AES-256-GCM",
  "keyWrap": "RSA-OAEP",
  "encryptedKey": "base64",
  "nonce": "base64",
  "tag": "base64",
  "mode": "stream",
  "contentMode": "binary",
  "aad": "present | none",
  "oaepHash": "sha1 | sha256"
}

El formato binario del .ccenc es:

CCRYPT2\n
uint32_be(header_json_length)
header_json_utf8
ciphertext

Reglas importantes:

  • oaepHash debe viajar en el sobre o header.
  • Si aad es "present", ambos lados deben usar exactamente el mismo AAD.
  • JSON cifrado en memoria se serializa como UTF-8.
  • Las firmas Ed25519 usan JSON canónico con claves ordenadas.
  • Binario debe tratarse como bytes crudos, no como texto UTF-8.
  • v8 solo es compatible con Node.js.
  • Para stream interoperable usa .ccenc, normalmente con contentMode: "binary".

Ejemplo de roundtrip SHA-256

const keys = generateRSAKeys(2048);

const encrypted = encryptHybrid(
  { ok: true },
  keys.publicKey
);

if (encrypted.oaepHash !== "sha256") {
  throw new Error("OAEP hash inesperado");
}

const decrypted = decryptHybrid(
  encrypted,
  keys.privateKey
);

console.log(decrypted); // { ok: true }

Ejemplo de roundtrip legacy SHA-1

const keys = generateRSAKeys(2048);

const encrypted = encryptHybrid(
  { legacy: true },
  keys.publicKey,
  "json",
  {
    oaepHash: "sha1",
  }
);

if (encrypted.oaepHash !== "sha1") {
  throw new Error("OAEP hash inesperado");
}

const decrypted = decryptHybrid(
  encrypted,
  keys.privateKey
);

console.log(decrypted); // { legacy: true }

Características

| Característica | Estado | | -------------------------------------- | --------------------------------------- | | AES-256-GCM | ✅ | | RSA-OAEP SHA-256 por defecto | ✅ | | RSA-OAEP SHA-1 legacy | ✅ | | Campo oaepHash en el sobre | ✅ | | JSON en memoria | ✅ | | Binario en memoria | ✅ | | Archivos y carpetas vía ZIP | ✅ | | Stream portable .ccenc | ✅ | | AAD para metadatos autenticados | ✅ | | Firmas Ed25519 para payloads JSON | ✅ | | Fingerprint SHA-256 de claves públicas | ✅ | | Tipos TypeScript incluidos | ✅ | | Interoperabilidad TypeScript ↔ Python | ✅ | | Modo v8 | ⚠️ Solo Node.js |

Ecosistema Cross-Crypto

Licencia

MIT © Jose Fabian Soltero Escobar