Challenge to the reader: Take three short text strings — call them E1, E2, E3. Set a seed h0 to 64 hex zeros. Compute h1 = SHA-256(h0 || E1), then h2 = SHA-256(h1 || E2), then h3 = SHA-256(h2 || E3), where || means “concatenate the two strings.” Now change E2 into E2x and recompute the same way, without touching your stored h1. Which stored hashes — h2, h3, both, or neither — no longer match? Write your answer down before reading on. Section 2 gives the exact numbers, and the mechanism that produces them is the entire point of this post.


A hash chain is the difference between “these are the logs we currently have” and “these logs match a cryptographic commitment that was preserved independently, before the investigation ever began.” The first statement is what a compromised server can hand you. The second is what lets an auditor trust what they’re reading.

Why it matters: almost every serious incident investigation eventually hits the same wall — how do we know nobody edited the logs before we looked at them? If an attacker has root, they can edit any file on disk. A hash chain doesn’t stop the edit, but it makes the edit detectable later, provided one thing is protected somewhere the attacker can’t reach. That one thing is the subject of Section 5, and it’s the difference between a hash chain that works and one that is pure theater.


1. The Core Idea in One Formula

Instead of hashing each log entry on its own, each entry’s hash absorbs the hash of everything that came before it:

\[h_0 = \text{an initial value (a fixed seed)}\] \[h_i = H\bigl(h_{i-1} \parallel \operatorname{encode}(E_i)\bigr)\]

Here $E_i$ is the $i$-th event record, $H$ is a cryptographic hash such as SHA-256, and encode is any unambiguous serialization of the event into bytes. The symbol $\parallel$ means concatenation — “hash the previous hash glued to this event.”

The recurrence is the whole trick. Every hash depends on the previous hash, which depends on the one before it, all the way back to the seed. Touch any event and you change its hash; change that hash and you change every hash after it. The chain binds records together in order — it isn’t just a list of independent fingerprints, it’s a single fingerprint that unfolds over time.

To see why independent hashes are different, imagine a log of three events with two schemes side by side:

INDEPENDENT hashing (what most people build first)
  d1 = H(E1)
  d2 = H(E2)        ← E2's hash knows nothing about E1
  d3 = H(E3)        ← E3's hash knows nothing about E2

CHAINED hashing (the hash chain)
  h1 = H(h0 || E1)
  h2 = H(h1 || E2)  ← E2's hash is bound to E1
  h3 = H(h2 || E3)  ← E3's hash is bound to E2 (and therefore to E1)

With independent hashes, you can delete E2 and d2 together and the surrounding records still “verify” — nothing links them. With a chain, removing E2 breaks the linkage between E1 and E3, because h3 was computed from h2, not from h1 directly.


2. A Worked Example You Can Reproduce

Let’s run the recurrence by hand, with real SHA-256 values. Use the seed as 64 hex zeros, and hash the previous digest as hex text concatenated with the event as plain text:

h0 = 0000000000000000000000000000000000000000000000000000000000000000

E1 = "Alice logs in"
h1 = SHA-256(h0 || E1)
   = e7d43f330d0538163fb02fdf336d726bebc573e0c928f8d0cc35c31403b3a47b

E2 = "Alice grants Bob administrator privileges"
h2 = SHA-256(h1 || E2)
   = c72a8c59750bde7ef4a66595d213d25b9eba22f8dac6c5bb6debf69eda7138df

E3 = "Bob exports customer records"
h3 = SHA-256(h2 || E3)
   = e12a1888b6d36f713d3818686a07170c952718cd045fa6aefa98e452e1ecea77

You can reproduce these with one shell line each:

h0=$(printf '0%.0s' {1..64})
h1=$(printf '%s%s' "$h0" "Alice logs in" | sha256sum | cut -d' ' -f1)
h2=$(printf '%s%s' "$h1" "Alice grants Bob administrator privileges" | sha256sum | cut -d' ' -f1)
h3=$(printf '%s%s' "$h2" "Bob exports customer records" | sha256sum | cut -d' ' -f1)
echo "$h3"

