---
{
  "title": "HIPAA Safe Harbor De-Identifikation in Aidbox: Von FHIR zu Analytics ohne Offenlegung von Patientendaten",
  "description": "Aidbox unterstützt HIPAA Safe Harbor-De-Identifikation direkt in ViewDefinitions. Wandeln Sie FHIR-Daten in konforme, analysebereit aufbereitete Tabellen um – mit spaltengenauer Steuerung – und ordnen Sie Ergebnisse bei Bedarf wieder zu.",
  "date": "2026-04-22",
  "author": "Andrew Listopadov",
  "reading-time": "10 min read",
  "tags": [
    "SQL on FHIR",
    "Compliance",
    "Analytics",
    "Aidbox"
  ],
  "seo-tags": [
    "HIPAA Safe Harbor",
    "FHIR de-identification",
    "SQL on FHIR",
    "healthcare data analytics",
    "PHI protection",
    "ViewDefinition"
  ]
}
---
## Das Problem mit Healthcare-Analytics

Gesundheitsorganisationen verfügen über wertvolle Daten, doch deren Weitergabe ist ein Minenfeld. Ein Krankenhaus möchte die Raten akuter Nierenschäden in seiner ICU-Population untersuchen. Ein externes Forschungsteam verfügt über die statistische Expertise. Die Daten sind vorhanden – Hunderttausende von Laborbefunden, die als FHIR-Ressourcen in Aidbox gespeichert sind. Die Weitergabe von Roh-Patientendatensätzen verstößt jedoch gegen HIPAA.

Der übliche Workaround ist eine maßgeschneiderte ETL-Pipeline: Daten exportieren, ein Skript ausführen, um Identifikatoren zu entfernen, hoffen, dass nichts durchrutscht, und die bereinigten Daten an anderer Stelle laden. Dies ist langsam, fehleranfällig und schwer zu auditieren. Jede neue Forschungsfrage erfordert eine weitere Pipeline.

Was wäre, wenn die De-Identifikation *innerhalb* der Datenbank stattfände, deklariert zusammen mit der View, die die Daten strukturiert?

## De-Identifikation als ViewDefinition-Funktion

Aidbox unterstützt jetzt spaltenweise De-Identifikation in [ViewDefinitions](/blog/what-is-a-viewdefinition). Sie versehen Spalten mit einer [FHIR-Extension](/articles/extending-fhir-resources), die angibt, welche Transformation angewendet werden soll. Der SQL-Compiler umschließt jeden Spaltenausdruck mit einer PostgreSQL-Funktion – sensible Daten werden transformiert, bevor sie die Ausgabe erreichen.

```json
{
  "resourceType": "ViewDefinition",
  "name": "deident_observations",
  "status": "active",
  "resource": "Observation",
  "select": [{
    "column": [
      {
        "name": "patient_id",
        "path": "subject.getReferenceKey(Patient)",
        "extension": [{
          "url": "http://health-samurai.io/fhir/core/StructureDefinition/de-identification",
          "extension": [
            {"url": "method", "valueCode": "cryptoHash"},
            {"url": "cryptoHashKey", "valueString": "my-secret-key"}
          ]
        }]
      },
      {
        "name": "status",
        "path": "status"
      }
    ]
  }]
}
```

Die Spalte `patient_id` trägt eine De-Identifikations-Extension. Sie gibt anstelle der echten Patientenreferenz einen HMAC-SHA256-Hash aus – deterministisch (derselbe Patient erhält immer denselben Hash) und nicht umkehrbar (der Originalwert lässt sich nicht aus dem Hash wiederherstellen). Die Spalte `status` besitzt keine Extension und wird unverändert durchgegeben.

Der ViewDefinition Builder in der Aidbox-Benutzeroberfläche macht dies visuell nachvollziehbar – klicken Sie auf das Schildsymbol bei einer beliebigen Spalte, um eine Methode auszuwählen und Parameter festzulegen:

![Set up de-identification in ViewDefinition Builder](image-1.png)

## Verfügbare Methoden

| Methode                 | Funktion                                                                      | Wichtige Parameter                         |
|-------------------------|-------------------------------------------------------------------------------|--------------------------------------------|
| **redact**              | Durch NULL ersetzen                                                           | —                                          |
| **cryptoHash**          | HMAC-SHA256-Hash (deterministisch, Einwegfunktion)                            | `cryptoHashKey`                            |
| **dateshift**           | Datum um ±1–50 Tage pro Ressource verschieben                                 | `dateShiftKey`                             |
| **birthDateSafeHarbor** | Datumsverschiebung + automatische Schwärzung bei Alter >89 (nur Patient.birthDate) | `dateShiftKey`                         |
| **encrypt**             | AES-128-CBC, Base64-Ausgabe (mit Schlüssel umkehrbar)                         | `encryptKey`                               |
| **substitute**          | Durch eine feste Zeichenkette ersetzen                                        | `replaceWith`                              |
| **perturb**             | Zufälliges Rauschen zu numerischen Werten hinzufügen                          | `span`, `rangeType`, `roundTo`             |
| **custom_function**     | Eigene PostgreSQL-Funktion aufrufen                                            | `custom_function`, `custom_arg` (optional) |

