
Un motor de cifrado de archivos de múltiples hilos y gigabytes por segundo. Logra un rendimiento extremo mediante un pipeline io_uring de triple búfer sin bloqueos, fragmentación paralela con Rayon y AEADs acelerados por hardware (AES-256-GCM / ChaCha20).
Un motor de cifrado AEAD multi-hilo escrito en Rust. Cifra y descifra archivos a un rendimiento de gigabytes por segundo utilizando un pipeline de io_uring con triple búfer, procesamiento paralelo de fragmentos mediante Rayon y cifrados optimizados en ensamblador a través de ring.
⚠️ AVISO: SOFTWARE EXPERIMENTAL ⚠️
Este proyecto es extremadamente nuevo y actualmente NO se recomienda para uso en producción o en entornos críticos. Si bien los primitivos criptográficos (AES-256-GCM, ChaCha20-Poly1305 mediante ring) y el diseño del formato son sólidos, el código base no ha pasado por auditorías de seguridad formales ni pruebas exhaustivas en el mundo real. Úselo bajo su propio riesgo. Para proteger datos sensibles, considere usar herramientas probadas como GnuPG, age u OpenSSL hasta que este proyecto madure.
ring (optimizado en ensamblador)--memory)seal_in_place_separate_tag / open_in_place mediante ring minimiza las asignaciones en el bucle principalO_DIRECT, evitando la caché de páginas del kernel para lecturas/escrituras a velocidad DMA en NVMe. Los grupos de búferes usan std::alloc con alineación de 4096 bytesBenchmark con cargo bench (Criterion, 10 muestras por medición). La derivación de clave está excluida: los números reflejan solo el rendimiento criptográfico puro.
Hardware:
Nota sobre E/S: Criterion escribe archivos temporales en /tmp, que en este sistema es tmpfs (respaldado por RAM). Con O_DIRECT, el kernel no puede usar DMA asíncrono real en tmpfs, por lo que estos números reflejan el rendimiento del cifrado + la sobrecarga de io_uring sin el beneficio de evitar DMA. En una unidad NVMe Gen4 real, O_DIRECT elimina el doble buffer de la caché de páginas y permite DMA directamente en los grupos de búferes alineados, lo que debería proporcionar un rendimiento significativamente mayor.
Barrido de tamaños de fragmento (AES-256-GCM, archivo de 64 MiB):
El motor utiliza ring (AES-NI / NEON / ARMv8-CE optimizado en ensamblador) para las operaciones de cifrado y un pipeline de io_uring con triple búfer para E/S. Tres grupos de búferes preasignados rotan a través del pipeline: mientras las escrituras del grupo A se completan en el kernel, el grupo B está siendo cifrado por Rayon en la CPU, y las lecturas del grupo C se están enviando al kernel. Esto superpone la latencia de E/S con el cómputo criptográfico.
Por qué AES-256-GCM es más rápido que ChaCha20-Poly1305 en archivos pequeños:
El backend de AES-GCM de ring explota las instrucciones de hardware AES-NI + CLMUL disponibles en x86-64, lo que le otorga una ventaja de hardware sobre ChaCha20 (que es un cifrado por software). En tamaños más grandes, ambos cifrados convergen a ~1.0 GiB/s, lo que indica que el cuello de botella se desplaza del rendimiento del cifrado a la sobrecarga de envío de E/S.
Por qué el rendimiento máximo está en 1-16 MiB, no en 256 MiB: Los archivos pequeños (1-16 MiB) tienen pocos fragmentos, por lo que el paralelismo de Rayon es eficiente y el conjunto de trabajo cabe en la caché. En 64-256 MiB, el pipeline de io_uring está completamente activo (tres lotes en vuelo), pero la sobrecarga de envío por SQE y finalización por CQE escala con el número de fragmentos. El diseño de triple búfer asegura que E/S y cifrado se superpongan, ocultando parcialmente este costo.
Por qué ~1.0 GiB/s y no 10+ GiB/s: El AES-NI moderno puede alcanzar 2-4 GiB/s por núcleo. Con 12 hilos, el rendimiento bruto del cifrado podría exceder 10 GiB/s. Tres factores explican la brecha:
PIPELINE_DEPTH=3, solo tres lotes rotan a través del pipeline en un momento dado. La superposición en estado estacionario verdadera requiere al menos tres lotes; los archivos que caben en uno o dos lotes no se benefician del pipeline.Ciclo de vida y seguridad de los búferes:
Los grupos de búferes se asignan una vez mediante std::alloc::alloc_zeroed con Layout::from_size_align(size, 4096) antes de que se cree el anillo io_uring, y se reutilizan en todas las iteraciones del pipeline sin reasignación. Cada fragmento cifrado se rellena con ceros hasta la alineación de sector antes de la escritura con O_DIRECT. El anillo se elimina explícitamente antes que los grupos de búferes, asegurando que el kernel nunca haga referencia a memoria liberada (sin UAF).
git clone https://github.com/frogsnot/concryptor.git
cd concryptor
cargo build --release
El binario estará en target/release/concryptor.
# AES-256-GCM (predeterminado), salida a myfile.dat.enc
concryptor encrypt myfile.dat
# ChaCha20-Poly1305, ruta de salida personalizada
concryptor encrypt myfile.dat --cipher chacha -o encrypted.enc
# Tamaño de fragmento personalizado (en MiB)
concryptor encrypt largefile.iso --chunk-size 8
# KDF más fuerte (costo de memoria de 512 MiB)
concryptor encrypt secrets.tar --memory 512
# No interactivo (omite la solicitud de contraseña)
concryptor encrypt myfile.dat -p "password"
Nota de seguridad:
--password/-ppasa la contraseña como argumento de CLI, que es visible en la salida depsy en el historial del shell. Para uso interactivo, omítalo para obtener la solicitud oculta segura. Para scripts, prefiera limpiar el historial después o usar un envoltorio que lea desde un descriptor de archivo.
# Cifrar un directorio (detección automática, produce mydir.tar.enc)
concryptor encrypt mydir/
# Con cifrado y salida personalizados
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
El cifrado de directorios crea un archivo tar temporal (.concryptor-*.tar, permisos 0600, nombre generado por CSPRNG), lo cifra y luego elimina automáticamente el archivo temporal. Los nombres de archivo, la estructura de directorios, los permisos y las marcas de tiempo están todos dentro de la carga útil cifrada.
# Elimina automáticamente la extensión .enc
concryptor decrypt myfile.dat.enc
# Ruta de salida personalizada
concryptor decrypt encrypted.enc -o restored.dat
# No interactivo
concryptor decrypt myfile.dat.enc -p "password"
# Descifrar y extraer en un solo paso (elimina automáticamente .tar.enc -> nombre de directorio)
concryptor decrypt mydir.tar.enc --extract
# Bandera corta, directorio de salida personalizado
concryptor decrypt mydir.tar.enc -x -o restored_dir/
Sin --extract, descifrar un archivo de directorio produce el archivo .tar intermedio, que puede inspeccionar o extraer manualmente.
concryptor --help
concryptor encrypt --help
concryptor decrypt --help
Todos los valores están en little-endian. La cabecera ocupa un sector completo de 4 KiB; cada ranura de fragmento cifrado se rellena hasta el siguiente límite de 4 KiB. Esto asegura que cada desplazamiento y tamaño de E/S esté alineado a sector para O_DIRECT.
Offset Tamaño Campo
------ ------ ---------------------
0 10 Bytes mágicos "CONCRYPTOR"
10 1 Versión de formato (4)
11 1 Tipo de cifrado (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
12 4 Tamaño de fragmento (bytes, LE)
16 8 Tamaño original del archivo (bytes, LE)
24 16 Sal Argon2 (criptográficamente aleatoria, única por archivo)
40 12 Nonce base (criptográficamente aleatorio, único por archivo)
52 4 Argon2 m_cost en KiB (LE, 0 = heredado 64 MiB)
56 4 Argon2 t_cost / iteraciones (LE, 0 = heredado 3)
60 4 Argon2 p_cost / paralelismo (LE, 0 = heredado 4)
64 4032 Reservado (relleno con ceros hasta 4096 bytes)
4096 ... [Fragmento 0: texto cifrado + etiqueta de 16 bytes + relleno de ceros hasta límite de sector]
[Fragmento 1: texto cifrado + etiqueta de 16 bytes + relleno de ceros hasta límite de sector]
...
Para fragmentos de 4 MiB: cada ranura de disco es ceil((4194304 + 16) / 4096) * 4096 = 4198400 bytes (4080 bytes de relleno por fragmento). Los 4032 bytes reservados en la cabecera están disponibles para características futuras (ranuras de clave asimétrica, metadatos, etc.).
La sal y el nonce base se generan frescos a partir de rand::rng() (respaldado por el CSPRNG del SO) en cada cifrado. Reutilizar una contraseña entre archivos es seguro porque diferentes sales producen diferentes claves Argon2id, y diferentes nonces base producen nonces por fragmento diferentes.
chunk_nonce = base_nonce XOR chunk_index (estilo TLS 1.3). Intercambiar fragmentos provoca fallo de descifrado porque el nonce en la posición N no coincidirá con el nonce usado para cifrar el fragmento originalmente en la posición M. Nota: La derivación de nonce mediante XOR tiene una debilidad teórica cuando se usa la misma clave en múltiples flujos (distintos nonces base pueden producir espacios de nonce superpuestos). Esto no aplica a Concryptor porque cada cifrado genera una sal aleatoria fresca de 128 bits, produciendo una clave Argon2id única por archivo. La unicidad del nonce solo importa bajo la misma clave, y la probabilidad de reutilización de clave es ~2^-128 por par de archivos.AAD = cabecera_completa_alineada (4096) || chunk_index (8 LE) || is_final (1) (4105 bytes en total). El sector completo de cabecera de 4 KiB (campos principales, parámetros KDF y relleno reservado) está vinculado en la etiqueta de autenticación de cada fragmento. Modificar cualquier byte de la cabecera (tipo de cifrado, tamaño de fragmento, tamaño original, sal, nonce, parámetros KDF o relleno reservado) invalida todos los fragmentos. Esto previene ataques de truncamiento donde un adversario edita original_size y elimina fragmentos finales, y también previene el contrabando de datos en la región de relleno reservado. Los archivos heredados v3 se descifran con AAD de 52 bytes para compatibilidad hacia atrás; no es posible una degradación de v4 a v3 porque el byte de versión en sí está dentro del AAD autenticado.0x01 para el fragmento final y 0x00 para todos los demás. Esto previene dos ataques:
# Ejecutar el conjunto completo de pruebas (67 pruebas)
cargo test
# Ejecutar benchmarks (informes HTML en target/criterion/)
cargo bench
# Filtrar benchmarks
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
El conjunto de pruebas cubre:
original_size modificado + fragmentos eliminados)chunk_size modificado)# Desde crates.io (recomendado)
cargo install concryptor
# Desde el código fuente
git clone https://github.com/FrogSnot/Concryptor
cd Concryptor
cargo build --release
# El binario está en target/release/concryptor
Este proyecto está licenciado bajo la GNU Affero General Public License v3.0.
| Tamaño de archivo | Cifrado AES-256-GCM | Cifrado ChaCha20 | Descifrado AES-256-GCM | Descifrado ChaCha20 |
|---|
| 64 KiB | 244 MiB/s | 233 MiB/s | 233 MiB/s | 234 MiB/s |
| 1 MiB | 1.08 GiB/s | 882 MiB/s | 1010 MiB/s | 876 MiB/s |
| 16 MiB | 1.10 GiB/s | 923 MiB/s | 1.06 GiB/s | 988 MiB/s |
| 64 MiB | 984 MiB/s | 935 MiB/s | 988 MiB/s | 973 MiB/s |
| 256 MiB | 1.00 GiB/s | 1015 MiB/s | 1.01 GiB/s | 1.02 GiB/s |
| Tamaño de fragmento | Rendimiento |
|---|
| 64 KiB | 1.01 GiB/s |
| 256 KiB | 1.05 GiB/s |
| 1 MiB | 1.07 GiB/s |
| 4 MiB | 988 MiB/s |
| 8 MiB | 988 MiB/s |
| 16 MiB | 1.00 GiB/s |
is_final = 0x000x01is_final = 0x01 sin la clave.rand::rng()) para cada cifrado. Dos cifrados del mismo archivo con la misma contraseña producen texto cifrado completamente diferente. La reutilización de nonce (que es catastrófica para AES-GCM) se evita por construcción.--memory), 3 iteraciones de tiempo, paralelismo de 4. El valor predeterminado de 256 MiB es 4 veces el mínimo de OWASP y es costoso para atacantes con GPU/FPGA/ASIC. Los parámetros KDF se almacenan en la cabecera del archivo (bytes 52-63), haciendo que los archivos sean autodescriptivos: el descifrado siempre utiliza los parámetros correctos independientemente de los valores predeterminados actuales. Si los bytes 52-63 son todos cero (archivos heredados anteriores a los parámetros KDF), se aplican los valores predeterminados antiguos de 64 MiB / 3 / 4.| Crate | Propósito |
|---|
ring | AES-256-GCM y ChaCha20-Poly1305 AEAD optimizados en ensamblador |
io-uring | Interfaz io_uring de Linux para E/S asíncrona de lectura/escritura |
libc | Indicador O_DIRECT y pread/pwrite alineado para E/S de cabecera |
argon2 | Derivación de clave Argon2id |
rayon | Procesamiento de fragmentos en paralelo de datos |
clap | Análisis de argumentos de CLI |
indicatif | Barra de progreso en terminal |
rand | Generación de números aleatorios criptográficos |
zeroize | Borrado seguro de memoria |
anyhow | Manejo de errores |
rpassword | Entrada oculta de contraseña |
tar | Archivado y extracción de directorios |