The block above prints e12a1888… — the chain’s head.


Now let’s tamper with it

The interesting part isn’t computing the chain; it’s what breaks when an attacker edits it. Consider three distinct attacks and what an auditor can see.

Attack A — the surgical edit. The attacker changes one event in place but is sloppy and doesn’t touch the stored hashes. Say they change E2 by adding a single trailing space (the classic invisible edit that changes “grants Bob administrator” to a subtly different record):

E2' = "Alice grants Bob administrator privileges "   (one trailing space)

Recompute forward from the edit point:
  h2' = SHA-256(h1 || E2')
      = 5dd42b9fa312431a941f690cd782845c53cac610b78a5fe7c314e7e795e2bed9

  stored h2 = c72a8c59…
  recomputed h2' = 5dd42b9f…   →  MISMATCH  ✗

The auditor recomputes h2 from the event they see and compares it to the stored h2. The two differ, so tampering is detected at the exact position of the edit — even though the edit was one invisible space. And it doesn’t stop there:

If the attacker also fixed h2 but forgot h3:
  h3' = SHA-256(h2' || E3)
      = 81834dbb6b0b0c3a65b5f6649f7e8347de171b576036410cf6a5dfb0a32773a1

  stored h3 = e12a1888…   →  MISMATCH  ✗

The corruption cascades forward: because every later hash is computed from the edited hash, one edit breaks the entire remainder of the chain. This is the property the whole technique rests on.

        edit E2 here
            │
            ▼
 h0 ──► h1 ──► h2 ✗ ──► h3 ✗   (both stored hashes now disagree)
              (recompute   (cascades from the
               differs)     changed h2)

Attack B — the full recompute. A smarter attacker changes E2 and recomputes h2 and h3 so the chain is internally consistent again. Now every link checks out. The chain alone cannot detect this — there is no fingerprint left behind that “knows” the old value. This is the central limitation, and Section 5 is the fix.

Attack C — truncation. The attacker deletes the last event, E3, along with its hash. What remains is h0 → h1 → h2, and it still verifies internally:

Recompute h1 from E1 → matches stored h1  ✓
Recompute h2 from E2 → matches stored h2  ✓

Nothing in the surviving chain reveals that E3 ever existed. Again: the chain is internally consistent, and an auditor who only holds the surviving records sees no error.

Challenge: Confirm Attack C for yourself. Using the hashes above, show that the surviving h0 → h1 → h2 is self-consistent while h3 is nowhere in evidence. Now ask: what single piece of information, stored elsewhere, would expose that E3 was deleted? (The answer is a checkpoint that records “the chain had 3 entries and its head was e12a1888….”)


3. What This Gives an Auditor or Investigator

Once a trusted anchor exists, recomputation turns the chain into evidence. The auditor walks the records from the seed forward, recomputing every hash, and compares the result to the anchor:

  • Integrity checking — do the supplied records still match the history committed to by the checkpoint?
  • Sequence verification — was the recorded ordering changed? Was a record removed or inserted mid-chain?
  • Forensic confidence — support an incident reconstruction with evidence the log wasn’t silently rewritten before it was collected.
  • Evidence-preservation checks — compare an acquired copy of the log against a previously preserved chain hash to detect any post-collection change.

All four boil down to the same operation: recompute and compare. The chain turns a vague “these are the logs we have” into a binary “these logs do, or do not, match the commitment.”


4. The Catch: An Attacker Can Recompute the Chain

A plain hash chain does not make rewriting history computationally infeasible. This is the most misunderstood fact about it, so it deserves its own section.

The chain is only a function of the records. Anyone who can rewrite the records can also rewrite the function’s output. An attacker with write access to every record and every stored hash can:

  1. Change any event.
  2. Recompute that event’s hash and every hash after it.
  3. Produce a perfectly consistent, “valid” chain.

