Libros blancos técnicos

Documentación técnica detallada sobre la arquitectura criptográfica, el modelo de seguridad y la ingeniería de privacidad de PDF Pro. Escrita para usuarios preocupados por la seguridad, auditores y revisores.

Publicado: 16 de abril de 2025 Última actualización: 16 de abril de 2026 Versión: 2.0

Índice de libros blancos

  1. Transferencia de archivos con cifrado de extremo a extremo
  2. Firma criptográfica de documentos con prioridad a la privacidad

Transferencia de archivos con cifrado de extremo a extremo

Libro blanco WP-001 de PDF Pro — Arquitectura y análisis de seguridad

1.1 Resumen

Este libro blanco describe la arquitectura del sistema de Transferencia Segura de PDF Pro, un mecanismo de transferencia de archivos de conocimiento cero en el que los archivos se cifran en el lado del cliente utilizando AES-256-GCM antes de subirse. El servidor solo almacena texto cifrado opaco y no tiene capacidad para descifrar, inspeccionar o leer el contenido de los archivos en ningún momento. El sistema admite dos modos de clave: una clave aleatoria autogenerada (transportada a través del fragmento de la URL, que nunca se envía al servidor) o una clave derivada de una contraseña mediante PBKDF2. En ambos modos, la clave de cifrado existe únicamente en los navegadores del remitente y el destinatario. Este documento detalla las primitivas criptográficas, el flujo de datos, el modelo de amenazas y las limitaciones honestas del sistema.

1.2 Visión general de la arquitectura

El sistema de Transferencia Segura sigue una separación estricta cliente-servidor en la que todas las operaciones criptográficas ocurren exclusivamente en el navegador:

El servidor está diseñado intencionalmente para ser una "tubería tonta" para los datos cifrados. No tiene conocimiento de la clave de cifrado, la contraseña ni el contenido de los archivos. Esto se aplica de forma arquitectónica, no solo por política.

Principio de diseño: el servidor debería poder verse comprometido sin comprometer los datos de los usuarios. Incluso con acceso total a la base de datos y ejecución de código del lado del servidor, un atacante no puede descifrar los archivos transferidos.

1.3 Cifrado: AES-256-GCM mediante la Web Crypto API

Todo el cifrado de archivos usa el algoritmo de cifrado autenticado AES-256-GCM, accedido a través de la Web Crypto API nativa del navegador. Esto proporciona tanto confidencialidad (los datos no se pueden leer) como integridad (los datos no se pueden modificar sin que se detecte).

1.3.1 Parámetros del algoritmo

ParámetroValorJustificación
AlgoritmoAES-256-GCMAprobado por NIST, cifrado autenticado con datos asociados (AEAD)
Tamaño de clave256 bitsLongitud máxima de clave AES; proporciona 128 bits de seguridad frente a fuerza bruta
Tamaño del IV96 bits (12 bytes)Longitud de IV recomendada por NIST para el modo GCM
Tamaño de la etiqueta128 bits (16 bytes)Etiqueta de autenticación de longitud completa para máxima protección de integridad
Generación del IVcrypto.getRandomValues()Generador de números aleatorios criptográficamente seguro

1.3.2 Implementación del cifrado

// Flujo de cifrado simplificado (Web Crypto API)

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

// Derivar la clave desde la contraseña (ver Sección 1.4)
const key = await deriveKey(passphrase, salt);

// Cifrar el archivo
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv: iv },
  key,
  fileArrayBuffer
);

// Paquete: [salt (16B)] + [iv (12B)] + [ciphertext + tag]
const blob = concatenate(salt, iv, ciphertext);

El blob cifrado final es la concatenación de tres componentes: el salt de 16 bytes (usado para la derivación de clave), el IV de 12 bytes (usado para AES-GCM) y el texto cifrado con la etiqueta de autenticación GCM de 16 bytes adjuntada. Este blob es lo que el servidor recibe y almacena.

1.4 Generación y transporte de claves

La Transferencia Segura admite dos modos de clave mutuamente excluyentes. El modo lo elige el remitente en el momento de crear la transferencia.

1.4.1 Modo A — Clave autogenerada (predeterminado)

En el modo predeterminado no interviene ninguna contraseña. El navegador genera directamente una clave AES de 256 bits criptográficamente aleatoria:

1.4.2 Modo B — Transferencia protegida con contraseña

