← Volver a todos los artículos

Cadenas de hashes y detección de manipulaciones: cómo un solo byte modificado invalida todo un archivo

SealDoc Team · · 10 min de lectura

Cuando alguien dice que un archivo documental es “tamper-evident”, es decir, que deja constancia de cualquier manipulación, la afirmación es concreta: cualquier modificación de cualquier registro produce una incoherencia detectable. No significa “tamper-proof”, a prueba de manipulaciones: esa es una afirmación distinta y más fuerte. Que deje constancia de las manipulaciones significa que, si el archivo ha sido modificado, puedes encontrar la modificación.

Las cadenas de hashes son la construcción estándar para lograr esta propiedad. No son nuevas (el concepto es décadas anterior a blockchain), pero se entienden mal con frecuencia, lo que lleva tanto a un exceso de confianza como a un uso insuficiente.

Este artículo explica cómo funcionan las cadenas de hashes, qué garantizan, qué no garantizan y por qué importan las decisiones concretas de implementación.

Qué es un hash criptográfico

Una función hash criptográfica toma una entrada de longitud arbitraria y produce una salida de longitud fija, llamada hash o resumen. SHA-384 produce una salida de 384 bits (48 bytes), que normalmente se representa como 96 caracteres hexadecimales.

Tres propiedades hacen que las funciones hash sean útiles para detectar manipulaciones:

Determinismo. La misma entrada siempre produce la misma salida. Si calculas dos veces el hash del mismo documento, obtienes el mismo hash.

Efecto avalancha. Un pequeño cambio en la entrada produce una salida completamente diferente. Cambiar un byte en un documento de 10.000 bytes produce un hash que no se parece en nada al original. No hay forma de predecir cómo será el nuevo hash.

Unidireccionalidad. Dado un hash, es computacionalmente inviable reconstruir la entrada. No puedes recorrer el camino inverso desde el hash hasta el documento.

La tercera propiedad es la que hace vinculantes las cadenas de hashes: una vez que publicas o registras un hash, te has comprometido con el contenido que lo produjo sin revelar el contenido en sí.

La construcción básica

Una cadena de hashes enlaza una secuencia de eventos de modo que cada evento depende de todos los eventos anteriores.

En la implementación de SealDoc, un evento de evidencia contiene:

  • El tipo de evento (p. ej., vault.ingested, anchor.rfc3161)
  • Un número de secuencia
  • Una marca de tiempo
  • Detalles específicos del evento (como JSON canónico)
  • El hash del evento anterior
  • El hash de este evento, calculado sobre todos los campos anteriores

El hash de cada evento incluye el hash del evento anterior. Esto significa que, para reproducir el hash del evento N, necesitas el contenido exacto del evento N y el hash exacto del evento N-1. El hash del evento N-1 dependía del evento N-2, y así sucesivamente hasta el primer evento.

El primer evento usa un valor génesis fijo como su “hash anterior”: una constante conocida que cualquiera puede verificar.

Esto crea una cadena en la que:

Hash(Event 1) = H("vault.ingested" | seq:1 | ts:... | details:... | genesis)
Hash(Event 2) = H("anchor.rfc3161" | seq:2 | ts:... | details:... | Hash(Event 1))
Hash(Event 3) = H("vault.storage-lock.applied" | seq:3 | ts:... | details:... | Hash(Event 2))

Qué ocurre cuando se manipula

Supongamos que un adversario quiere modificar los detalles del Evento 1. Cambia un campo del evento y vuelve a calcular el hash del Evento 1. El nuevo hash es distinto del original.

Pero el hash del Evento 2 se calculó usando el hash original del Evento 1. El adversario necesita ahora volver a calcular el hash del Evento 2 usando el nuevo hash del Evento 1. Eso produce un hash distinto para el Evento 2.

Ahora el Evento 3 tiene un hash anterior incorrecto. Y así sucesivamente con cada evento posterior.

Para que la modificación sea indetectable, el adversario necesita volver a calcular los hashes de todos los eventos desde el punto manipulado en adelante. En un sistema local y autocontenido, esto es posible. La cadena por sí sola no lo impide.

