When compliance frameworks require tamper-evident audit logs, most teams encrypt their log storage and call it done. Encryption is valuable but it addresses the wrong threat. Encryption prevents an attacker from reading log content. It does not prevent an attacker from deleting log entries, modifying timestamps, or rewriting events — all of which are more useful to an attacker than reading what the logs say.
The Hash-Chain Pattern
A hash-chained log works like a blockchain without the distributed consensus overhead. Each log entry contains a hash of the previous entry. To modify or delete any entry, an attacker must recompute every subsequent hash in the chain — and the current chain head hash is held in a separate system (or published). Tampering becomes detectable.
import hashlib
import json
from datetime import datetime
def append_log_entry(prev_hash: str, event: dict) -> dict:
entry = {
"timestamp": datetime.utcnow().isoformat(),
"event": event,
"prev_hash": prev_hash,
}
entry_bytes = json.dumps(entry, sort_keys=True).encode()
entry["hash"] = hashlib.sha256(entry_bytes).hexdigest()
return entry
# Each entry references the previous entry's hash
# Modifying any entry breaks every subsequent hash in the chainWhat This Means in Practice
For HIPAA audit logs, SOC 2 evidence, and GDPR Article 30 records of processing: a hash-chained log gives you a property that encrypted-but-not-chained logs do not — you can prove to an auditor that the log has not been modified since it was written. The chain head published daily to a separate system (even a public S3 bucket) is enough to anchor the chain.
G8KEPR's audit log uses SHA-256 hash chaining on all entries. Every read, write, and export of customer data generates a hash-chained entry. The chain head is published hourly to a separate append-only store. Log integrity can be verified programmatically by any auditor with read access.