Zwei Methoden sind für das Muster, das wir gleich erkunden werden, besonders relevant:

**cryptoHash** erzeugt für dieselbe Eingabe dieselbe Ausgabe – was bedeutet, dass Sie sie zum Verknüpfen von Tabellen nutzen können. Hashen Sie die Patienten-ID in zwei verschiedenen ViewDefinitions mit demselben Schlüssel, und die Hashes stimmen überein. Dies ist die Grundlage für die Re-Identifikation.

**dateshift** verschiebt alle Daten innerhalb derselben Ressource um denselben Offset und bewahrt so zeitliche Beziehungen. Ein Abstand von 3 Tagen zwischen zwei Ereignissen bleibt ein Abstand von 3 Tagen. Der Offset wird aus `HMAC(dateShiftKey, resource.id)` abgeleitet und ist daher pro Ressource deterministisch, ohne den Schlüssel jedoch nicht vorhersehbar.

## Praktisches Beispiel: Outsourcing der AKI-Forschung

Lassen Sie uns ein realistisches Szenario mit MIMIC-IV-Daten durchgehen, die in Aidbox geladen wurden – 100 ICU-Patienten, über 800.000 Beobachtungen.

**Das Ziel**: Ein Krankenhaus möchte, dass ein externes Forschungsteam die Raten akuter Nierenschäden (AKI) in seiner ICU-Population analysiert. Die Forscher benötigen Kreatinin-Laborbefunde, dürfen jedoch keine echten Patientenidentifikatoren oder genauen Daten einsehen.

**Der Ansatz**: Erstellen Sie zwei materialisierte Tabellen – einen de-identifizierten Datensatz für die Forscher und eine interne Zuordnungstabelle, die gehashte IDs mit echten Patienten verknüpft.

### Entwurf der de-identifizierten View

Wir beginnen mit einer ViewDefinition auf der `Observation`-Ressource, gefiltert nach Kreatinin-Laborbefunden. Vier Spalten, jede mit einer anderen De-Identifikationsstrategie:

**Patienten-ID** – die Spalte mit dem größten Schutzbedarf. Wir wenden `cryptoHash` an, um die echte UUID in eine nicht umkehrbare HMAC-SHA256-Hex-Zeichenkette umzuwandeln:

```json
// ---8<--- snip
{
  "name": "patient_id",
  "path": "subject.getReferenceKey(Patient)",
  "extension": [{
    "url": "..de-identification",
    "extension": [
      {"url": "method", "valueCode": "cryptoHash"},
      {"url": "cryptoHashKey", "valueString": "aki-study-2026"}
    ]
  }]
}
// ---8<--- snap
```

**Kreatininwert und Einheit** – die klinischen Daten, die der Forscher tatsächlich benötigt. Keine De-Identifikation – Laborwerte sind keine Identifikatoren:

```json
// ---8<--- snip
{
  "name": "creatinine",
  "path": "value.ofType(Quantity).value",
  "type": "decimal"
},
{
  "name": "unit",
  "path": "value.ofType(Quantity).unit"
}
// ---8<--- snap
```

**Effektivdatum** – verschoben durch einen deterministischen ressourcenspezifischen Offset mittels `dateshift`. Ein Abstand von 3 Tagen zwischen zwei Laborbefunden bleibt erhalten, das echte Kalenderdatum wird jedoch verborgen:

```json
// ---8<--- snip
{
  "name": "effective_date",
  "path": "effective.ofType(dateTime)",
  "extension": [{
    "url": "..de-identification",
    "extension": [
      {"url": "method", "valueCode": "dateshift"},
      {"url": "dateShiftKey", "valueString": "aki-study-2026"}
    ]
  }]
}
// ---8<--- snap
```

### Die Zuordnungstabelle

Die zweite ViewDefinition dient der Re-Identifikation – sie verbleibt intern. Zwei Spalten auf der Patient-Ressource, beide auf `id` zeigend: eine unverändert, eine gehasht mit **demselben Schlüssel** wie oben. Da `cryptoHash` deterministisch ist, stimmen die gehashten IDs in beiden Tabellen überein.

### Erstellung in der Benutzeroberfläche