Por eso importa el anclaje.

Anclaje: hacer que la cadena sea verificable externamente

Una cadena de hashes que solo existe en tu propia base de datos prueba la integridad dentro de tu propio sistema. Un adversario que controle tu base de datos puede reescribir toda la cadena. La cadena necesita anclarse a algo que esté fuera de tu control.

El mecanismo estándar es un sello de tiempo RFC 3161. El flujo funciona así:

  1. En el momento de la creación del documento, calcular el hash del evento de evidencia.
  2. Enviar ese hash a una Autoridad de Sellado de Tiempo (TSA) independiente.
  3. La TSA firma una respuesta que contiene tu hash, el sello de tiempo de la TSA y la identidad de la TSA. Esta respuesta firmada es el token de sello de tiempo.
  4. Almacenar el token de sello de tiempo junto a la cadena de hashes.

El token de sello de tiempo está firmado con la clave privada de la TSA. La cadena de certificados de la TSA es verificable públicamente. Cualquiera que tenga el documento, el hash original y el token de sello de tiempo puede verificar de forma independiente que el documento existía en ese estado en ese momento, sin necesidad de confiar en ti en absoluto.

Ahora el problema del adversario es más difícil. Para modificar el Evento 1 sin que se detecte, necesita:

  1. Modificar el evento y volver a calcular todos los hashes posteriores.
  2. Obtener de una TSA de confianza un nuevo sello de tiempo RFC 3161 para el nuevo hash.

El segundo paso es imposible de forma retroactiva. El sello de tiempo de la TSA lleva incorporada una hora. Un sello de tiempo que afirme que el documento modificado existía en la hora original tendría que haberlo emitido la TSA en esa hora original, algo que el adversario no puede organizar a posteriori.

Esto es lo que significa “tamper-evident” en la práctica: no que la modificación se impida a nivel físico, sino que cualquier modificación que un adversario quiera que pase desapercibida exige o bien comprometer la firma de la TSA independiente o bien reescribir la historia, y ambas cosas son computacionalmente inviables.

Por qué dos resúmenes, y cuál está dónde

SHA-256 es el algoritmo hash más utilizado para este tipo de aplicación. SealDoc lo usa para la cadena de procesamiento y el manifiesto del paquete, y SHA-384 para el registro de evidencia (evidence ledger). Ambos se ejecutan para cada documento; responden a preguntas distintas.

La cadena de procesamiento no puede elegir libremente. Su resumen es también la clave de almacenamiento, y es la huella del mensaje (message imprint) que firma la Autoridad de Sellado de Tiempo, de modo que la línea de evidencia va de los bytes, a través de la huella, hasta el token. Cambiarlo supondría migrar cada localizador almacenado y cada token existente, no cambiar un ajuste.

El registro de evidencia se construyó más tarde y no tiene esa vinculación, y por eso usa el resumen más largo.

La diferencia práctica es el margen. SHA-256 se considera seguro actualmente, pero las recomendaciones criptográficas evolucionan. SHA-384 pertenece a la misma familia SHA-2 y se calcula de forma idéntica, solo que con un estado interno mayor. Para los archivos con períodos de retención largos (7 años para las facturas de IVA de la UE, hasta 20 años para determinados registros del sector público), el margen adicional importa.

El resumen más largo también reduce la probabilidad (ya insignificante) de colisiones de hash, en las que dos documentos distintos producen el mismo hash. Para los archivos de cumplimiento, es una propiedad relevante: dos facturas distintas nunca deberían producir el mismo identificador.

Verificar la cadena

La verificación funciona hacia atrás desde el último evento. Dado un conjunto de eventos en orden de secuencia:

  1. Para cada evento, vuelve a calcular el hash con los campos almacenados: tipo de evento, número de secuencia, marca de tiempo, detalles y hash anterior.
  2. Compara el hash recalculado con el hash almacenado. Si coinciden, el contenido del evento no ha cambiado desde que se calculó el hash.
  3. Comprueba que el previousHash de cada evento coincide con el hash almacenado del evento que lo precede. Si coinciden, la cadena está intacta.
  4. Para cualquier evento que lleve un ancla de sello de tiempo RFC 3161, verifica el token de sello de tiempo frente a la cadena de certificados de la TSA.

