gematik lib-vau / lib-vau-csharp
AES-GCM-Nonce-Wiederverwendung in der VAU-Server-Verschlüsselung
Der VAU-Server verwendet den vom Client gelieferten Request-Counter als 64-Bit-Counter-Teil seines eigenen AES-GCM-IV, anstatt einen unabhängigen serverseitigen Verschlüsselungscounter zu führen. Die IV-Eindeutigkeit reduziert sich auf die 4 zufälligen Bytes, die der Server voranstellt, sodass der Server-zu-Client-Kanal nach ~77.000 Antworten mit gleichem Counter dem Joux Forbidden Attack offensteht.
Beschreibung
Die VAU-Protokollspezifikation (gemSpec_Krypt V2.40.0) verlangt in A_24631, dass der Server seinen eigenen unabhängigen 64-Bit-Verschlüsselungscounter für den Application-Data-Key K2_s2c_app_data führt. Der Counter muss bei 0 beginnen und vor jeder Verschlüsselung inkrementiert werden. A_24632 spezifiziert weiter, dass der AES-GCM-IV des Servers als random(4) || server_encryption_counter(8) konstruiert wird, während das Request-Counter-Feld im Antwort-Header separat den vom Client gespeicherten Wert aus A_24630 zurückspiegelt.
Beide Referenzimplementierungen weichen davon ab. VauServerStateMachine hat kein serverseitiges Counter-Feld und überschreibt die Verschlüsselungsmethode nicht. getRequestCounter() auf der Server-Seite gibt clientRequestCounter zurück (den aus der jüngsten Client-Anfrage gespeicherten Wert), und derselbe Wert wird sowohl für das Request-Counter-Feld im Antwort-Header als auch für den AES-GCM-IV verwendet.
In der Java-Referenzimplementierung (lib-vau) verwendet der Server den Counter des Clients für den IV:
lib-vau VauServerStateMachine.java:181-184
@Overrideprotected long getRequestCounter() { return clientRequestCounter;}Der Server speichert den vom Client gelieferten Counter ohne Monotonie- oder Replay-Prüfung:
lib-vau VauServerStateMachine.java:191-194
@Overrideprotected void checkRequestCounter(long reqCtr) { this.clientRequestCounter = reqCtr;}Die IV-Konstruktion verwendet getRequestCounter() für den Counter-Teil:
lib-vau AbstractVauStateMachine.java:112-124
public byte[] encryptVauMessage(byte[] cleartext) { byte versionByte = 2; byte puByte = 0; byte reqByte = getRequestByte(); byte[] reqCtrBytes = ByteBuffer.allocate(8).putLong(getRequestCounter()).array(); byte[] header = unionByteArrays(versionByte, puByte, reqByte, reqCtrBytes, getKeyId());
byte[] a = new byte[4]; new SecureRandom().nextBytes(a);
byte[] iv = unionByteArrays(a, reqCtrBytes);
byte[] ciphertext = encryptWithAesGcm(encryptionVauKey.getAppData(), iv, cleartext, header);Die Client-Seite inkrementiert ihren eigenen Counter korrekt vor jeder Verschlüsselung:
lib-vau VauClientStateMachine.java:169-173
@Overridepublic byte[] encryptVauMessage(byte[] cleartext) { try { requestCounter++; return super.encryptVauMessage(cleartext);Die C#-Referenzimplementierung (lib-vau-csharp) trägt dieselbe Logik und erzeugt denselben Effekt:
lib-vau-csharp VauServerStateMachine.cs:58-70
protected override void CheckRequestCounter(long requestCounter){ clientRequestCounter = requestCounter;}
protected override long GetRequestCounter(){ return clientRequestCounter;}lib-vau-csharp AbstractVauStateMachine.cs:47-61
public virtual byte[] EncryptVauMessage(byte[] plaintext){ byte versionByte = 2; byte puByte = (byte)(isPu ? 1 : 0); byte requestByte = GetRequestByte(); long requestCounter = GetRequestCounter(); byte[] requestCounterBytes = BitConverter.GetBytes(requestCounter).Reverse().ToArray(); byte[][] headerBytes = new byte[][] { new byte[] { versionByte }, new byte[] { puByte }, new byte[] { requestByte }, requestCounterBytes, KeyId }; byte[] header = Arrays.ConcatenateAll(headerBytes);
byte[] random = new byte[4]; new SecureRandom().NextBytes(random);
AesGcm aesGcm = new AesGcm(); aesGcm.initAESForEncryption(random, requestCounter, header, encryptionVauKey);Der IV selbst wird in crypto/AesGcm.cs zusammengesetzt; gematiks eigener Kommentar nennt A_24628:
lib-vau-csharp crypto/AesGcm.cs:81-91
private static byte[] initializeIV(byte[] random, long lCounter){ // A_24628 -> 32 Bit Random + 64 Bit Verschlüsselungszähler if (random?.Length != 4) { throw new ArgumentNullException(nameof(random), "Invalid random value!"); }
byte[] counter = BitConverter.GetBytes(lCounter).Reverse().ToArray(); // A_24629, A_24631 -> 64 Bit encryption counter return random.Concat(counter).ToArray(); // A_24628 -> concat random and counter}Da A_24623 in der aktuellen ePA-Ausbaustufe Replay-Schutz und Sequenzordnung nicht erzwingt (ERP=false, ESO=false), kann ein Client jeden Counter-Wert senden, einschließlich eines bereits verwendeten. Die GCM-Nonce-Eindeutigkeit der Server-Antworten hängt dann nur von den 32 Zufallsbits ab, die der Server voranstellt.
Nach dem Geburtstagsparadoxon wächst die Kollisionswahrscheinlichkeit für das 4-Byte-Zufallspräfix schnell, wenn der Counter-Teil konstant gehalten wird:
Geburtstags-Kollisionswahrscheinlichkeit bei konstantem 64-Bit-Counter
Nachrichten P(IV-Kollision) 100 < 0,01 % 1.000 0,01 % 10.000 1,16 % 50.000 25,23 % 77.163 50,00 % 100.000 68,55 %AES-GCM bietet bei Nonce-Wiederverwendung keine Vertraulichkeits- oder Integritätsgarantien mehr. Der Joux Forbidden Attack (2006) erlaubt einem Angreifer, der zwei unter demselben Schlüssel und IV verschlüsselte Ciphertexte beobachtet, aus den beiden Authentifizierungs-Tags den GHASH-Authentifizierungsschlüssel H zu rekonstruieren und durch XOR der beiden Ciphertexte Klartext-Bytes zurückzugewinnen.
Auswirkung
- Die innere VAU-Schicht schützt Medikationsdaten, Diagnoseberichte, Verordnungshistorien, Dokumenten-Reads und -Writes sowie die Autorisierungstransaktionen, die den Zugriff auf eine Patientenakte steuern.
- Ein legitimer VAU-Client (oder ein Angreifer im Netzwerk, der einen VAU-Handshake abgeschlossen hat) kann den Counter-Teil des Server-IV konstant halten, indem er einen Request-Counter-Wert wiederverwendet, und so lange Server-Antworten sammeln, bis zwei denselben 12-Byte-IV teilen.
- Sobald zwei Antworten denselben IV teilen, rekonstruiert der Joux Forbidden Attack den GHASH-Authentifizierungsschlüssel
H. MitHkann der Angreifer das GCM-Authentifizierungs-Tag für Ciphertexte unter jedem IV fälschen, für den eine Kollision beobachtet wurde, und Server-zu-Client-VAU-Nachrichten in einer Sitzung mit fortlaufender Nonce-Wiederverwendung manipulieren. - Das XOR der beiden kollidierenden Ciphertexte ergibt das XOR der beiden Klartexte und gibt damit Informationen über die durch die innere VAU-AES-GCM-Schicht geschützten ePA-Antwort-Payloads preis.
- Die innere VAU-Schicht ist die Verteidigung gegen einen kompromittierten TLS-Terminator außerhalb der TEE. Bricht diese Schicht, verschwindet die Verteidigung.
- In Kombination mit gematik: VAU-Handshake führt nur 2 von 6 vorgeschriebenen Server-Schlüssel-Prüfungen aus wird der Angriff praktikabel. Ein MITM, der während des Handshakes seine eigenen Schlüssel eingeschleust hat, kann den Client-Request-Counter bei jeder weitergeleiteten Anfrage auf einen festen Wert fixieren, und die Kollisionssammlung wird zur passiven Beobachtung eines Kanals, den der Angreifer bereits kontrolliert.
Abhilfe
Konsumenten, die eine der Bibliotheken produktiv einsetzen, sollten einen unabhängigen serverEncryptionCounter zu VauServerStateMachine (oder dessen C#-Äquivalent) hinzufügen, ihn auf 0 initialisieren und encryptVauMessage() so überschreiben, dass dieser Counter vor der Verschlüsselung inkrementiert wird. Eine reine Überschreibung von getRequestCounter() reicht nicht aus, weil super.encryptVauMessage() den Rückgabewert sowohl für den IV als auch für den Header nutzt; der Server muss encryptVauMessage() vollständig überschreiben und die beiden Counter-Verwendungen trennen. Gemäß A_24632 nutzt der Counter-Teil des IV den eigenen Counter des Servers, und das Request-Counter-Feld im Antwort-Header spiegelt den gespeicherten Client-Wert. Bis das umgesetzt ist, sollte der innere VAU-Server-zu-Client-Kanal so behandelt werden, als biete er Integrität und Vertraulichkeit nur bis zur Größenordnung von 2^32 Nachrichten pro Sitzung.
Checkliste für Betreiber
Prüfen, ob Sie lib-vau / lib-vau-csharp ausliefern.
Wenn Ihr ePA-Client (oder irgendein Produkt, das VAU spricht) lib-vau oder lib-vau-csharp direkt, über einen Fork oder über ein Git-Submodul einbindet, betrifft Sie die Lücke. Die Java-Bibliothek wurde von mindestens einem produktiven Konsumenten unverändert übernommen (med-united/epa4all); prüfen Sie Ihren eigenen Abhängigkeitsbaum, bevor Sie davon ausgehen, nicht betroffen zu sein.
Einen serverseitigen Counter in Ihrem Build hinzufügen.
Überschreiben Sie
encryptVauMessage()des Servers so, dass ein unabhängiger 64-Bit-Counter geführt, vor jeder Verschlüsselung inkrementiert und genau dieser Counter (nicht der Wert des Clients) für den Counter-Teil des IV verwendet wird. Lassen Sie das Request-Counter-Feld im Antwort-Header gemäß A_24632 weiterhin den Client-Wert zurückspiegeln.Sitzungslänge als Sicherheitsparameter behandeln.
Bis ein serverseitiger Counter etabliert ist, rotieren Sie VAU-Sitzungen deutlich unterhalb der 2^32-Nachrichten-Grenze. Die IV-Kollisionswahrscheinlichkeit übersteigt bereits bei rund 10.000 Antworten mit gleichem Counter die 1-Prozent-Schwelle; das ist Ihre operative Marge.
Auf dem Server gesehene Request-Counter-Werte protokollieren.
Auch wenn Sie das Protokollverhalten nicht ändern, ist ein Counter, der sich innerhalb einer Sitzung über viele Antworten hinweg wiederholt oder rückwärts läuft, erkennbar. Erfassen und prüfen Sie ihn; dieselbe Metrik deckt fehlkonfigurierte Clients und aktiven Missbrauch ab.
Bewertung im Detail
Referenzen
So können wir helfen
Wer wir sind
Die Sicherheitsforscher hinter diesem Sicherheitshinweis.

Dr. rer. nat. Simon Weber
Senior Pentester & MedSec-Forscher
Ich evaluiere Ihr SaMD mit derselben branchenprägenden Sicherheitsexpertise, die ich dem BAK MV für die Überarbeitung des B3S-Standards beigetragen habe.
- Promotion über Krankenhaus-Cybersicherheit
- Kritische Schwachstellen in Krankenhaussystemen gefunden
- Alumni der THB MedSec-Forschungsgruppe
- gematik Security Hero

Dipl.-Inf. Volker Schönefeld
Senior Application Security Expert
Als ehemaliger CTO und Entwickler, der zum Pentester wurde, arbeite ich mit Ihrem Team zusammen, um Schwachstellen aufzudecken und Lösungen zu finden, die zu Ihrer Architektur passen.
- 20+ Jahre als CTO, 50+ Mio. App-Downloads
- Architektur und Absicherung großer IoT-Flotten
- Certified Web Exploitation Specialist
- gematik Security Hero
Penetrationstest gesucht?
Machine Spirits ist spezialisiert auf Sicherheitsbewertungen für Medizinprodukte und Gesundheits-IT. Von MDR-Penetrationstests bis C5-Cloud-Compliance helfen wir MedTech-Unternehmen, regulatorische Anforderungen zu erfüllen.