Der ViewDefinition Builder macht dies visuell nachvollziehbar. Klicken Sie auf das Schildsymbol bei einer beliebigen Spalte, um eine De-Identifikationsmethode auszuwählen und deren Parameter festzulegen:

![Create the ViewDefinition in the builder interface and configure de-identification methods on each column](image-2.png)

Nach dem Speichern als Tabelle materialisieren:

![Materialize the ViewDefinition as a table](image-3.png)

{% hint style="info" %}
ViewDefinitions mit De-Identifikations-Extensions können nur als `table` materialisiert werden – nicht als `view` oder `materialized-view`. PostgreSQL speichert die vollständige SQL-Definition von Views in Systemkatalogen (`pg_views`), was Ihre kryptografischen Schlüssel für jeden mit Katalogzugriff offenlegen würde. Tabellen speichern nur die transformierten Daten.
{% endhint %}

### Was der Forscher sieht

Der Forscher fragt die de-identifizierte Tabelle ab. Keine echten Patienten-IDs, keine echten Daten:

```sql
SELECT * FROM sof.deident_creatinine LIMIT 5;
```

| `patient_id`               | `creatinine` | `unit` | `effective_date`          |
|----------------------------|-------------:|--------|---------------------------|
| db1d98d58...f1508c7b439aba |          1.1 | mg/dL  | 2113-01-17T09:45:00-05:00 |
| db1d98d58...f1508c7b439aba |          1.4 | mg/dL  | 2116-03-05T16:16:00-05:00 |
| f852c0e57...ca9c6e3db462a9 |          0.8 | mg/dL  | 2153-03-08T02:17:00-04:00 |
| 59e60d080...01688175f47b16 |          1.7 | mg/dL  | 2147-09-09T15:20:00-04:00 |
| 6283390bd...738cd02dac4c08 |          0.8 | mg/dL  | 2140-10-03T15:20:00-04:00 |

Die `patient_id` ist eine Hex-Zeichenkette – der Forscher kann danach gruppieren, eindeutige Patienten zählen und Trends im Zeitverlauf verfolgen. Der Originalwert lässt sich jedoch nicht rekonstruieren.

### Berechnung der Kennzahl

AKI wird häufig durch erhöhtes Kreatinin identifiziert. Ein vereinfachtes Screening-Kriterium: Kreatinin über 1,5 mg/dL weist auf eine mögliche Nierenschädigung hin. Der Forscher führt folgende Abfrage aus:

```sql
SELECT
  CASE
    WHEN creatinine <= 1.1 THEN 'Normal (≤1.1)'
    WHEN creatinine <= 1.5 THEN 'Elevated (1.1–1.5)'
    WHEN creatinine <= 3.0 THEN 'High (1.5–3.0)'
    ELSE 'Critical (>3.0)'
  END AS risk_category,
  count(*) AS observation_count,
  count(DISTINCT patient_id) AS patient_count,
  round(avg(creatinine)::numeric, 2) AS avg_creatinine
FROM sof.deident_creatinine
GROUP BY 1
ORDER BY 1;
```

| `risk_category`    | `observation_count` | `patient_count` | `avg_creatinine` |
|--------------------|--------------------:|----------------:|-----------------:|
| Critical (>3.0)    |                 307 |              19 |             5.08 |
| Elevated (1.1–1.5) |                 393 |              40 |             1.33 |
| High (1.5–3.0)     |                 575 |              30 |             2.11 |
| Normal (≤1.1)      |                1728 |              84 |             0.77 |

Bei 19 Patienten wurden Kreatininwerte über 3,0 mg/dL gemessen – ein kritischer Schwellenwert, der auf eine schwere Nierenschädigung hindeutet. Der Forscher identifiziert diese Patienten anhand ihrer gehashten IDs und sendet die Ergebnisse an das Krankenhaus zurück.

### Re-Identifikation für die klinische Nachverfolgung

Das Krankenhaus verknüpft die Forschungsergebnisse mit der internen Zuordnungstabelle – indem gehashte IDs den echten Patienten-UUIDs zugeordnet werden:

```sql
SELECT
  m.real_id,
  max(o.creatinine) AS peak_creatinine,
  count(*) AS high_readings
FROM sof.deident_creatinine o
JOIN sof.patient_id_map m ON o.patient_id = m.hashed_id
WHERE o.creatinine > 3.0
GROUP BY m.real_id
ORDER BY peak_creatinine DESC
LIMIT 20;
```