Cuando el remitente opta por establecer una contraseña, la clave se deriva de esa contraseña mediante PBKDF2:

1.4.3 Parámetros de PBKDF2 (Modo B)

ParámetroValorJustificación
AlgoritmoPBKDF2Recomendado por NIST SP 800-132; ampliamente auditado
Función hashSHA-256Hash criptográfico estándar; salida de 256 bits
Iteraciones600.000Cumple con la recomendación de OWASP 2023 para PBKDF2-SHA256
Tamaño del salt128 bits (16 bytes)Único por transferencia; previene ataques con tablas arcoíris
Longitud de la clave de salida256 bitsCoincide con el requisito de clave AES-256

1.4.4 Implementación de la derivación de claves (Modo B)

async function deriveKey(passphrase, salt) {
  // Importar la contraseña como material de clave en bruto
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(passphrase),
    { name: "PBKDF2" },
    false,
    ["deriveKey"]
  );

  // Derivar la clave AES-256-GCM
  return crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt: salt,
      iterations: 600000,
      hash: "SHA-256"
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
}

Nota de seguridad: en el Modo B la seguridad depende de la entropía de la contraseña. Recomendamos contraseñas de 12 caracteres o más que combinen mayúsculas, minúsculas, dígitos y símbolos.

1.5 Flujo de datos

El ciclo de vida completo de una transferencia segura sigue este recorrido:

1.5.1 Envío (subida) — Modo A (clave autogenerada)

  1. El remitente selecciona un archivo en el navegador.
  2. El navegador genera una clave AES-GCM aleatoria de 256 bits y un IV aleatorio de 12 bytes mediante crypto.getRandomValues().
  3. El navegador cifra el archivo usando AES-256-GCM, produciendo texto cifrado + etiqueta de autenticación.
  4. El navegador concatena [IV | ciphertext+tag] en un único blob.
  5. El navegador sube el blob cifrado al servidor mediante HTTPS.
  6. El servidor almacena el blob en Supabase Storage y crea un registro de metadatos (ID de transferencia, expiración, límite de descargas, nombre del archivo, tamaño del archivo).
  7. El servidor devuelve una URL de transferencia. El navegador añade la clave exportada al fragmento de la URL (#).
  8. El remitente comparte la URL completa (con el fragmento) con el destinatario.

1.5.2 Envío (subida) — Modo B (contraseña)

  1. El remitente selecciona un archivo e introduce una contraseña en el navegador.
  2. El navegador genera un salt aleatorio de 16 bytes y un IV de 12 bytes mediante crypto.getRandomValues().
  3. El navegador deriva una clave AES-256 a partir de la contraseña mediante PBKDF2 (600K iteraciones).
  4. El navegador cifra el archivo usando AES-256-GCM, produciendo texto cifrado + etiqueta de autenticación.
  5. El navegador concatena [salt | IV | ciphertext+tag] en un único blob.
  6. El navegador sube el blob cifrado al servidor mediante HTTPS.
  7. El servidor almacena el blob en Supabase Storage y crea un registro de metadatos (ID de transferencia, expiración, límite de descargas, nombre del archivo, tamaño del archivo).
  8. El servidor devuelve una URL de transferencia que contiene el ID de la transferencia.
  9. El remitente comparte la URL de la transferencia y la contraseña con el destinatario a través de canales separados.

1.5.3 Recepción (descarga) — Modo A

  1. El destinatario abre la URL completa de la transferencia (con la clave en el fragmento).
  2. El navegador extrae la clave AES del fragmento de la URL (nunca se envía al servidor).
  3. El navegador descarga el blob cifrado desde el servidor mediante HTTPS.
  4. El navegador extrae el IV (primeros 12 bytes) del blob.
  5. El navegador descifra el texto cifrado usando AES-256-GCM con la clave y el IV extraídos.
  6. Si el descifrado tiene éxito (la etiqueta GCM es válida), se presenta al usuario el archivo en texto claro.
  7. El servidor actualiza el contador de descargas y, si la opción "borrar al leer" está activada, elimina el blob.

1.5.4 Recepción (descarga) — Modo B

  1. El destinatario abre la URL de la transferencia e introduce la contraseña en el navegador.
  2. El navegador descarga el blob cifrado desde el servidor mediante HTTPS.
  3. El navegador extrae el salt (primeros 16 bytes) y el IV (siguientes 12 bytes) del blob.
  4. El navegador deriva la clave AES-256 a partir de la contraseña + salt mediante PBKDF2 (600K iteraciones).
  5. El navegador descifra el texto cifrado usando AES-256-GCM con la clave derivada y el IV.
  6. Si el descifrado tiene éxito (la etiqueta GCM es válida), se presenta al usuario el archivo en texto claro.
  7. Si el descifrado falla (contraseña incorrecta = clave incorrecta = etiqueta GCM inválida), se muestra un error.
  8. El servidor actualiza el contador de descargas y, si la opción "borrar al leer" está activada, elimina el blob.

1.6 Lo que el servidor almacena vs. lo que nunca ve

Elemento de datos¿El servidor tiene acceso?Detalles
Blob cifrado (salt + IV + ciphertext + tag)Datos binarios opacos; el servidor no puede interpretar el contenido
ID de la transferenciaLos identificadores de transferencia son valores UUID v4 generados por gen_random_uuid() de PostgreSQL, que proporcionan 122 bits de aleatoriedad criptográfica desde el CSPRNG del servidor
Nombre original del archivoAlmacenado en los metadatos para mostrarlo al destinatario
Tamaño original del archivoAlmacenado en los metadatos con fines de visualización
Marca de tiempo de expiraciónSe utiliza para aplicar el borrado automático
Recuento/límite de descargasSe utiliza para aplicar el borrado al leer y el límite de descargas
ID del usuario remitente (si está autenticado)Vincula la transferencia a la cuenta para su gestión
ContraseñaNo — nuncaNunca se envía al servidor; nunca sale del navegador
Clave de cifrado derivadaNo — nuncaExiste únicamente en la memoria del navegador durante el cifrado/descifrado
Contenido del archivo en texto claroNo — nuncaSolo el texto cifrado llega al servidor
Número de iteraciones de PBKDF2NoCodificado en el cliente; no se transmite al servidor

1.7 Expiración automática y borrado al leer

Todas las transferencias seguras son efímeras por diseño. No existe opción de almacenamiento permanente.

1.7.1 Opciones de expiración

OpciónComportamientoAplicación
Borrar al leerEl blob cifrado se elimina inmediatamente después de la primera descarga con éxitoEn el servidor: el blob se elimina del almacenamiento cuando finaliza el flujo de descarga
1 horaSe elimina automáticamente tras 1 hora, independientemente del estado de descargaEn el servidor: tarea de limpieza programada + comprobación de expiración al acceder
24 horasSe elimina automáticamente tras 24 horasIgual que arriba
7 díasRetención máxima; se elimina automáticamente tras 7 díasIgual que arriba

1.7.2 Garantía de eliminación

Cuando una transferencia expira o se borra al leer, el blob cifrado se elimina permanentemente de Supabase Storage. El registro de metadatos se conserva durante 30 días en estado de borrado lógico (para investigación de abusos) antes de purgarse permanentemente. El registro de metadatos no contiene la clave de cifrado, la contraseña ni ninguna información que pueda usarse para reconstruir el archivo.

1.8 Modelo de amenazas y mitigaciones

AmenazaVector de ataqueMitigación
Compromiso del servidor El atacante obtiene acceso total a la base de datos y al almacenamiento Todos los datos almacenados son texto cifrado AES-256-GCM. El atacante solo obtiene blobs opacos. Sin la contraseña, forzar AES de 256 bits es computacionalmente inviable.
Interceptación de red (MITM) El atacante intercepta los datos en tránsito Todo el tráfico usa TLS 1.3. Incluso si TLS se viese comprometido, el atacante solo obtendría blobs cifrados (igual que en un compromiso del servidor).
Contraseña débil El atacante fuerza por fuerza bruta una contraseña corta o común PBKDF2 con 600K iteraciones hace que cada intento sea costoso computacionalmente. Una contraseña de 4 caracteres aún requeriría una cantidad significativa de cómputo. La interfaz aplica una longitud mínima de contraseña y ofrece retroalimentación de fortaleza.
Interceptación de la contraseña El atacante intercepta la contraseña compartida entre remitente y destinatario La contraseña se comparte fuera de banda (no a través de nuestro sistema). Recomendamos compartirla por un canal distinto al de la URL de la transferencia. Esto es responsabilidad del usuario.
Manipulación del código del cliente El atacante modifica el JavaScript servido al usuario Todos los recursos se sirven por HTTPS con la CDN de Vercel. Los hashes Subresource Integrity (SRI) protegen frente a manipulación a nivel de CDN. Los usuarios pueden verificar el código fuente en las DevTools del navegador.
Extracción de memoria El atacante extrae la clave de cifrado de la memoria del navegador Las claves de la Web Crypto API se marcan como no extraíbles siempre que es posible. Las claves derivadas existen en memoria solo durante la operación de cifrado/descifrado. El aislamiento de memoria del navegador proporciona protección a nivel del sistema operativo.
Ataque de repetición El atacante reproduce un blob cifrado capturado Cada transferencia tiene un ID único y está protegida por límites de descarga y expiración. Las transferencias con borrado al leer se eliminan tras el primer acceso.

1.8.1 Supuestos

1.8.2 Fuera del alcance

1.9 Limitaciones y divulgación honesta

Limitaciones honestas: ningún sistema de seguridad es perfecto. Creemos en la divulgación transparente de las limitaciones conocidas.


Firma criptográfica de documentos con prioridad a la privacidad

Libro blanco WP-002 de PDF Pro — Arquitectura y análisis de seguridad

2.1 Resumen

Este libro blanco describe la arquitectura del sistema de Firma con Privacidad de PDF Pro, un mecanismo criptográfico de firma de documentos en el que el PDF nunca sale del navegador del firmante. El sistema utiliza ECDSA con la curva P-256 y hashing SHA-256 para producir firmas digitales separadas. Al servidor solo se transmiten el hash del documento, la firma criptográfica y la clave pública. Las claves privadas se generan, se cifran y se almacenan exclusivamente en el navegador del usuario mediante IndexedDB, protegidas por derivación de clave PBKDF2 (600.000 iteraciones) y cifrado AES-256-GCM. Este documento detalla la arquitectura completa de firma y verificación, el modelo de gestión de claves, el esquema del payload firmado, el diseño del registro de auditoría, el modelo de amenazas y las limitaciones honestas.

2.2 Visión general de la arquitectura

El sistema de Firma con Privacidad se diseña en torno a una restricción fundamental: el PDF nunca debe transmitirse al servidor. Esto se aplica arquitectónicamente mediante un modelo de firma separada.

Garantía central: el servidor nunca ve, recibe ni procesa el PDF. Los únicos datos relacionados con el documento que el servidor recibe son un hash SHA-256 — un valor de tamaño fijo de 256 bits a partir del cual no se puede reconstruir el documento original.

2.3 Algoritmo de firma: ECDSA P-256 / SHA-256

2.3.1 Parámetros del algoritmo

ParámetroValorJustificación
Algoritmo de firmaECDSA (Elliptic Curve Digital Signature Algorithm)NIST FIPS 186-4; firmas compactas; alta seguridad por bit de clave
CurvaP-256 (secp256r1 / prime256v1)Aprobada por NIST; nivel de seguridad de 128 bits; amplio soporte en la Web Crypto API
Función hashSHA-256NIST FIPS 180-4; digest de 256 bits; resistente a colisiones
Tamaño de claveClave privada de 256 bits, clave pública de 512 bits (sin comprimir)Estándar para P-256; equivalente a ~3072 bits de RSA
Tamaño de la firma64 bytes (r: 32 bytes, s: 32 bytes)Compacto; adecuado para almacenamiento y transmisión
Formato de firmaIEEE P1363 (r || s en bruto)Salida nativa de la Web Crypto API; codificada en base64url para su almacenamiento

Codificación de la firma: ECDSA con P-256 de la Web Crypto API produce una firma en bruto de 64 bytes compuesta por dos enteros de 32 bytes (r || s) en formato big-endian de ancho fijo. Esta salida en bruto se codifica en base64url para su almacenamiento. No está codificada en DER — es el formato IEEE P1363 que produce nativamente la Web Crypto API.

2.3.2 Flujo de firma

// 1. Calcular el hash del PDF (en el cliente)
const fileBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", fileBuffer);
const hashHex = Array.from(new Uint8Array(hashBuffer))
  .map(b => b.toString(16).padStart(2, '0')).join('');

// 2. Firmar el hash con la clave privada (en el cliente)
const signature = await crypto.subtle.sign(
  { name: "ECDSA", hash: "SHA-256" },
  privateKey,   // CryptoKey desde IndexedDB (descifrada)
  hashBuffer
);

// 3. Enviar al servidor: hash + firma + clave pública (NO el PDF)
await submitSignature({
  documentHash: hashHex,
  signature: base64Encode(signature),
  publicKey: exportedPublicKeyJWK
});

2.4 Gestión de claves: efímeras vs. persistentes

Tipo de claveContextoCiclo de vidaAlmacenamiento
Efímera Usuarios invitados (no autenticados) Se genera por sesión; se destruye al cerrar la pestaña Solo en memoria (objeto CryptoKey); nunca se persiste
Persistente Usuarios autenticados (con sesión iniciada) Se genera una vez; persiste entre sesiones; revocable IndexedDB (cifrada con PBKDF2 + AES-256-GCM)

2.4.1 Generación de claves

// Generar par de claves ECDSA P-256 (Web Crypto API)
const keyPair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,   // extraíble (necesario para cifrado + almacenamiento)
  ["sign", "verify"]
);

