Actualizado

Cifrado de conocimiento cero: cómo Vaulted mantiene tus secretos privados

Por

El cifrado de conocimiento cero significa que el servidor nunca ve tus datos en texto plano ni tus claves de cifrado. Vaulted lo implementa cifrando cada secreto en tu navegador con AES-256-GCM antes de que ningún dato llegue al servidor. El servidor almacena únicamente texto cifrado: nunca tiene acceso a la clave de descifrado, que vive exclusivamente en el fragmento de la URL.

Este artículo recorre la implementación criptográfica exacta: cómo se generan las claves, cómo se cifran los datos, cómo llega la clave al destinatario y qué ocurre cuando añades una frase de contraseña. Cada detalle aquí refleja el código real que se ejecuta en tu navegador.

Para la visión general de por qué importa el cifrado del lado del cliente, consulta nuestro artículo anterior sobre el cifrado del lado del cliente. Este artículo profundiza en las primitivas criptográficas.

Generación de claves

Cada vez que creas un secreto, Vaulted genera una clave de cifrado AES-256-GCM nueva mediante la Web Crypto API:

crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
  'encrypt',
  'decrypt',
]);

Algunas cosas que conviene tener en cuenta:

  • AES-GCM (Galois/Counter Mode) proporciona tanto confidencialidad como integridad: detecta manipulaciones, no solo la lectura no autorizada.
  • La longitud de clave de 256 bits es la opción más robusta disponible para AES y la usan gobiernos e instituciones financieras de todo el mundo.
  • Extractable: true: la clave puede exportarse como bytes en bruto para poder colocarla en el fragmento de la URL destinado al destinatario.

La Web Crypto API delega en el motor criptográfico nativo del navegador (p. ej., BoringSSL en Chromium, NSS en Firefox). La clave se genera de forma segura en memoria y nunca toca el disco ni la red.

Cifrado

Con la clave generada, Vaulted cifra tu texto plano:

const iv = crypto.getRandomValues(new Uint8Array(12));

crypto.subtle.encrypt(
  { name: 'AES-GCM', iv },
  key,
  new TextEncoder().encode(plaintext),
);

El vector de inicialización (IV): AES-GCM requiere un IV aleatorio de 12 bytes (96 bits) para cada operación de cifrado. El IV no necesita ser secreto —se almacena junto al texto cifrado—, pero nunca debe reutilizarse con la misma clave. Como Vaulted genera una clave nueva para cada secreto, la reutilización del IV es imposible por diseño.

Codificación: Tanto el texto cifrado como el IV se codifican como cadenas base64url (base64 estándar con + reemplazado por -, / reemplazado por _ y los = finales eliminados). Esto hace que sean seguros para incluirlos en URLs y cargas útiles JSON.

El texto cifrado y el IV se envían al servidor para su almacenamiento. El texto plano nunca sale del navegador.

Entrega de la clave en el fragmento de la URL

Tras el cifrado, la clave se exporta y se coloca en el fragmento de la URL: la parte después de #:

const rawKey = await crypto.subtle.exportKey('raw', key);
// rawKey → base64url-encoded string

El enlace para compartir resultante tiene este aspecto:

https://vaulted.fyi/s/abc123#dGhpcyBpcyBhIGtleQ
                              ^^^^^^^^^^^^^^^^^^^^^^^^
                              encryption key (base64url)

El separador # es fundamental. Según el RFC 3986, el identificador de fragmento lo procesa íntegramente el cliente. Los navegadores nunca incluyen el fragmento en las solicitudes HTTP: ni en la URL, ni en la cabecera Referer, ni en ningún lugar que el servidor pueda ver.

Cuando el destinatario abre el enlace:

  1. El navegador solicita /s/abc123 al servidor (sin fragmento)
  2. El servidor devuelve el texto cifrado y el IV
  3. JavaScript lee el fragmento desde window.location.hash
  4. La clave se importa de vuelta a la Web Crypto API
  5. El texto cifrado se descifra localmente

El servidor facilita el almacenamiento y la recuperación del bloque cifrado. Nunca participa en las operaciones criptográficas.

Protección opcional con frase de contraseña

Para los secretos que necesitan protección adicional, Vaulted admite el encapsulado de la clave basado en una frase de contraseña. Esto añade una segunda capa: aunque alguien intercepte el enlace, no podrá descifrar el secreto sin la frase de contraseña.

Derivación de claves con PBKDF2

Cuando estableces una frase de contraseña, Vaulted deriva una clave de encapsulado mediante PBKDF2:

crypto.subtle.deriveKey(
  {
    name: 'PBKDF2',
    salt, // 16 bytes, randomly generated
    iterations: 600_000,
    hash: 'SHA-256',
  },
  keyMaterial, // the passphrase, imported as raw key material
  { name: 'AES-KW', length: 256 },
  false,
  ['wrapKey', 'unwrapKey'],
);
  • 600.000 iteraciones encarecen computacionalmente los ataques de fuerza bruta.
  • Un salt aleatorio de 16 bytes garantiza que frases de contraseña idénticas produzcan claves derivadas distintas en distintos secretos.
  • SHA-256 es la función hash utilizada en cada iteración de PBKDF2.
  • La clave derivada es una clave AES-KW (AES Key Wrap, RFC 3394), no una clave AES-GCM. El encapsulado de la clave es una operación distinta del cifrado.

