Cifrado híbrido AES-GCM + RSA-OAEP con firma Ed25519 e interoperabilidad entre Python, TypeScript y Rust
Project description
Cross Crypto Py

Cifrado híbrido interoperable entre Python y TypeScript, con diseño compatible para Rust.
Cross Crypto Py 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 y objetos serializados con
dill. - Stream portable
.ccencpara archivos grandes.
Introducción
Cross Crypto Py es una librería de cifrado híbrido diseñada para interoperar entre distintos lenguajes, especialmente Python ↔ TypeScript, 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,AADo 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.
dillpuede ejecutar código durante deserialización si el payload no es confiable.- No uses
mode="dill"con datos de origen no confiable, incluso si están cifrados o firmados.
Instalación
pip install cross-crypto-py
Requisitos
- Python ≥ 3.7
cryptographypycryptodomedill
Uso básico: JSON en memoria
from cross_crypto_py import generateRSAKeys, encryptHybrid, decryptHybrid
keys = generateRSAKeys(bits=4096)
public_key = keys["publicKey"]
private_key = keys["privateKey"]
payload = {
"mensaje": "Hola desde Python",
"ok": True,
}
encrypted = encryptHybrid(payload, public_key)
print(encrypted["mode"]) # json
print(encrypted["oaepHash"]) # sha256
decrypted = decryptHybrid(encrypted, private_key)
print(decrypted)
Salida esperada:
{
"mensaje": "Hola desde Python",
"ok": true
}
RSA-OAEP SHA-256 por defecto
Desde 2.0.0, el default es:
encrypted = encryptHybrid(
data,
public_key,
oaep_hash="sha256",
)
Y el descifrado detecta automáticamente el campo oaepHash si está presente:
decrypted = decryptHybrid(encrypted, private_key)
No necesitas pasar oaep_hash manualmente cuando el paquete trae:
{
"oaepHash": "sha256"
}
Compatibilidad legacy con SHA-1
Versiones anteriores usaban RSA-OAEP con SHA-1 por compatibilidad con algunas implementaciones.
Para cifrar en modo legacy:
encrypted = encryptHybrid(
{"legacy": True},
public_key,
oaep_hash="sha1",
)
Para descifrar paquetes viejos que no traen oaepHash:
decrypted = decryptHybrid(
encrypted_legacy,
private_key,
oaep_hash="sha1",
)
Si el paquete viejo trae:
{
"oaepHash": "sha1"
}
entonces decryptHybrid(...) lo detecta automáticamente.
Modos soportados
JSON
encrypted = encryptHybrid(
{"hello": "world"},
public_key,
mode="json",
)
decrypted = decryptHybrid(
encrypted,
private_key,
mode="json",
)
Binario
data = b"\x00\x01\x02hello\xff"
encrypted = encryptHybrid(
data,
public_key,
mode="binary",
)
decrypted = decryptHybrid(
encrypted,
private_key,
mode="binary",
)
assert decrypted == data
Dill
dill permite serializar objetos Python, pero no es interoperable con TypeScript/Rust.
Además, deserializar dill puede ejecutar código. Úsalo solo con datos confiables y en flujos completamente controlados.
En esta versión, mode="dill" espera una firma embebida válida para descifrar correctamente. Para interoperabilidad Python ↔ TypeScript usa json, binary, archivos ZIP o stream .ccenc.
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.
aad = {
"tenant": "acadyne",
"purpose": "test",
}
encrypted = encryptHybrid(
{"msg": "hola"},
public_key,
aad=aad,
)
decrypted = decryptHybrid(
encrypted,
private_key,
aad=aad,
)
AAD incorrecto:
decryptHybrid(
encrypted,
private_key,
aad={"tenant": "otro"},
)
Debe fallar con error de autenticación.
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 guarda en JSON .enc.json.
from cross_crypto_py.file_crypto import encryptFileHybrid, decryptFileHybrid
encrypted = encryptFileHybrid(
paths=["datos/", "documento.pdf"],
public_key=public_key,
save_file=True,
output_enc="datos.enc.json",
)
output_dir = decryptFileHybrid(
"datos.enc.json",
private_key,
extract_to="datos_descifrados",
)
print("Archivos restaurados en:", output_dir)
Cifrado de archivos con OAEP SHA-256
Por defecto:
encrypted = encryptFileHybrid(
paths=["datos/"],
public_key=public_key,
save_file=True,
output_enc="datos.enc.json",
)
equivale a:
encrypted = encryptFileHybrid(
paths=["datos/"],
public_key=public_key,
save_file=True,
output_enc="datos.enc.json",
oaep_hash="sha256",
)
Para compatibilidad legacy:
encrypted = encryptFileHybrid(
paths=["datos/"],
public_key=public_key,
save_file=True,
output_enc="datos_legacy.enc.json",
oaep_hash="sha1",
)
Y para descifrar un paquete viejo sin oaepHash:
output_dir = decryptFileHybrid(
"datos_legacy.enc.json",
private_key,
oaep_hash="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,contentMode,aad,cipherykeyWrap. - Ciphertext AES-GCM después del header.
from cross_crypto_py import encryptHybrid, decryptHybrid
encrypted = encryptHybrid(
"video.mp4",
public_key,
mode="binary",
stream=True,
output_path="video.mp4.ccenc",
)
output_path = decryptHybrid(
"video.mp4.ccenc",
private_key,
stream=True,
decrypted_output_path="video_restaurado.mp4",
)
print(output_path)
También puedes descifrar usando el objeto retornado:
output_path = decryptHybrid(
encrypted,
private_key,
stream=True,
decrypted_output_path="video_restaurado.mp4",
)
También puedes recuperar bytes directamente:
data = decryptHybrid(
"video.mp4.ccenc",
private_key,
stream=True,
return_bytes=True,
)
assert isinstance(data, bytes)
Archivos/carpetas con stream .ccenc
encryptFileHybrid(..., use_stream=True) empaqueta primero los archivos en un ZIP y luego cifra ese ZIP como .ccenc.
from cross_crypto_py.file_crypto import encryptFileHybrid, decryptFileHybrid
encrypted = encryptFileHybrid(
paths=["datos/"],
public_key=public_key,
output_enc="datos.ccenc",
use_stream=True,
attach_metadata=True,
)
print(encrypted["streamFormat"]) # envelope
print(encrypted["encryptedPath"]) # datos.ccenc
output_dir = decryptFileHybrid(
"datos.ccenc",
private_key,
extract_to="datos_descifrados",
)
print(output_dir)
En este modo:
output_encdebe apuntar normalmente a.ccenc.- No necesitas
save_file=True, porque el stream escribe directamente el archivo binario. - El resultado de
encryptFileHybrid(...)devuelve 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.
from cross_crypto_py import (
generateEd25519Keys,
signPayload,
verifyPayload,
)
keys = generateEd25519Keys()
payload = {
"user": "fabian",
"scope": "admin",
}
signature = signPayload(
payload,
keys["privateKey"],
key_id="v1",
)
ok = verifyPayload(
payload,
signature,
keys["publicKey"],
)
print(ok) # True
Verificación con expiración
Puedes limitar la edad aceptada de una firma:
ok = verifyPayload(
payload,
signature,
keys["publicKey"],
max_age_seconds=300,
)
Esto rechaza firmas demasiado antiguas o con timestamps demasiado adelantados.
Fingerprint de claves públicas
from cross_crypto_py import fingerprintPublicKey
fp = fingerprintPublicKey(keys["publicKey"])
print(fp)
El fingerprint se calcula normalizando la clave pública a DER y aplicando SHA-256.
Interoperabilidad Python ↔ TypeScript
El subconjunto interoperable entre Python y TypeScript es:
mode="json"mode="binary"- Archivos/carpetas vía ZIP cifrado
- Stream portable
.ccenc - AAD
- OAEP SHA-256 / SHA-1 según
oaepHash - Firmas Ed25519 sobre payloads JSON canónicos
mode="dill" es solo Python.
Sobre JSON para datos en memoria
{
"encryptedKey": "base64",
"encryptedData": "base64",
"nonce": "base64",
"tag": "base64",
"mode": "json | binary | dill",
"aad": "present | none",
"oaepHash": "sha1 | sha256"
}
Para interoperabilidad Python ↔ TypeScript, 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:
oaepHashdebe viajar en el sobre o en el header del.ccenc.- Si
aades"present", ambos lados deben usar exactamente el mismo AAD. - JSON cifrado se serializa como UTF-8 compacto, sin espacios innecesarios.
- Las firmas Ed25519 usan JSON canónico con claves ordenadas.
- Binario debe tratarse como bytes crudos, no como texto UTF-8.
dillsolo es compatible con Python.- El stream
.ccencno depende de archivos secundarios: header y ciphertext viajan juntos. - Para stream interoperable Python ↔ TypeScript, usa
contentMode="binary".
Ejemplo de roundtrip SHA-256
from cross_crypto_py import generateRSAKeys, encryptHybrid, decryptHybrid
keys = generateRSAKeys(bits=2048)
encrypted = encryptHybrid(
{"ok": True},
keys["publicKey"],
)
assert encrypted["oaepHash"] == "sha256"
decrypted = decryptHybrid(
encrypted,
keys["privateKey"],
)
assert decrypted == {"ok": True}
Ejemplo de roundtrip legacy SHA-1
from cross_crypto_py import generateRSAKeys, encryptHybrid, decryptHybrid
keys = generateRSAKeys(bits=2048)
encrypted = encryptHybrid(
{"legacy": True},
keys["publicKey"],
oaep_hash="sha1",
)
assert encrypted["oaepHash"] == "sha1"
decrypted = decryptHybrid(
encrypted,
keys["privateKey"],
)
assert 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 | ✅ |
Modo streaming portable .ccenc |
✅ |
| AAD para metadatos autenticados | ✅ |
| Firmas Ed25519 para payloads JSON | ✅ |
| Fingerprint SHA-256 de claves públicas | ✅ |
Stubs .pyi incluidos |
✅ |
| Interoperabilidad Python ↔ TypeScript | ✅ |
dill para objetos Python |
⚠️ Solo fuentes confiables |
Ecosistema Cross-Crypto
Licencia
MIT © Jose Fabian Soltero Escobar
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cross_crypto_py-2.0.0.tar.gz.
File metadata
- Download URL: cross_crypto_py-2.0.0.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5aab576cf5f805d1712e9937a9f641fe35507154e62cac940a61346e963c0f51
|
|
| MD5 |
bab99705a7a2dcb956155dd0b6b739ac
|
|
| BLAKE2b-256 |
9a0cc0e939a458ddb2a9003265aff3cc196954bccc16450204c40ac33062ff58
|
File details
Details for the file cross_crypto_py-2.0.0-py3-none-any.whl.
File metadata
- Download URL: cross_crypto_py-2.0.0-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b41f9b0641a03756f9cd2d83f8e7e3f1a8fe5755fc38582bddbae67328cc73b
|
|
| MD5 |
7fdf87e1ef60977b7b5b13f1e637838a
|
|
| BLAKE2b-256 |
b512ba5065d9593bb26c17bfee3301ea41fbe1a041a5cfa20e42d6d6430fe4aa
|