Event-Sourcing verspricht Unveränderlichkeit: Ereignisse werden nur angehängt, niemals überschrieben oder gelöscht. Doch diese Zusicherung gilt nur so lange, wie niemand Zugriff auf die zugrundeliegende Datenbank hat – was in der Praxis selten der Fall ist.
Ein Datenbankadministrator kann Daten migrieren, Backups mit manipulierten Datensätzen wiederherstellen oder nächtliche Abfragen auf falsche Verbindungen ausführen. Selbst wenn die Anwendung selbst die Ereignisse nicht verändert, bleibt die Gefahr bestehen. Erst wenn die Datenintegrität technisch erzwungen wird, lässt sich Manipulation zuverlässig erkennen.
Hash-Ketten: Die unsichtbare Sicherheitskette
Die Lösung ist einfach, aber wirkungsvoll: Jede Zeile in der Ereignistabelle erhält zwei zusätzliche Spalten – einen Hash des Inhalts und einen Hash des vorherigen Eintrags. Diese Hashes bilden eine Kette, bei der jeder Eintrag vom vorherigen abhängt.
#1 KontoEröffnet prev=00000… hash=70be4f… │
#2 Einzahlung prev=70be4f… hash=796018… │
#3 Abbuchung prev=796018… hash=6a0260…Der Hash wird nach folgender Formel berechnet: SHA-256(previousHash || json(payload))
Ändert jemand einen Eintrag, bricht die Kette sofort auf. Selbst wenn der Angreifer den Hash des manipulierten Eintrags anpasst, stimmt der nachfolgende Hash nicht mehr – die Kette ist zerstört. Das System erkennt die Manipulation beim nächsten Verifizierungslauf.
Implementierung: Minimaler Code, maximale Wirkung
Die Anhang-Operation eines Ereignisses sieht in C# etwa so aus:
public HashChainedEntry Append(object payload) {
var previousHash = _entries.Count == 0 ? GenesisHash : _entries[^1].Hash;
var hash = ComputeHash(previousHash, payload);
var entry = new HashChainedEntry(_entries.Count + 1, payload, previousHash, hash);
_entries.Add(entry);
return entry;
}
internal static byte[] ComputeHash(byte[] previousHash, object payload) {
var payloadJson = JsonSerializer.SerializeToUtf8Bytes(payload, payload.GetType());
var combined = new byte[previousHash.Length + payloadJson.Length];
Buffer.BlockCopy(previousHash, 0, combined, 0, previousHash.Length);
Buffer.BlockCopy(payloadJson, 0, combined, previousHash.Length, payloadJson.Length);
return SHA256.HashData(combined);
}Die Verifizierung funktioniert umgekehrt: Das System durchläuft die Einträge, berechnet die Hashes neu und prüft zwei Bedingungen pro Zeile:
- Stimmt der
previousHashmit dem Hash des vorherigen Eintrags überein? - Stimmt der berechnete Hash mit dem gespeicherten Hash überein?
byte[] previousHash = new byte[32]; // Genesis-Hash
foreach (var entry in store.Entries) {
if (!ByteArraysEqual(previousHash, entry.PreviousHash))
throw new EventStreamCorruptedException(entry.Sequence, "Zeiger auf vorherigen Hash stimmt nicht");
var recomputed = ComputeHash(previousHash, entry.Payload);
if (!ByteArraysEqual(recomputed, entry.Hash))
throw new EventStreamCorruptedException(entry.Sequence, "Speicher-Hash stimmt nicht mit Neuhash überein");
previousHash = entry.Hash;
}Ein Versuch, einen Eintrag direkt in der Datenbank zu ändern, scheitert bereits beim nächsten Verifizierungslauf. Das System wirft eine Ausnahme und zeigt genau die manipulierte Zeile an.
Wo Hash-Ketten an Grenzen stoßen
Ein wichtiger Hinweis: Hash-Ketten sind eine Prüfsumme, keine digitale Signatur. Wer vollen Zugriff auf die Datenbank hat, kann nicht nur einzelne Einträge ändern, sondern die gesamte Kette neu berechnen. Die Manipulation bleibt dann unbemerkt, solange die Kette intern konsistent bleibt.
- Ein Administrator könnte alle Hashes nach einer Änderung neu berechnen.
- Die Kette selbst bietet keinen Schutz gegen Angreifer, die sowohl Datenbank als auch Anwendungslogik kontrollieren.
Diese Schwäche lässt sich nur überwinden, indem ein externer, vertrauenswürdiger Ankerpunkt hinzugefügt wird.
Externe Anker: Sicherheit jenseits der eigenen Infrastruktur
Die Lösung heißt Ankerung. Neben der internen Hash-Kette wird in regelmäßigen Abständen der aktuelle Kopf der Kette an einem Ort gespeichert, der nicht kontrolliert werden kann:
- Eine öffentliche Blockchain
- Ein RFC-3161-Zeitstempeldienst
- Ein OpenTimestamps-Kalender
- Ein Notar
Diese Ankerpunkte werden mit einem eindeutigen Transaktionshash versehen. Ändert jemand später Daten in der Datenbank, stimmt der interne Hash nicht mehr mit dem externen Anker überein. Die Manipulation wird sofort sichtbar.
Praktische Umsetzung: Was ihr selbst bauen müsst
Die Implementierung einer solchen Lösung umfasst mehrere Komponenten:
- Eine Tabelle für die Hash-Kette mit den zusätzlichen Spalten
- Einen Hintergrundprozess, der die Hashes berechnet und die Kette aktualisiert
- Einen Mechanismus, der die Ankerpunkte an externe Dienste übermittelt
- Einen Verifizierungsdienst, der die Konsistenz zwischen interner Kette und externen Ankern prüft
Stratara, das im Artikel erwähnte Framework, stellt die Grundstruktur bereit. Die konkrete Anbindung an einen externen Ankerdienst – sei es eine Blockchain, ein Zeitstempeldienst oder ein Notar – müsst ihr jedoch selbst konfigurieren. Die Beispielimplementierung läuft vollständig im Speicher, um die Funktionsweise zu demonstrieren.
Wichtige Einschränkungen und Empfehlungen
- Ein Angreifer, der sowohl die Datenbank als auch die Anker-Pipeline kontrolliert, kann weiterhin Manipulationen durchführen.
- Die Hash-Berechnung sollte nicht inline bei jedem Schreibvorgang erfolgen, sondern im Hintergrund. So bleibt die Performance der Anwendung stabil.
- Die Verifizierung sollte nicht Teil des Lesepfads sein, sondern als separater Prozess laufen.
Event-Sourcing bietet viele Vorteile, insbesondere für Systeme mit hoher Audit-Anforderung. Doch erst durch technische Maßnahmen wie Hash-Ketten und externe Anker wird die versprochene Unveränderlichkeit wirklich durchgesetzt. Wer diese Mechanismen ignoriert, riskiert, dass die historische Integrität der Daten nur auf gutem Glauben beruht.
KI-Zusammenfassung
Etkinlik kaynağı (event sourcing) ile verilerinizi değiştirilemez hale getirmek mümkün mü? Hash zinciri ve dış köklendirme yöntemleriyle verilerinizin güvenliğini nasıl artırabilirsiniz.