The chain’s own links cannot tell this new chain apart from the original, because it was computed by the exact same rule. Likewise, the attacker can delete the tail of the chain and the remaining prefix stays valid — Attack C above.

So the honest summary is:

What a bare hash chain defeats:
  ✗ a sloppy attacker who edits records but forgets the hashes
  ✗ accidental corruption, truncation, and reordering

What a bare hash chain does NOT defeat:
  ✓ an attacker who edits records AND recomputes the hashes
  ✓ an attacker who deletes the tail of the log

The chain raises the bar from “edit the file” to “edit the file and recompute everything downstream,” but it does not stop the latter. That’s why the next step matters more than the chain itself.

Challenge: You have root on a log server that uses a bare hash chain with no external anchor. You want to hide that Bob ever exported the customer records. Describe, precisely, every write you must make for the chain to still verify. (If your answer is “delete the record and recompute every hash after it,” you already understand the limitation — and why the next section exists.)


5. The Fix: Anchor the Chain with an Independent Checkpoint

The chain is worthless if the attacker controls its only copy. The fix is to keep a checkpoint somewhere the attacker cannot also write: a hash value, plus the position it covers, copied periodically to independent, ideally write-once (WORM) storage.

  Log server (attacker CAN write)              Independent WORM storage
  ───────────────────────────────              ─────────────────────────
  E1 → E2 → … → E1000                                  (attacker CANNOT write)
                  │
                  └── copy (position 1000, h1000) ────►  checkpoint: (1000, h1000)

  Later, during an investigation:
    recompute h1000 from E1 … E1000
    compare it to the protected checkpoint

        ┌─ match   → the log is intact through record 1000
        └─ differ  → someone tampered (or the log was corrupted)

The checkpoint does two things the chain alone can’t:

  1. It defeats Attack B. Even if the attacker recomputes every hash on the server, the recomputed head won’t equal the checkpoint’s copy, because the checkpoint was written before the tampering and lives out of reach.
  2. It defeats Attack C. The checkpoint records the position (1000), so deleting records 901–1000 leaves a chain that stops short of the anchored position — the gap is itself evidence.

The anchor protects history through its position. Records written after the last checkpoint remain unanchored and exposed until the next checkpoint lands. This is why production schemes copy the head periodically (hourly, per batch, per rotation) — it bounds the window an attacker has to tamper undetected to the interval between checkpoints.

Challenge: A server checkpoints its chain head to WORM storage once per hour. An attacker gets root at 10:15 and is discovered at 10:45. What is the longest span of log history they can tamper with without detection, and why? (Hint: the last checkpoint was at 10:00.)


6. What a Hash Chain Still Does NOT Prove

Even a perfectly anchored chain answers exactly one question — did the stored bytes and their sequence stay intact since the anchor? It answers nothing about truth, completeness, authorship, or time. Those are separate problems with separate mechanisms:

Claim someone might make What the chain actually establishes
“The logged event really happened.” Integrity of the recorded bytes says nothing about whether the logger recorded the truth.
“Every event was logged.” Chaining cannot reveal an event that was omitted before it ever entered the log.
“This person created the record.” Hashes alone don’t authenticate an author; that’s what signatures are for.
“The timestamp is accurate.” Protecting a timestamp from later edit doesn’t establish the original clock was correct.
“This is a complete forensic chain of custody.” Cryptographic integrity supports — but does not replace — documented collection, access, transfers, and handling.

The second row is the subtlest: a hash chain is only as complete as the log it seals. If the logging software itself dropped an event before hashing it, the chain will happily attest to the edited sequence. Integrity of what was recorded ≠ completeness of what happened.


7. The Family Tree: Where Hash Chains Fit in Cryptography

