gematik lib-vau / lib-vau-csharp
AES-GCM Nonce Reuse in VAU Server Encryption
lib-vau and lib-vau-csharp are gematik's reference implementations of the VAU protocol — the security layer that protects traffic to Germany's electronic patient record (ePA) backend, in use across the ~73 million statutory-insured citizens enrolled since 2025 unless they have opted out. The VAU server in both reference implementations reuses the client-supplied Request-Counter value as the 64-bit counter portion of its own AES-GCM initialisation vector, instead of maintaining an independent server-side encryption counter as required by gemSpec_Krypt A_24631 and A_24632. IV uniqueness collapses to the 4 random bytes the server prepends, putting the encrypted server-to-client channel within reach of the Joux forbidden attack at ~77,163 same-counter responses. Tracked as MS-LIB-VAU-774212 (Java) and MS-LIB-VAU-CSHARP-60819d (C#).
Description
The VAU protocol specification (gemSpec_Krypt V2.40.0) requires in A_24631 that the server maintain its own independent 64-bit encryption counter for the application data key K2_s2c_app_data. The counter must start at 0 and increment on each encryption. A_24632 then specifies that the server's AES-GCM IV is constructed as random(4) || server_encryption_counter(8), while the response header's Request-Counter field separately echoes the client's stored value from A_24630.
Both reference implementations diverge from this. VauServerStateMachine has no server-side counter field and does not override the encryption method. The server's getRequestCounter() returns clientRequestCounter — the value stored from the client's most recent request — and the same value is used for both the response header's Request-Counter field and the AES-GCM IV.
In the Java reference implementation (lib-vau), the server reuses the client's counter for the IV:
lib-vau VauServerStateMachine.java:181-184
@Overrideprotected long getRequestCounter() { return clientRequestCounter;}Server stores the client-supplied counter with no monotonicity or replay check:
lib-vau VauServerStateMachine.java:191-194
@Overrideprotected void checkRequestCounter(long reqCtr) { this.clientRequestCounter = reqCtr;}IV construction uses getRequestCounter() for the counter portion:
lib-vau AbstractVauStateMachine.java:112-122
public byte[] encryptVauMessage(byte[] cleartext) { 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);}By contrast, the client side correctly increments its own counter before each encryption, illustrating that the missing server-side increment is asymmetric:
lib-vau VauClientStateMachine.java:169-173
@Overridepublic byte[] encryptVauMessage(byte[] cleartext) { try { requestCounter++; return super.encryptVauMessage(cleartext);The C# reference implementation (lib-vau-csharp) carries the same logic and produces the same effect:
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-98
public virtual byte[] EncryptVauMessage(byte[] plaintext){ long requestCounter = GetRequestCounter(); // ... byte[] iv = new byte[12]; // iv[0..4] = random // iv[4..12] = requestCounter (client-supplied)}Because A_24623 explicitly does not enforce replay protection or sequence ordering in the current ePA build stage (ERP=false, ESO=false), a client can send any counter value it likes — including a previously used one. GCM nonce uniqueness for the server's responses then depends on only the 32 random bits the server prepends.
By the birthday paradox, the collision probability for the 4-byte random prefix grows quickly when the counter portion is held constant:
Birthday collision probability for a fixed 64-bit counter
Messages P(IV collision) 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 provides no confidentiality or integrity guarantees on nonce reuse. The Joux forbidden attack (2006) lets an attacker who observes two ciphertexts encrypted under the same key and IV recover the GHASH authentication key H from the two authentication tags, and XOR the two ciphertexts to recover plaintext bytes.
Impact
- In ePA terms, the payloads the inner-VAU layer protects include medication records, diagnostic reports, prescription history, document reads and writes, and the authorization transactions that control access to a patient's record. The two effects below apply to those payloads.
- A legitimate VAU client (or a network attacker who has completed a VAU handshake) can hold the server's IV counter portion constant by replaying a Request-Counter value, and collect server responses until two share the same 12-byte IV.
- Once two responses share the same IV, the Joux forbidden attack recovers the GHASH authentication key
H. WithH, the attacker can forge the GCM authentication tag for ciphertexts under any IV where a collision has been observed, enabling them to tamper with server-to-client VAU messages in a session where nonce reuse is ongoing. - XOR of the two colliding ciphertexts yields the XOR of the two plaintexts, leaking information about ePA response payloads protected by the inner-VAU AES-GCM layer.
- Above the inner-VAU layer there is still TLS to the ePA backend, but the inner-VAU layer is the property that ePA's threat model relies on to defend patient data against compromise of TLS infrastructure that sits outside the TEE boundary. Breaking it removes that defence-in-depth boundary.
- When combined with the missing server authentication (MS-LIB-VAU-eaea8d), the attacker's position is not hypothetical. A MITM who has substituted their own keys during the handshake can pin the client Request-Counter to any fixed value on every relayed request, reducing the birthday collection from an active attack to passive observation on a channel the attacker already controls.
Mitigation
Downstream consumers that ship either library in a production deployment should add an independent serverEncryptionCounter to VauServerStateMachine (or its C# equivalent), initialise it to 0, and override encryptVauMessage() to increment that counter before encryption. A getRequestCounter() override alone is not sufficient — super.encryptVauMessage() uses that return value for both the IV and the header, so the server must fully override encryptVauMessage() to split the two counter uses explicitly. The override needs to use two distinct counter values per A_24632: the server's own counter for the IV's counter portion, and the client's stored value for the response header's Request-Counter field. Until that change is in place, treat the inner-VAU server-to-client channel as offering integrity and confidentiality only on the order of 2^32 messages per session before the GCM guarantees degrade.
Defender's Checklist
Identify whether you ship lib-vau / lib-vau-csharp.
If your ePA client (or any product that speaks VAU) embeds lib-vau or lib-vau-csharp directly, via a fork, or via a git submodule, the gap applies. The Java library has been pulled in unchanged by at least one production-side consumer (med-united/epa4all); audit your own dependency tree before assuming you're not affected.
Add a server-side counter in your build.
Override the server's
encryptVauMessage()to maintain an independent 64-bit counter, increment it before each encryption, and use that counter (not the client's value) for the IV's counter portion. Keep the response header's Request-Counter field echoing the client's value, per A_24632.Treat session length as a security parameter.
Until a server-side counter is in place, rotate VAU sessions well below the 2^32 message boundary. The IV-collision probability passes 1 % around 10,000 same-counter responses; that is your operational margin.
Log Request-Counter values seen on the server.
Even if you do not change the protocol behaviour, a counter that repeats or steps backwards across many responses in a single session is detectable. Capture and review it; the same metric covers misconfigured clients and active abuse.
Severity Reasoning
References
- gemSpec_Krypt V2.40.0 (A_24631, A_24632, A_24623)
- NIST SP 800-38D (AES-GCM)
- Joux (2006) — Authentication Failures in NIST version of GCM
- gematik lib-vau (Java) Repository
- gematik lib-vau-csharp Repository
- Related: Missing VAU Server Authentication (MS-LIB-VAU-eaea8d)
- Related: ePA-VAU Client Security overview
How We Can Help
Who We Are
The security researchers behind this advisory.

Dr. rer. nat. Simon Weber
Senior Pentester & MedSec Researcher
I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.
- PhD on Hospital Cybersecurity
- Critical vulnerabilities found in hospital systems
- Alumni of THB MedSec Research Group
- gematik Security Hero

Dipl.-Inf. Volker Schönefeld
Senior Application Security Expert
As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.
- 20+ years as CTO, 50M+ app downloads
- Architected and secured large-scale IoT fleets
- Certified Web Exploitation Specialist
- gematik Security Hero
Looking for a Penetration Test?
Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.