// Exportar la clave pública como JWK para registrarla en el servidor
const publicKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.publicKey);

// Exportar la clave privada como JWK para su almacenamiento cifrado
const privateKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.privateKey);

Formato de la clave pública: las claves públicas se exportan y almacenan en formato JWK (JSON Web Key). La huella digital de la clave se calcula como SHA-256 del JWK canónico que contiene solo los campos públicos {crv, kty, x, y} con las claves ordenadas alfabéticamente.

2.5 Protección de la clave privada: PBKDF2 + AES-GCM → IndexedDB

Las claves privadas persistentes nunca se almacenan en texto claro. Antes de escribirse en IndexedDB, la clave privada (exportada como JSON JWK) se cifra siguiendo el mismo patrón que la Transferencia Segura:

2.5.1 Parámetros de protección

ParámetroValor
Derivación de clavePBKDF2-SHA256, 600.000 iteraciones
SaltAleatorio de 16 bytes (por clave)
CifradoAES-256-GCM
IVAleatorio de 12 bytes (por cifrado)
EntradaJWK de la clave privada (cadena JSON codificada en UTF-8)
Salida almacenada en IndexedDB{ salt, iv, ciphertext, publicKeyJWK, keyId, createdAt }

2.5.2 Esquema de almacenamiento

// Estructura del registro de IndexedDB para una clave de firma cifrada
{
  "keyId":       "uuid-v4-unique-identifier",
  "publicKey":   { /* formato JWK, sin cifrar */ },
  "encryptedPrivateKey": {
    "salt":       "base64-encoded-16-bytes",
    "iv":         "base64-encoded-12-bytes",
    "ciphertext": "base64-encoded-aes-gcm-ciphertext"
  },
  "algorithm":   "ECDSA",
  "curve":       "P-256",
  "createdAt":   "2026-04-16T00:00:00.000Z",
  "userId":      "supabase-auth-user-id"
}

