Alcance
Opcional, detrás de un feature de compilación, y fijado en la creación del fichero: activarlo más tarde significaría reescribir cada página.
Algoritmo
ChaCha20-Poly1305 por defecto, AES-256-GCM seleccionable. El valor por defecto sigue la división entre los entornos de destino: con aceleración por hardware, AES-GCM alcanza 1-2 GB/s y cifrar una página de 4 KB cuesta 2-4 μs; sin ella — procesadores IoT sin extensiones criptográficas — el AES por software cae a 50-100 MB/s, y esa misma página cuesta 40-80 μs. ChaCha20 sin aceleración mantiene 200-400 MB/s, es decir, 10-20 μs.
Ambos usan las implementaciones de RustCrypto — Rust puro, compatibles con no_std — que añaden 60-100 KB al binario, y se compilan solo cuando el feature está activo. inro no implementa sus propias primitivas criptográficas: la dependencia que se ahorra no compensa el riesgo.
Esta es la única excepción a la política por defecto de cero dependencias del proyecto, y lleva una advertencia que merece decirse sin rodeos: forbid(unsafe_code) no alcanza a las dependencias, y estos crates usan unsafe en la detección de características de la CPU y en sus backends SIMD. La fase 1 tiene que evaluar si el backend portable de ChaCha20 puede prescindir de ese unsafe, y a qué coste de rendimiento — medido, no asumido.
Disposición de la página cifrada
0 8 contador del nonce (en claro: necesario para re-derivar el nonce)
8 16 etiqueta de autenticación
24 .. texto cifrado de la página lógica completaEl nonce se deriva, no se almacena: nonce = page_number (u32) || counter (u64), exactamente 12 bytes. El contador es un contador de escritura monótono y persistido — no el txn_id de la transacción — reservado por adelantado para cada escritura de página y registrado en las dos páginas meta. La etiqueta sustituye al CRC32, que pasa a ser redundante — una página corrupta o manipulada falla la autenticación en su lugar.
Sobrecoste neto: 20 bytes por página — 0,5% con páginas de 4 KB, 3,9% con páginas de 512 B. Un nodo de 31 MB con páginas de 4 KB paga 155 KB.
El invariante del nonce
page || counter es único mientras el contador no se reutilice para la misma página — una garantía que impone quien reparte los contadores, no algo que el diseño copy-on-write proporcione por sí mismo.
Una versión anterior de este diseño derivaba el nonce a partir de page || txn_id, razonando que el copy-on-write ya garantiza la unicidad: una página sucia se modifica en RAM y se vuelca exactamente una vez, en el commit. Eso vale para una transacción que escribe cada página como mucho una vez — pero txn_id es el mismo para todas las páginas de una transacción, y una página puede escribirse más de una vez dentro de una misma transacción (liberar una página y reasignarla antes del commit reutiliza su número de página), lo que reutilizaría ese txn_id y, por tanto, el nonce. En su lugar, inro reserva un contador de escritura dedicado por cada escritura de página, de modo que ese caso no puede darse: el par (page, counter) nunca se repite, sin importar cuántas veces se reescriba la misma página dentro de una transacción.
Si el contador se reutilizara alguna vez — un fallo en cómo se reserva, o un volcado intermedio que no lo avanza — eso rompe la confidencialidad de ambas páginas tanto con ChaCha20-Poly1305 como con AES-GCM, y puede permitir falsificar la etiqueta. Se mantiene como un invariante documentado, con un test automatizado que falla si alguna vez se viola.
Gestión de claves
El motor recibe 32 bytes, ya derivados. Derivar a partir de una contraseña queda fuera de alcance: significaría meter Argon2 — pesado en RAM, inadecuado para IoT — o PBKDF2, y esas son decisiones que no le corresponden a un motor de almacenamiento. En el edge la clave llega desde el plano de control durante el aprovisionamiento; en IoT, desde el arranque seguro o la configuración.
La rotación funciona por envoltura: la clave que recibe el motor es la clave master, y envuelve una clave data por fichero, almacenada cifrada en las dos páginas meta. Rotar la master reescribe esos bytes en las dos metas, no el fichero entero. Rotar la clave data sí implica reescribir el fichero, y solo hace falta si se sospecha que se ha filtrado.
Lo que no protege
La cabecera de 64 bytes se mantiene en claro, porque el tamaño de página tiene que conocerse antes de poder descifrar nada. Lo que se filtra: que el fichero es una base de datos inro, su versión de formato, su geometría, su tamaño total, el número de transacciones realizadas, y el patrón de acceso. Las claves y los valores dentro de las páginas están cifrados, así que ni su longitud ni su contenido se filtran.
El cifrado protege contra el robo del disco, la copia de una copia de seguridad, y el acceso del proveedor de hosting. No protege contra un proceso comprometido, porque la clave vive en su memoria.