A hash chain is the simplest member of a family of integrity mechanisms. Each sibling adds one ingredient to solve one more of the gaps above:

                          HASH CHAIN
                    (link each record to
                     the one before it)
                            │
      ┌──────────────┬──────┼──────────────┬───────────────┐
      │ add a        │ add  │ add a        │ publish the   │ add a trusted
      │ tree shape   │ a key│ consensus    │ head somewhere│ anchor copied
      │              │      │ + PoW        │ public        │ off the box
      ▼              ▼      ▼              ▼               ▼
  Merkle tree      HMAC   Blockchain   Certificate     Hash chain +
  (hash many       (keyed (chain +      Transparency   independent
   records per     hash;  Merkle tree)  log (Merkle)   checkpoint
   block)          authenticity)                         │
                                                          ▼
                                                   Tamper-EVIDENT
                                                    audit log
                                                (the full recipe)
  • Hash chain — binds order and content. Integrity + sequence, no authenticity, no anchor.
  • Merkle tree — generalizes the chain into a tree so you can prove one record is in a set of thousands with a short proof. Used by Certificate Transparency and Bitcoin’s transaction blocks.
  • HMAC — H(key || H(key || message)): a hash chain link, but keyed. Adds authenticity — only someone holding the key can produce a valid tag. Doesn’t scale to many auditors (they’d all need the key).
  • Digital signature — a public-key wrapper over a hash. Adds attribution and non-repudiation: this keyholder vouched for this hash.
  • Blockchain — a hash chain plus a Merkle tree plus proof-of-work (or other consensus), where everyone keeps a copy so no single party can rewrite the chain undetected.
  • Hash chain + independent checkpoint — the minimal, cheap recipe for a tamper-evident audit log, and the subject of this post.

The pattern is always the same: a hash gives you integrity; each extra ingredient buys you one more property on top.

Real systems that are secretly hash chains

Once you know the shape, you see it everywhere:

  • Git — every commit’s SHA is H(parent commit hash + tree hash + message + metadata). The commit graph is a hash chain (a DAG of them). Rewriting one commit forces a recompute of every descendant, and any other clone still holds the original — so a silent rewrite is visible the moment the two repos are compared.
  • Bitcoin — each block header hashes the previous block header and a Merkle root of its transactions. That’s a hash chain (history) riding on a Merkle tree (the block’s contents), anchored by proof-of-work and full replication.
  • Certificate Transparency — every issued certificate lands in a public, append-only Merkle tree whose root is published. That’s the “publish the head somewhere public” branch of the family tree.
  • systemd journald FSS — Forward Secure Sealing periodically seals the journal with a keyed hash that then rotates, so even a stolen sealing key can’t rewrite the past without detection. It’s the keyed-HMAC branch applied to a live log.

8. The Deeper Lesson

The hash chain is really a lesson about anchoring trust in something that can’t be edited after the fact. The hash itself is just math — anyone can run it forward. What makes the scheme real is the placement of a small piece of state (the head hash, and its position) somewhere the adversary’s reach ends.

That’s a surprisingly general idea:

  • A database’s audit log is only trustworthy if its “latest sequence number + hash” is copied somewhere the DBA can’t quietly update.
  • A Git repository’s history is only as immutable as the other copies of it that exist.
  • A blockchain is only tamper-evident because many independent parties each hold a copy of the head.

In every case, the integrity mechanism and the anchor are two different problems. People tend to over-invest in the first (exotic hashing, Merkle proofs) and forget the second (where does the trust actually live?). A bare SHA-256 chain with a weekly off-site checkpoint beats an elaborate Merkle-DAG-with-signatures that stores its own head on the same disk it protects.


Final challenge: Design the logging scheme for a small payments service. Requirements: (1) an internal attacker with root on the app server must not be able to hide a transaction, and (2) an external auditor must be able to verify six months later that a specific record was present and unaltered. Decide: your chain construction, your checkpoint cadence and destination, and — most importantly — list three things your scheme still does not prove. If your list includes “the transaction really happened” and “nothing was dropped before it was logged,” you’ve internalized the distinction this whole post is about.


Source & Further Reading