Blockchain und DSGVO müssen kein Widerspruch sein

Nächster Schritt: Secret

Ein einfacher Hash lässt sich bei einem bekannten, begrenzten Suchraum mit moderner Hardware schnell finden. Ein direktes Nachvollziehen, welcher Hash welchen Kunden darstellt, ist also einfach. In der Beispielimplementierung wird das mit dem Hinzufügen eines Secrets zu den zu hashenden Daten gelöst. Ist es groß genug, macht es das Erraten des Hashes unmöglich, wie man am Hash mit Secret (in JavaScript) sieht:

const web3 = require('web3')


//Größe des Secrets, je größer, desto schwieriger ist das Erraten des zu schützenden Wertes
let secretSize = 64

//Generiert ein zufälliges Secret, z.B.: 0x507f251c9d0a65dc9064f005e1cf099f22b7b881a3e19815dcdfc9d77b8c3e5d7146ffc6a7ff1066425568b0b2ac6e3e6bbaea2c2d6819279c4b250ba6e568ce
let secret = web3.utils.randomHex(secretSize)

//Der zu schützende Wert
let value = 'Max Mustermann, 01577123456, Musterstraße 42, 04321 Musterstadt'

//Das Endergebnis, zu speichern auf die Blockchain, z.B.: 0x83316cb9645f7857d30771051be16b78a6fe9ffaa5d8806bf6858888447b97be
let hashedValue = web3.utils.sha3(secret + value)

Ein weiteres Problem ist, dass die alleinige Verwendung der Blockchain unter Umständen keinen Vorteil bringt. Beispielsweise wird Transparenz, eine der großen Stärken der Blockchain, komplett ausgehebelt, wenn niemand außer dem Verwalter der Daten einsehen kann, was im System passiert. Um diesen Vorteil nutzen zu können, muss wenigstens noch ein weiterer Teilnehmer über relevante Vorgänge Bescheid wissen. Wird im Auftrag von Kunden oder unter der Aufsicht eines Regulators gearbeitet, ist ein Nachweis nützlich, hinter welchem Hash sich welcher Kunde verbirgt.

Möglich wird das, indem der Verifizierungsstelle das Secret ausgehändigt wird. Der Verifizierer kann nun mit den ihm bekannten Daten und dem Secret den Hash errechnen, also nachvollziehen, was in Zusammenhang mit dem Kunden getan wurde. Eine klare Schwäche dieses Mechanismus ist, dass das Secret verraten werden muss. Das wird über irgendeinen Kanal geschehen, der wahrscheinlich nicht ganz sicher ist. Es ist also davon auszugehen, dass ein Dritter das Secret erfahren und damit wieder einfach herausfinden kann, wer sich hinter dem zugehörigen Hash befindet.

Nächster Schritt: Merkle-Tree

Eine teilweise Lösung diese Problems lässt sich erreichen, wenn für jeden Datenpunkt ein eigenes Secret verwendet und aus den resultierenden Hashes ein Merkle-Tree gebaut wird. Dessen Roothash (Top hash) wird dann in der Blockchain gespeichert. Hier das Erstellen eines Merkle-Trees für den Datensatz eines Kunden (wieder in JavaScript):

const web3 = require('web3')

let secretSize = 64

//Kundendaten
let name = 'Max'
let lastname = 'Mustermann'
let address = 'Musterstraße 42, 04321 Musterstadt'
let phone = '01577123456'

//Generierung der Secrets für alle Datenpunkte
let nameSecret = web3.utils.randomHex(secretSize)
let lastnameSecret = web3.utils.randomHex(secretSize)
let addressSecret = web3.utils.randomHex(secretSize)
let phoneSecret = web3.utils.randomHex(secretSize)

//Gernerirung der Merkle-Tree Leafs für alle Datenpunkte
let nameLeaf = web3.utils.sha3(nameSecret + name)
let lastnameLeaf = web3.utils.sha3(lastnameSecret + lastname)
let addressLeaf = web3.utils.sha3(addressSecret + address)
let phoneLeaf = web3.utils.sha3(phoneSecret + phone)

//Generierung des ersten Levels des Merkle-Trees
let aNode = web3.utils.sha3(nameLeaf + lastnameLeaf)
let bNode = web3.utils.sha3(addressLeaf + phoneLeaf)

//Generierung des Roothash des Merkle-Trees
let rootHash = web3.utils.sha3(aNode + bNode)
Beispiel für einen Merkle-Tree (Abb. 1) (Bild: Wikipedia)