Recuperación de claves: si el usuario olvida su contraseña de firma, la clave privada no se puede recuperar. No tenemos la contraseña, la clave derivada ni ningún mecanismo para saltarnos la protección PBKDF2 + AES-GCM. Los usuarios deben exportar copias de seguridad de sus claves.

2.6 Modelo de firma separada

PDF Pro utiliza un modelo de firma separada, lo que significa que la firma se almacena por separado del documento. Este es el mecanismo crítico de privacidad: el documento nunca sale del dispositivo del firmante.

2.6.1 Qué se envía al servidor

2.6.2 Qué NO se envía al servidor

Garantía criptográfica: SHA-256 es una función hash de un solo sentido. Dado únicamente el hash e3b0c44298fc1c14..., se proporciona una sólida garantía criptográfica, bajo los supuestos de seguridad de ECDSA y SHA-256, de que el documento original no puede reconstruirse. El hash no revela nada sobre el contenido, la longitud o la estructura del documento más allá de confirmar su identidad cuando se vuelve a calcular.

2.7 Esquema del payload firmado v1.0

El siguiente esquema JSON define el registro de firma completo almacenado en el servidor:

{
  "schemaVersion":  "1.0",
  "signatureId":    "uuid-v4",
  "documentHash":   "sha256-hex-64-chars",
  "hashAlgorithm":  "SHA-256",
  "signature":      "base64-encoded-ecdsa-signature",
  "signatureAlgorithm": "ECDSA",
  "curve":          "P-256",
  "publicKey": {
    "kty": "EC",
    "crv": "P-256",
    "x":   "base64url-encoded-x-coordinate",
    "y":   "base64url-encoded-y-coordinate"
  },
  "signer": {
    "identityLevel": "authenticated | self-asserted",
    "displayName":   "string or null",
    "email":         "string or null",
    "userId":        "supabase-uid or null"
  },
  "timestamp":      "ISO-8601-UTC",
  "metadata": {
    "fileName":     "original-file-name.pdf",
    "fileSize":     123456,
    "pageCount":    12,
    "clientVersion": "2.0.0",
    "userAgent":    "browser-user-agent-string"
  },
  "auditChain": {
    "previousEventHash": "sha256-of-previous-audit-event or null",
    "eventHash":         "sha256-of-this-record"
  }
}