Encapsulado de la clave de cifrado

La clave AES-KW derivada encapsula la clave de cifrado original:

crypto.subtle.wrapKey('raw', encryptionKey, wrappingKey, 'AES-KW');

El fragmento de la URL contiene ahora tanto la clave encapsulada como el salt, separados por un punto:

#wrappedKeyBase64url.saltBase64url

Desencapsulado al descifrar

Cuando el destinatario introduce la frase de contraseña, Vaulted invierte el proceso:

  1. Divide el fragmento por . para obtener la clave encapsulada y el salt
  2. Deriva la misma clave de encapsulado AES-KW usando PBKDF2 con la frase de contraseña y el salt
  3. Desencapsula la clave de cifrado con crypto.subtle.unwrapKey
  4. Descifra el texto cifrado con la clave AES-GCM desencapsulada

Si la frase de contraseña es incorrecta, unwrapKey lanza un error: no hay descifrado parcial ni filtración de información.

Flujo de descifrado

Sin frase de contraseña, el descifrado es directo:

  1. Importa la clave desde el fragmento de la URL (crypto.subtle.importKey)
  2. Descifra el texto cifrado con el IV (crypto.subtle.decrypt)
  3. Decodifica el texto plano desde los bytes UTF-8

Con una frase de contraseña:

  1. Divide el fragmento para extraer la clave encapsulada y el salt
  2. Deriva la clave de encapsulado a partir de la frase de contraseña y el salt (PBKDF2)
  3. Desencapsula la clave de cifrado (AES-KW)
  4. Descifra el texto cifrado (AES-GCM)
  5. Decodifica el texto plano

Todo esto ocurre en el navegador. El servidor devuelve el bloque cifrado y los metadatos (recuento de vistas, caducidad): nada más.

Modelo de amenazas

Ninguna herramienta de seguridad protege contra todo. Esto es lo que la arquitectura de Vaulted está diseñada para manejar y lo que queda fuera de su alcance.

Contra qué protege Vaulted

  • Compromiso del servidor: el servidor almacena únicamente texto cifrado. Sin la clave (que nunca se envía al servidor), los datos son ilegibles. Un volcado completo de la base de datos arroja solo bloques cifrados.
  • Filtración de la base de datos: igual que lo anterior. El texto cifrado sin claves es computacionalmente inútil con AES-256-GCM.
  • Ataque de intermediario del lado del servidor: la clave de cifrado vive en el fragmento de la URL, que nunca se transmite en las solicitudes HTTP. TLS protege el texto cifrado en tránsito.
  • Operadores del servicio leyendo secretos: conocimiento cero significa exactamente eso. No podemos leer tus secretos ni aunque se nos obligara a hacerlo, porque no tenemos las claves.

Contra qué NO protege Vaulted

  • Navegador o dispositivo comprometido: si el dispositivo del destinatario tiene malware o un keylogger, el texto plano descifrado puede capturarse tras el descifrado.
  • Extensiones de navegador maliciosas: las extensiones con acceso a la página pueden leer el DOM tras el descifrado, incluido el secreto revelado.
  • Interceptación del enlace: la URL contiene la clave de cifrado en el fragmento. Cualquiera que obtenga la URL completa (incluido el fragmento) puede descifrar el secreto. Comparte los enlaces a través de canales seguros.
  • Espionaje por encima del hombro: una vez que el secreto se muestra en pantalla, cualquiera que esté mirando puede verlo.
  • Comportamiento del destinatario: tras el descifrado, el destinatario puede hacer una captura de pantalla, copiar o compartir el texto plano. Vaulted no puede evitarlo.

La función de frase de contraseña mitiga la interceptación del enlace: incluso con la URL completa, el atacante todavía necesita la frase de contraseña. Comparte la frase de contraseña a través de un canal distinto (una llamada telefónica, un mensaje aparte) para una defensa en profundidad.

Resumen

El cifrado de conocimiento cero de Vaulted significa:

  1. Se genera una clave AES-256-GCM nueva para cada secreto
  2. El cifrado ocurre íntegramente en tu navegador
  3. El servidor almacena únicamente texto cifrado
  4. La clave de descifrado viaja solo en el fragmento de la URL, que los navegadores nunca envían a los servidores
  5. El encapsulado opcional con frase de contraseña añade una segunda capa mediante PBKDF2 y AES-KW

El resultado: aunque todos los servidores, bases de datos y rutas de red se vieran comprometidos simultáneamente, tus secretos siguen cifrados. Las claves solo existen en los enlaces que compartes.


¿Todo listo para compartir un secreto de forma segura? Crea un secreto en Vaulted: solo lleva 10 segundos.


Relacionado