| `real_id`                            | `peak_creatinine` | `high_readings` |
|--------------------------------------|------------------:|----------------:|
| af0e5009-d87d-52a8-ac8a-676e471c41f1 |              15.2 |               9 |
| 8e77dd0b-932d-5790-9ba6-5c6df8434457 |                11 |              80 |
| 1cf9e585-806c-513b-80af-4ca565a28231 |               9.1 |              62 |
| 568cb149-804c-59e8-bdf5-816e8151cd22 |               8.3 |               9 |
| df756e08-6ea8-5d69-b918-67911945f827 |               7.9 |              11 |
| 7ec7078a-0593-5a99-9862-ebbff47fd1c5 |               6.4 |              20 |
| dd2bf984-33c3-5874-8f68-84113327877e |               6.2 |              26 |
| 4f773083-7f4d-5378-b839-c24ca1e15434 |               5.3 |              11 |
| 72d56b49-a7ee-5b9a-a679-25d1c836d3c3 |               5.1 |               3 |
| ...                                  |               ... |             ... |
| 842680b3-e421-58cc-8050-3b29668b438c |               3.2 |               1 |

Das Krankenhaus weiß nun genau, welche Patienten klinische Aufmerksamkeit benötigen – ohne deren Identitäten jemals dem Forschungsteam offengelegt zu haben.

### Das Gesamtbild

```mermaid
graph TD
  A[FHIR Observations in Aidbox] -->| ViewDefinition + de-identification | B[De-identified table]
  A -->| ViewDefinition ID mapping | C[Mapping table]
  B -->| shared with | D[External researcher]
  D -->| results with hashed IDs | E[Hospital receives results]
  C -->| stays internal | E
  E -->| JOIN on mapping table | F[Real patient IDs for follow-up]
```

Die zentrale Erkenntnis: **Derselbe `cryptoHashKey` verbindet alles miteinander**. Der Forscher arbeitet ausschließlich mit gehashten Identifikatoren. Das Krankenhaus besitzt die Zuordnungstabelle und den Schlüssel. Die Re-Identifikation erfolgt ausschließlich intern und nur bei Bedarf.

## Vorgefertigte Safe Harbor-ViewDefinitions

Das Schreiben von De-Identifikationsregeln für jeden Ressourcentyp ist mühsam. Wir stellen [`io.health-samurai.de-identification.r4`](https://get-ig.org/io.health-samurai.de-identification.r4) bereit – ein FHIR-Paket, das über die Artifact-Registry von Aidbox verfügbar ist, mit vorgefertigten ViewDefinitions für 17 gängige R4-Ressourcentypen: Patient, Encounter, Observation, Condition, Claim, ExplanationOfBenefit, AllergyIntolerance, DiagnosticReport, MedicationRequest, MedicationDispense, MedicationAdministration, Immunization, Procedure, Specimen, DocumentReference, Practitioner und Location.

Jede ViewDefinition wendet Safe Harbor-Regeln an:

- **cryptoHash** auf Identifikatoren und Referenzen – tabellenübergreifend konsistent für Joins
- **dateshift** auf klinische Daten – bewahrt zeitliche Beziehungen
- **birthDateSafeHarbor** auf Patient.birthDate – Datumsverschiebung plus automatische Schwärzung bei Patienten über 89 Jahren, wie von [45 CFR 164.514(b)(2)(i)(C)](https://www.law.cornell.edu/cfr/text/45/section-164.514) gefordert
- **redact** auf Namen, Adressen und andere direkte Identifikatoren

Alle kryptografischen Schlüsselparameter sind standardmäßig leer – legen Sie Ihre eigenen Schlüssel fest, bevor Sie materialisieren. Installieren Sie das Paket über FAR, konfigurieren Sie die Schlüssel, materialisieren Sie als Tabellen und beginnen Sie mit Abfragen.

## Erste Schritte

1. **FHIR-Schema-Modus aktivieren** – setzen Sie `fhir.validation.fhir-schema-validation=true` (erforderlich seit Aidbox 2604)
2. **`io.health-samurai.de-identification.r4` über FAR installieren** oder eigene ViewDefinitions schreiben
3. **Kryptografische Schlüssel** in jeder ViewDefinition festlegen – leere Schlüssel werden abgelehnt
4. **Als Tabellen materialisieren** – `$materialize` mit `type=table` oder über den ViewDefinition Builder
5. **Mit SQL abfragen** über die Aidbox SQL-Konsole, Grafana oder ein beliebiges BI-Tool

Die vollständige Methodenreferenz und Parameterdetails finden Sie in der [De-Identifikationsdokumentation](https://docs.aidbox.app/modules/sql-on-fhir/de-identification). Um das vollständige Szenario aus diesem Beitrag mit einem einzigen `docker compose up` auszuprobieren, besuchen Sie das [De-Identifikationsbeispiel](https://github.com/Aidbox/examples/tree/main/aidbox-features/de-identification).