2.8 Flujo de verificación

La verificación de firmas es un proceso en dos fases: verificación criptográfica en el cliente seguida de comprobación cruzada en el servidor.

2.8.1 Fase 1: Verificación criptográfica en el cliente

  1. El usuario carga un PDF en el navegador.
  2. El navegador calcula el hash SHA-256 del PDF cargado.
  3. El navegador consulta al servidor cualquier registro de firma que coincida con ese hash.
  4. Para cada registro de firma devuelto, el navegador realiza la verificación ECDSA:
    const isValid = await crypto.subtle.verify(
      { name: "ECDSA", hash: "SHA-256" },
      importedPublicKey,
      signatureBuffer,
      hashBuffer
    );
  5. Si isValid === true, la firma es criptográficamente válida: el documento no se ha modificado desde la firma y la firma fue producida por el titular de la clave privada correspondiente.

2.8.2 Fase 2: Comprobación cruzada en el servidor

  1. El servidor confirma que la clave pública del registro de firma está registrada a un usuario conocido (para firmas autenticadas).
  2. El servidor confirma que el registro de firma no ha sido revocado.
  3. El servidor confirma la integridad del registro de auditoría (validación de la cadena de hashes).
  4. El servidor devuelve el nivel de identidad del firmante y el estado de registro.

2.8.3 Resultados de la verificación

ResultadoSignificado
Válida (autenticada)La firma es criptográficamente válida Y la clave pública pertenece a un usuario de PDF Pro registrado y autenticado.
Válida (autoafirmada)La firma es criptográficamente válida, pero la identidad del firmante está autoafirmada (usuario invitado o nombre no verificado).
InválidaLa verificación criptográfica ha fallado. El documento ha sido modificado desde la firma, o la firma está corrupta.
RevocadaLa firma era válida, pero ha sido revocada explícitamente por el firmante.
No se encontró firmaNo existe ningún registro de firma para este hash de documento.

2.9 Registro de auditoría: eventos encadenados por hash

Todos los eventos de firma y verificación se registran en un registro de auditoría a prueba de manipulaciones. Los eventos se encadenan por hash: cada evento incluye el hash SHA-256 del evento anterior, formando una cadena de solo-adición similar a una blockchain.

2.9.1 Tipos de eventos de auditoría

Tipo de eventoDisparadorDatos registrados
KEY_REGISTEREDEl usuario registra una nueva clave públicaJWK de la clave pública, ID de usuario, marca de tiempo
DOCUMENT_SIGNEDEl usuario firma un documentoHash del documento, firma, clave pública, identidad del firmante, marca de tiempo
SIGNATURE_VERIFIEDCualquier usuario verifica una firmaHash del documento, resultado de la verificación, info del verificador (si está autenticado), marca de tiempo
SIGNATURE_REVOKEDEl firmante revoca una firmaID de firma, motivo de revocación, marca de tiempo
KEY_REVOKEDEl usuario revoca una clave públicaID de la clave pública, motivo de revocación, marca de tiempo

2.9.2 Estructura de la cadena de hashes

// Cada evento de auditoría incluye:
{
  "eventId":            "uuid-v4",
  "eventType":          "DOCUMENT_SIGNED",
  "timestamp":          "ISO-8601-UTC",
  "data":               { /* payload específico del evento */ },
  "previousEventHash": "sha256-of-previous-event-json",
  "eventHash":          "sha256-of-this-event-json-without-eventHash"
}

// Detección de manipulación: para verificar la cadena, calcula:
// SHA-256(JSON.stringify(event without eventHash field))
// y confirma que coincide con eventHash.
// Después confirma que previousEventHash coincide con el eventHash del evento anterior.

