Skip to content

Integrity and Tamper Detection

Elman Mohammadi edited this page Oct 6, 2026 · 1 revision

Integrity and Tamper Detection

V14 uses multiple independent integrity mechanisms instead of relying on a single hash.

Defense in depth

Header binding
     |
     v
AEAD-authenticated slots
     |
     v
AEAD-authenticated manifest
     |
     v
SHA-256 for every ciphertext blob
     |
     v
Complete inventory check
     |
     v
Merkle root
     |
     v
Authenticated secretstream records
     |
     v
Exact plaintext-length enforcement

Each layer answers a different question.

Header binding

The package header has a canonical core. V14 computes:

SHA-256(CanonicalJSON(headerCore))

and stores the resulting 64-hex digest as header_binding.

If a package attacker changes its identity, salt, creation time, or cryptographic profile, the binding no longer matches.

Manifest authentication

The manifest is encrypted with XChaCha20-Poly1305. Authentication uses associated data containing the package ID and header binding.

Consequently, authentication fails if the encrypted manifest is altered or if an attacker attempts to reuse it in another header context.

Completion record

complete.json contains manifest_sha256, which is compared against the actual encrypted manifest.enc bytes.

It also repeats the format, version, package ID, header binding, and creation timestamp.

This is a consistency record, not a replacement for AEAD authentication.

Ciphertext hashes

For each file node, the manifest stores a SHA-256 hash of its complete encrypted blob.

During restore, the reader:

  1. inventories every blob present in blobs/;
  2. checks that every manifest-referenced blob exists;
  3. hashes each referenced blob from disk;
  4. compares the actual digest against the manifest's declared cipher_hash;
  5. rejects any mismatch.

This ensures tampering is detected before file decryption starts.

Unreachable blob detection

The reader also counts the referenced blobs and compares that set with the actual blob inventory.

A blob that exists in the package but is not referenced by the authenticated manifest causes restore failure.

This prevents an attacker from silently smuggling additional ciphertext objects into the package.

Merkle tree

V14 builds a Merkle root across file records.

The leaf is:

SHA-256(
  "SecurePackage|V14|MERKLE|LEAF|" ||
  recordId || "|" ||
  cipherHash || "|" ||
  plainSize
)

The internal node is:

SHA-256(
  "SecurePackage|V14|MERKLE|NODE|" ||
  left || right
)

When a level has an odd number of nodes, the final node is duplicated for the next level.

An empty tree has a dedicated domain-separated hash, although a valid V14 package must contain at least one file.

What the Merkle root protects

The leaf contains both the ciphertext digest and expected plaintext size, tied to the file record ID.

Therefore changing any of these values changes the leaf and eventually the root.

The root stored in the authenticated manifest is recomputed during restore. A mismatch aborts the operation before any restore output begins.

Secretstream integrity

Each file uses libsodium secretstream. Every record is authenticated, and associated data binds:

  • package ID
  • header binding
  • file record ID
  • chunk index
  • expected plaintext size

The final record must carry the final tag. The reader also checks that the total plaintext emitted is exactly the manifest-declared size and rejects trailing bytes after the final record.

Why multiple layers?

Different integrity mechanisms provide different boundaries:

  • AEAD protects record authenticity and context binding.
  • Blob SHA-256 detects complete on-disk ciphertext changes and provides an inventory-level digest.
  • The Merkle root gives the package a compact commitment over all file records.
  • Completion metadata links the manifest bytes to the package identity.

Together they reduce the chance that a single parser, storage, or bookkeeping mistake turns into silent data corruption.

Test evidence

The repository includes focused regression tests for:

  • header binding
  • header tampering
  • blob tampering
  • tamper detection before restore
  • V14 package round-trips
  • Merkle-related invariants
  • descriptor validation
  • package archive handling

The validation record dated 2026-10-01 reports these V14 integrity scenarios as passed in the development environment, with ZIP-specific execution limited there by the absence of the PHP Zip extension.

Clone this wiki locally