El endpoint GET /api/vault/{recordId}/verify de SealDoc recalcula cada hash, comprueba cada enlace, verifica las firmas de los eventos e informa del anclaje temporal, con un desglose por evento:

{
  "chainValid": true,
  "eventCount": 3,
  "events": [
    {
      "sequenceNo": 1,
      "eventType": "vault.ingested",
      "hashVerified": true,
      "chainLinkVerified": true,
      "signaturePresent": true,
      "signatureVerified": true
    }
  ]
}

hashVerified significa que el hash recalculado coincide con el hash almacenado. chainLinkVerified significa que el puntero al hash anterior es coherente. signatureVerified significa que la firma ECDSA sobre el hash del evento es válida frente a la clave de firma conocida.

Una cadena en la que cada evento devuelve hashVerified: true y chainLinkVerified: true es una cadena en la que no se ha modificado ningún dato almacenado. Los sellos de tiempo de anclaje aportan la prueba externa, verificable a través de la TSA, de cuándo ocurrió cada evento.

Lo que la cadena no garantiza

Dejar constancia de las manipulaciones es una propiedad concreta y acotada. Conviene ser preciso sobre lo que cubre y lo que no.

No impide la eliminación. Una cadena de hashes detecta la modificación de registros existentes, pero no puede detectar que se ha eliminado un registro. Si el Evento 2 de una cadena de cinco eventos se elimina por completo, la cadena se rompe, pero solo si el verificador sabe que debería haber cinco eventos, no cuatro. Impedir la eliminación es una cuestión aparte, propia de la capa de almacenamiento, como el almacenamiento WORM.

No impide que el sistema de origen mienta. Si el documento ya estaba dañado antes de entrar en el flujo de procesamiento, la cadena de hashes se compromete con esa versión dañada. La cadena prueba la integridad desde el momento de la ingesta en adelante, no desde el momento de la creación. Por eso la validación (la comprobación de conformidad PDF/A, la validación del esquema Factur-X) se realiza antes de consignar el hash.

La cadena solo es tan sólida como sus anclas. Una cadena sin anclas RFC 3161 externas es un sistema que se sella a sí mismo. Prueba la coherencia interna, pero no la vinculación temporal. Para cualquier caso de uso serio de cumplimiento, son necesarios sellos de tiempo anclados a una TSA independiente.

La conclusión práctica para los ingenieros

Si estás construyendo un flujo de procesamiento de documentos y necesitas que cualquier manipulación quede en evidencia, la implementación es sencilla:

  1. En la ingesta, calcula un hash del documento con SHA-256 o SHA-384.
  2. Envía el hash a una TSA RFC 3161 y almacena el token de sello de tiempo devuelto.
  3. Para cada evento posterior, enlaza el hash del evento con el hash del evento anterior.
  4. Almacena la cadena en un almacenamiento de solo anexión (sin actualizaciones ni eliminaciones en la tabla de eventos).
  5. Expón un endpoint de verificación que recalcule y compruebe la cadena a petición.

La complejidad conceptual es baja. La complejidad de implementación procede de cuestiones operativas: la gestión de claves, la fiabilidad de la TSA, la migración de la cadena si cambias el algoritmo hash y el tratamiento del caso en que el envío a la TSA falla a mitad de una transacción.

SealDoc se ocupa de estas cuestiones como infraestructura, de modo que los flujos que envían documentos a través de la API obtienen la cadena sin implementarla. Pero si la construyes tú mismo, lo anterior es la construcción mínima viable.


El Evidence Vault de SealDoc produce un registro de eventos con cadena de hashes para cada documento y solicita un sello de tiempo RFC 3161 a una Autoridad de Sellado de Tiempo independiente de la UE. La cadena y los sellos de tiempo son verificables mediante GET /api/vault/{recordId}/verify sin confiar en la infraestructura de SealDoc.


← Volver a todos los artículos