Si se modifica algún evento de la cadena, todos los enlaces de hash posteriores se romperán, haciendo que la manipulación sea detectable de inmediato. Esto proporciona una sólida garantía de integridad del registro de auditoría.

Limitación importante: la cadena de hashes hace que la manipulación sea detectable dentro de la secuencia de eventos registrados. Sin embargo, un administrador de base de datos con acceso directo podría en teoría eliminar y reconstruir la cadena. Para garantías más fuertes se requeriría un timestamping externo o una atestación de terceros, que no están implementados en esta versión.

2.10 Niveles de identidad

NivelRequisitosPropiedades de confianzaCaso de uso
Autenticada Usuario de PDF Pro con sesión iniciada y correo verificado; par de claves persistente registrado en la cuenta Correo verificado por Supabase Auth; clave pública vinculada a una cuenta autenticada; registro de auditoría enlazado al ID de usuario Documentos empresariales, contratos, acuerdos formales
Autoafirmada Usuario invitado o autenticado con nombre introducido por sí mismo; par de claves efímero o persistente Integridad criptográfica garantizada; la identidad del firmante es autodeclarada y no se verifica de forma independiente Firma rápida, documentos personales, acuerdos informales

Nota: en la interfaz del producto, 'self_asserted' puede mostrarse como 'Identidad autoafirmada' o 'Firma de invitado'. 'authenticated' puede mostrarse como 'Cuenta autenticada'. Son etiquetas de visualización para los mismos niveles de identidad subyacentes.

Vinculación de identidad: una firma "autenticada" significa que la clave pública está registrada en una cuenta de PDF Pro con un correo electrónico verificado. NO significa que la identidad real del firmante haya sido verificada mediante documento de identidad oficial, biometría o verificación presencial. No realizamos comprobaciones Know Your Customer (KYC).

2.11 Modelo de amenazas

AmenazaVector de ataqueMitigación
Ataque de repetición El atacante copia una firma válida y la aplica a otro documento La firma está ligada al hash SHA-256 del documento específico. Un documento diferente tendrá un hash diferente y la verificación ECDSA fallará.
Falsificación El atacante crea una firma válida sin la clave privada La seguridad de ECDSA P-256 se basa en el Problema del Logaritmo Discreto Elíptico (ECDLP). Falsificar una firma sin la clave privada es computacionalmente inviable (nivel de seguridad de 128 bits).
Sustitución de claves El atacante registra su propia clave pública y afirma que una firma fue realizada por otra persona Las firmas autenticadas vinculan la clave pública a un correo verificado. El registro de auditoría anota qué clave firmó qué documento. Los eventos de registro de clave se encadenan por hash.
Sustitución del documento El atacante modifica un PDF firmado y afirma que la firma sigue siendo válida Cualquier modificación del PDF cambia su hash SHA-256. La firma existente fallará la verificación contra el nuevo hash. Encontrar una colisión (documento distinto con el mismo hash) requiere ~2^128 operaciones.
Robo de la clave privada El atacante extrae la clave privada cifrada de IndexedDB La clave privada está cifrada con AES-256-GCM, cuya clave deriva de PBKDF2 (600K iteraciones). Sin la contraseña, descifrar la clave es computacionalmente inviable.
Compromiso del servidor El atacante obtiene acceso total al servidor El servidor solo tiene claves públicas y registros de firmas. Las claves privadas nunca llegan al servidor. Un atacante no puede falsificar nuevas firmas. Podría eliminar o modificar registros existentes, pero las roturas de la cadena de hashes serían detectables.
Manipulación del registro de auditoría El atacante modifica eventos del registro de auditoría en el servidor Eventos encadenados por hash: modificar cualquier evento rompe la cadena a partir de ese punto. Herramientas de verificación independientes pueden detectar roturas en la cadena.

2.11.1 Supuestos

2.11.2 Fuera del alcance

2.12 Divulgación honesta

Lo que las firmas de PDF Pro NO son:

2.12.1 Para qué SÍ son adecuadas las firmas de PDF Pro

Nuestro compromiso: construimos la seguridad mediante la arquitectura, no mediante el marketing. Estos libros blancos describen exactamente cómo funcionan nuestros sistemas, incluidas sus limitaciones. Creemos que los usuarios preocupados por la seguridad merecen total transparencia técnica. Si tienes dudas sobre cualquier aspecto de nuestra arquitectura, contáctanos en info@webdesign9.com.