Um nun die Zugehörigkeit eines Hashes zu einem Kunden zu beweisen, wird einfach ein Merkle-Proof für das Leaf des betreffenden Datenpunkts generiert und mitsamt dessen Secret übermittelt. Das sei anhand eines Beispiels für das Erstellen eines Merkle-Proofs über die Telefonnummer eines Kunden in JavaScript verdeutlicht:

let proofText = '{"type":"Phone",' + '"secret":"' + phoneSecret + '","leaf":"' + addressLeaf + '","node":"' + aNode + '","root":"' + rootHash + '"}'

Voraussetzung für diesen Schritt sind die Daten aus dem dritten größeren Listing ("Hash mit Secret in JS"). Hier werden die dort gesetzten Variablen direkt verwendet, im Betrieb eine Datenbank. Es müssen übrigens nicht alle Daten zwischengespeichert werden. Da die Hashfunktion deterministisch ist, lassen sich Leafs und Nodes neu generieren, das heißt, es müssen lediglich die Daten, Secrets und der Roothash (zum einfachen Zuordnen) gespeichert werden.

Der Empfänger kann dann mit den ihm bekannten persönlichen Daten den Proof verifizieren und für sich bestätigen, dass der entsprechende Roothash tatsächlich in der Blockchain gespeichert ist:

//Voraussetzung ist der proofText aus dem vorhergehenden größeren Listing

const web3 = require('web3')


//Dem Verifizierer bekannter Datensatz
let verifyPhone = '01577123456'

//Parst den proofText als JavaScript-Objekt
let proof = JSON.parse(proofText)

//Generiert das zum bekannten Datensatz gehörige Leaf
let leaf = web3.utils.soliditySha3(verifyPhone + proof.secret)
let node
let root
//Switch zum korrekten Generieren des zugehörigen Roothashes
switch (proof.type) {
case 'Name':
node = web3.utils.sha3(leaf + proof.leaf)
root = web3.utils.sha3(node + proof.node)
break
case 'Lastname':
node = web3.utils.sha3(proof.leaf + leaf)
root = web3.utils.sha3(node + proof.node)
break
case 'Mail':
node = web3.utils.sha3(leaf + proof.leaf)
root = web3.utils.sha3(proof.node + node)
break
case 'Phone':
//Generiert die Node über dem zu verifizierenden Datensatz
node = web3.utils.sha3(proof.leaf + leaf)
//Generiert den Roothash aus eben generierter und übermittelter Node
root = web3.utils.sha3(proof.node + node)
break
}
//Überprüft, ob Proof valide ist, anschließend kann der Status des Roothashes in der Blockchain überprüft werden
return root === proof.rootHash

Ein Dritter kann nun im besten Fall den einen Datenpunkt einfacher erraten. Der Vorteil hierbei ist, dass sich mit den meisten einzelnen Datenpunkten kein sicherer Rückschluss auf die Identität der Person ziehen lässt – ein Vor- oder Nachname ist ja nicht einzigartig.

Abrunden lässt sich das Ganze durch sogenannte Zero-Knowledge-Proofs. Mit einem geeigneten Konstrukt lässt sich auf die Freigabe des Secrets verzichten. In der einfachsten Version hinterlegt man einfach einen Beweis, dass ein bestimmter öffentlicher Inputwert (z. B. Name der Person) Teil der Merkleroot ist. Das entsprechende Secret bleibt privat. Wenn nun jemand wissen will, ob er wirklich durch einen bestimmten Hash repräsentiert wird, kann er den Proof mit dem ihm bekannten Input (z. B. sein eigener Name) verifizieren.

Interessanterweise wird es dadurch für einen Angreifer wieder einfacher, die Hashes zu erraten, da er das Secret nicht mehr braucht. Er kann einfach einen Proof nehmen, alle möglichen Werte (z. B. Namen) als Input probieren, bis der Proof verifizierbar ist. Möglich wäre es nun, auf den Merkle-Tree zu verzichten und einfach alle Daten mit einem Secret (was notwendig bleibt, da sonst gleiche Personen immer gleiche Hashes haben werden, was zu einer einfachen Verfolgbarkeit führt) zu hashen. Das heißt, der öffentliche Input des Proofs ist nun die Gesamtheit der verwalteten personenbezogenen Daten, zum Beispiel Name, Nachname, Adresse und Telefonnummer. Das macht ein Erraten beziehungsweise Durchprobieren verschiedener Inputs wieder deutlich schwieriger.

Es ist auch möglich, auf andere Arten an die Daten zu kommen und dann den Proof zu nutzen, um herauszufinden, wer wer ist. Merkle-Tree und zk-SNARKs sind also ein Tradeoff, und es muss genau abgewogen werden, welcher Weg Verwendung finden soll.