Das Wort der Pseudonymisierung hat Konjunktur – spätestens seit der europäischen Datenschutzgrundverordnung (GDPR). Gemeint ist eine einfache Trennung von Identifikationsmerkmalen und Daten. „Einfach“, weil das Ziel klar ist. Die praktische Umsetzung kann jedoch stark variieren und im schlimmsten Fall den Datenfluss einschränken. Dabei müssen auch sensible Daten innerhalb von Geschäftsprozessen zur richtigen Zeit, am richtigen Ort für die richtigen Personen verfügbar sein. Das erfordert einen klugen Umgang mit den rechtlichen Anforderungen. Mit präzisen und flexiblen Demaskierungsstandards können Datenschutz und Datenfluss zusammenarbeiten. Der folgende Blog stellt dafür die dynamische Maskierung mit Apache Ranger in Hive vor.
Zwei Eigenschaften der Maskierung
Eine Maskierung muss zunächst zwei Eigenschaften erfüllen. Zum einen soll sie die Identität der Person unkenntlich machen. In manchen Fällen reicht dafür eine Trennung von Name und Daten, in anderen Fällen müssen weitere Identifikationsmerkmale maskiert werden (z. B. durch höherstufige Aggregation). Die zweite Eigenschaft ist die Leserlichkeit – besonders wichtig für die interne und externe Ergebniskommunikation. Das Datawarehouse-System im Hadoop Ökosystem Apache Hive sorgt mit nativen Maskierungsfunktionen wie z.B. einem SHA265-Hashing für eine sichere Maskierung mit geringer Kollisionswahrscheinlichkeit. Zwei unterschiedliche Eingabewerte werden sehr wahrscheinlich auf eindeutige Hashwerte abgebildet. Es liegt jedoch in der Natur der Sache, dass dieser Hash nicht sonderlich leserlich erscheint, sondern langkettig und diffus. Zur Unkenntlichkeit trägt bei, dass bei der in Hive implementierten Funktion auch leere Strings und Null Values gehasht werden. In der Praxis soll aber eine Reportingtabelle auf einem Blick Aufschluss über einen Sachverhalt bieten und nicht mit Hashwerten überfrachtet werden. Ein naiver Ansatz dieses Problem zu lösen ist eine einfache Substitution mit Lookup-Tabellen: ersetze Müller durch Schmidt. Diese Transformation sichert jedoch keine eindeutige Rückführung der Namen. Es muss also ein Mittelweg zwischen reinem Hash und reinem Namen gefunden werden, der beide Vorteile, nämlich sicher und leserlich, vereint. Individualisierte Formen der Maskierung können im Hadoop Ökosystem bequem mit User Defined Functions (UDFs) in Apache Hive und Apache Ranger Policies implementiert werden.
Policies flexibel festlegen – Apache Ranger Hive Plugin
Der Zugriff auf sensible Datenbestände lässt sich in Form von sogenannten Policies verwalten und bezieht sich auf Benutzer, Gruppen oder festgelegte Bedingungen (z.B. Benutzermerkmale). Für gewöhnlich sind Berechtigungen an die physischen Zugriffsobjekte (z.B. DB, Tabellen etc.) gekoppelt und setzen voraus, dass der Administrator die Speicherorte kennt. Ranger ermöglicht mit seinen Tag-based Policies zusätzlich die Zugriffsvergabe auf Grundlage von Metadaten aus Apache Atlas. Sobald ein Tag mit einem Datenobjekt assoziiert ist, kann der Administrator Berechtigungen flexibel über den Dateninhalt steuern. Flexibel ist auch die Segmentierung sensibler Datenbestände, die mit Apache Ranger sowohl zeilen- als auch spaltenbasiert erfolgen kann. In der Praxis erweist sich diese Funktionalität als besonders nützlich, beispielsweise dürfen Ländergesellschaften oftmals nur die Kunden ihres eigenen Landes einsehen, daher müssen die restlichen Kundenzeilen verborgen werden. In anderen Fällen sind sensible Merkmale wie zum Beispiel die Kreditkartennummer nur für die autorisierte Bankabteilung von Interesse, wodurch die Merkmalsspalte für andere Abteilungen ausgeschlossen werden soll. Ranger kann beides, Zeilen filtern und segmentieren und Spalten ausschließen.
Datenschutz erfordert jedoch nicht per se eine Beschneidung des ursprünglichen Datensatzes, sondern kann auch in Form einer benutzerdefinierten Datenanzeige geregelt werden. Apache Ranger stellt dafür die dynamische Maskierung zur Verfügung. Dynamisch bedeutet, dass die Maskierung der Daten on-demand erfolgt. Während eine persistente Maskierung eine Kopie der Daten erstellen, schreibt Apache Ranger Datenanfragen dynamisch um und maskiert Daten „on-the-fly“. Die festgesetzten, aktiven Policies werden bei jeder Anfrage im Hintergrund der vorgegebenen Reihenfolge nach evaluiert und entscheiden somit, wie Datenbestände für den jeweiligen Nutzer einsehbar sind. Dafür sind weder eine Änderung der Anwendung oder des Hive-Layer, noch ein Datenfluss außerhalb Hive erforderlich. Ein großer Vorteil der dynamischen Maskierung ist somit die Einsparung von ETL-Prozessen und eine Minimierung der Datenredundanz. Um die Datenanfragen zu dokumentieren, wird für jede Anwendung einer Policy ein eigener Audit-log-Eintrag erstellt, der in einer interaktiven Ansicht in der Admin-Konsole aufgerufen werden kann.
Die Art der Maskierung kann in einem Optionsmenü festgelegt werden. Standardmaskierungen sind z.B. eine partielle Maskierung wie die Anzeige der ersten oder letzten 4 Zeichen, ein Hash- oder Nullwert, die Reduktion des Geburtsdatums auf die Jahreszahl. Darüber hinaus können auch individuelle Maskierungstransformationen definiert werden. Wie an obiger Stelle schon für den Datenzugriff erwähnt, kann auch die Maskierung sowohl tag-basiert als auch zeilen- oder spaltenweise festgelegt werden, wodurch Zeile und Spalte eine eigene Maskierungs-Policy erhalten. Die praktische Umsetzung der Maskierung mit Lookup-Tabellen soll anhand eines kurzen Beispiels veranschaulicht werden.
Maskierung mit Lookup-Tabellen
Da die Anonymisierung mit Lookup-Tabellen in Hive nicht implementiert ist, müssen benutzerdefinierte Funktionen (UDF) geschrieben werden. Folgende Code-Snippets zeigen, wie solch eine UDF aussehen könnte:
private void readLookupFile(String lookupFile) throws HiveException {
try {
Configuration conf = new Configuration();
Path filePath = new Path(lookupFile);
FileSystem fs = FileSystem.get(filePath.toUri(), conf);
FSDataInputStream inputFile = fs.open(filePath);
initializeLookup(inputFile);
} catch (Exception e) {
throw new HiveException(e + ": when attempting to
access: " + lookupFile);
}
}
protected void intializeLookup(InputStream inputStream) throws IOException {
//lookupMap variable is initialized in the UDF Class
lookupMap = new HashMap<Integer, String>();
BufferedReader buffIn = new BufferedReader(new
InputStreamReader(inputStream));
String line;
int lineNr = 0;
while ((line = buffIn.readLine()) != null) {
lookup.put(lineNr, line);
lineNr++;
}
}
Die Lookup-Tabelle (CSV-Datei mit einer Spalte Lookup-Werten) wird in der Regel im HDFS abgelegt und mit Hilfe der beschriebenen Utility Funktion in eine HashMap überführt. Die HashMap ermöglicht ein performates Lookup innerhalb der UDF Aufrufe.
Ein bekanntes Problem bei der Maskierung mit Lookups ist, dass die Lookup-Tabelle deutlich weniger Unique Values als die Ursprungstabelle hat. In der folgenden Funktion werden daher an den Namen, der über einen ersten Hash aus der Lookup Tabelle extrahiert wird, noch die ersten 5 Zeichen des SHA-256-Hex Hashs des Ursprungswertes angehangen. Beispielweise kann mit dem Lookup der Name Müller durch den Namen Meier x1y2z ersetzt werden. Ein anderer Name wie z.B. Schulze (der im Lookup auch den Namen Meier zugewiesen bekäme) würde dann aber Meier a4b5c heißen. So bleibt trotzt lesbarem Lookup die Eindeutigkeit der Ursprungswerte erhalten. Das Ergebnis ist eine Maskierung mit eindeutiger Wertzuweisung.
public Object evaluate(DeferredObject[] args) throws HiveException {
// when value is null return null
if(args[0].get() == null) return null;
// when value is only blanks return only blanks
String col = (String) stringConverter.convert(args[0].get());
if (col.trim().length() == 0) return returnHelper.setReturnValue(col);
// only initialize the lookup HashMap if not yet done
if (lookup == null) {
if (args.length > 1) {
initHdfsLookup((String)fileInspector
.getPrimitiveJavaObject(args[1].get()));
}
//check if lookup is filled
if(lookup.size() == 0) throw new HiveException("Lookup File is empty
or was in the wrong format");
}
// perform lookup
String inputString = (String) stringConverter.convert(args[0].get());
String val = null;
// use the hashCode for lookup
int hash = inputString.hashCode();
int lookupRow = Math.abs(hash%lookup.size());
try {
// add part of sha1base64 as suffix to anonymized value to have (limited)
uniqueness
String sha1base64 = new Sha1Base64().evaluate(inputString);
val = lookup.get(lookupRow) +"_"+sha1base64.substring(0,5);
return returnHelper.setReturnValue(val);
}catch(NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
val = "lookup failed";
return returnHelper.setReturnValue(val);
}
Der Funktionsaufruf in Hive kann folgendermaßen aussehen:
Select hash_lookup(name_column, “/hdfs/path/to/name_lookup.csv”) from table_name;
Es gibt jedoch einiges zu beachten:
- Die Lookup-CSV sollte nur vom Hive-User lesbar sein, da darüber eine Rückführung der Namen bzw. ein manuelles Bilden des Hash-Wertes ermöglicht wird. (Hive-Konfiguration muss dafür angepasst sein)
- Die Lookup-CSV darf keine eindeutigen Namen enthalten, sondern nur Namen die häufig vorkommen
- Da nur ein Teil des Hashs als Suffix verwendet wird, besteht in der Theorie die Möglichkeit, dass unterschiedliche Namen denselben Lookup und Hash wert bekommen. Diese Chance ist aber sehr gering und in den meisten Fällen vernachlässigbar
Die Funktion ist flexibel und kann zur Maskierung mit Lookup-Tabellen für verschiedene fachliche Datentypen genutzt werden. Sie hat eine vergleichbare Performance bei dynamischer Maskierung mit unmaskierten Datenaufrufen und ist auch im Zusammenhang mit tagbasierten policies sehr gut einsetzbar. Hierbei können je nach Tag-Zuweisung andere Lookup-Dateien genutzt werden.
Fazit
Insgesamt bietet die dynamische Maskierung mit Apache Ranger Policies eine Reihe von Vorteilen. Sie löst das Problem der unleserlichen Hashwerte und der ungenauen Namenssubstitution durch eine Auswahl an Maskierungsmöglichkeiten und verbindet die Vorteile beider Ansätze. Sie verbraucht keinen zusätzlichen Speicher durch lokale Kopien und kann durch Metadaten flexibel gesteuert werden. Die Datenansicht von Benutzern und Gruppen kann sowohl für Zeilen- als auch Spalten eingeschränkt werden und bietet dadurch eine anpassungsfähige Segmentierung. Trotz der vielen Vorteile leidet die dynamische Maskierung im Vergleich mit der hashbasierten Hive-Maskierung an keinem deutlichen Performanceverlust und ist somit den anderen Alternativen vorzuziehen.