Part of our complete guide to negative news screening for Swiss banks. This post is the deep dive on SHA-256 hash chains for tamper-evident evidence; the guide covers the end-to-end picture.
A reasonable engineer's first instinct when asked to make evidence "tamper-proof" is to reach for write-once storage: AWS S3 Object Lock, Azure immutable blob storage, or a WORM appliance. These are necessary. They are not sufficient. They protect against the wrong threat.
The problem most regulated institutions actually face is rarely somebody deleting a file. The harder problem is proving, years after the fact, that the file shown to an auditor is exactly the file captured at the time of the original decision. Write-once storage cannot answer that on its own. A hash chain can.
What the simple version misses
The most common first implementation is to store a SHA-256 next to each artifact. An auditor verifies by re-hashing the artifact and comparing.
This works against external tampering of a single artifact. It fails against two threats that matter in regulated environments:
- The hash itself can be modified. If the database row holding
evidence_id, sha256is mutable, an insider with database access can swap both the file and the hash without leaving a trace. - The set of evidence can be modified. Even if every individual artifact is hashed, an insider can quietly add or remove artifacts from the set. A per-item hash gives no signal.
Designs that protect AML evidence have to assume the adversary may have database write access at some point in the future. This is the threat model regulators implicitly use when they ask for tamper-evidence. Not just "did the file get changed?" but "can you prove it didn't?"
Hash chains, briefly
A hash chain is a sequence of artifacts where each one's hash incorporates the previous one's hash:
H_0 = SHA-256(genesis_marker)H_n = SHA-256(H_(n-1) || artifact_n_metadata || artifact_n_content)
The property that matters: if anyone changes artifact k, every H_n
for n >= k becomes wrong. To make the chain look valid again, the
attacker has to recompute every subsequent hash and persuade an
external observer that the new chain head is the real one.
That second part is what makes the chain useful. As long as the chain head is periodically published to somewhere the institution does not control (a notary, a regulatory inbox, an internal HSM, a Git repository, an opt-in blockchain anchor), backdating becomes practically impossible.
The pattern is well-trodden. Certificate Transparency (RFC 6962, with its successor RFC 9162) applies the same idea using a Merkle tree to give append-only logs of TLS certificate issuances. The underlying primitive, SHA-256 itself, is specified in NIST FIPS 180-4; NIST SP 800-107 gives the application guidance.
A practical implementation shape
For each investigation, an append-only evidence ledger with a schema roughly like this works well:
investigation_id(UUID)sequence(monotonic int per investigation)evidence_id(UUID, references the immutable storage row)artifact_sha256(the hash of the evidence content)previous_chain_hash(the previous row'schain_hash)chain_hash(SHA-256(previous_chain_hash || sequence || evidence_id || artifact_sha256 || timestamp))captured_at(timestamp)captured_by(user id, frozen at capture time)
When the investigation reaches a decision and the four-eyes approval
lands, the final chain_hash gets signed (typically with the
institution's HSM) and written into a separate decision_seal row. The
seal is what gets published to the institution's chosen anchor.
In regulated environments, the choice of anchor is usually conservative. Some institutions email the signed seal to the compliance officer and a notary. Some use an internal append-only log managed by a separate team. Public blockchain anchoring is technically attractive but tends to be ruled out on principle: most regulated banks do not want any metadata about their KYC operations on a public ledger, even hashed.
The cryptography is not the hard part. The hard part is making sure the chain head escapes the institution's control before anyone has reason to forge it.
Performance considerations
Each evidence capture adds two database writes (the evidence row, the ledger row) and one hash computation. SHA-256 over a typical PDF on modern commodity hardware is sub-millisecond. Intel CPUs since Goldmont and AMD CPUs since Zen include hardware SHA extensions that make this essentially free. The chain hash is computed over kilobytes of metadata, not the full artifact, so it is even cheaper.
Concrete latency depends on database round-trip and storage write characteristics, not on the hashing itself. The right way to verify this in your own environment is to measure: turn on the chain in a staging environment, push representative load, look at the p99.
Storage overhead is also small. A typical ledger row with the schema above fits comfortably in a few hundred bytes. Compared to the actual evidence (which dominates), the ledger is negligible.
Chain verification at audit time is bounded by the size of the
investigation, not the entire ledger. A reviewer asking to verify a
specific investigation reads its rows in order, recomputes each
chain_hash, and compares to the stored value. For a typical
investigation with a few dozen artifacts, this is well under a second.
Things to watch for
A few patterns that come up when this kind of system meets reality:
- Same evidence, multiple investigations. Two investigations may reference the same news article from the same URL, captured at different times. The capture context (who, when, why) is part of the evidence story, so even bytewise-identical artifacts usually deserve separate ledger entries, though deduplication is reasonable at the storage layer.
- Verifying integrity over time. Storage corruption, accidental re-encoding by a misconfigured pipeline, or migration between systems can change bytes silently. A nightly job that re-hashes a sample of stored artifacts and flags mismatches is worth more than people expect.
- HSM lifecycle. If the institution rotates HSM providers or keys,
prior
decision_sealrows still need to verify against the old key. The cleanest path is to record the key fingerprint or certificate in each seal row and keep a public registry of historical keys with their retirement dates. None of this is hard. All of it is fiddly if you do not plan for it.
What is deliberately not included
Two things worth considering and rejecting on purpose:
- Public blockchain anchoring as a default. Technically elegant, but the regulated banks we have spoken to are uniformly cool on it. Make it an opt-in plugin.
- Merkle trees instead of a linear chain. Merkle trees would let you prove the inclusion of a single artifact without revealing the others, which is genuinely useful for some applications. For an audit story that is "show me the evidence for this investigation, in order", a linear chain is simpler and matches the question being asked. The OpenZeppelin docs on Merkle proofs are a good starting point if you do need them.
The hard parts in order: (1) deciding what to publish and where, (2) freezing the evidence content on capture so the hash is stable, (3) handling the case where the same evidence is referenced by multiple investigations. The hashing itself is the easy part. Pick any well-reviewed library and move on.
What this gets you
For any investigation, the institution can produce, on demand (and the audit drill protocol is the way to test whether yours actually does):
- The exact set of evidence the analyst reviewed.
- The exact bytes of each piece of that evidence.
- A cryptographic proof, verifiable without trusting any single component (the application, the database, the storage layer), that the evidence has not been altered since capture.
- The signature of the institution's compliance officer at the time of approval.
That is what an examiner, an external auditor, or a journalist with a specific question needs to see. Not a screenshot of a dashboard.



