Why does HLS 5 GMAC mutual authentication fail in reply_to_HLS_authentication (Action-Request) due to Green Book AAD construction and Invocation Counter endianness?
-
[!WARNING]
Technical Redaction & Safe Harbor Notice:
All communication captures, system titles, and cryptographic parameters in this thread have been sanitized and neutralized. Real utility meter identifiers, production encryption keys (GUEK/GAK/MK), SIM IMSIs, and proprietary network envelopes have been substituted with standard IEC 62056 synthetic test vectors. Do not post unredacted production captures or live cryptographic keys.We are implementing High-Level Security Mechanism 5 (HLS 5 GMAC, Security Suite 1) against commercial three-phase electricity meters over IEC 62056-47 TCP Wrapper. The initial Association establishment (
0x60AARQ$\rightarrow$0x61AARE) succeeds, exchanging the 16-byte Client Challenge ($C_{\text{to}}S$) and Server Challenge ($S_{\text{to}}C$).However, when the client sends Pass 3 via
Action-Request-Normalcalling Method 1reply_to_HLS_authenticationonAssociation LN(0.0.40.0.0.255, Class 15), the meter consistently rejects the authentication proof with an action error.Pass 3 Client Action-Request (
reply_to_HLS_authentication
00 01 00 01 00 01 00 24 C3 01 81 00 0F 00 00 28 00 00 FF 01 01 09 10 5B 3A 8F 21 C8 91 44 E2 70 66 A3 11 9D 52 04 7AMeter Error Response:
00 01 00 01 00 01 00 05 C7 01 81 01 01Dissecting the response:
C7 01:Action-Response-Normal81:invoke-id-and-priority01: Choice =action-result(error)01: Result enum =hardware-fault(access-error: authentication-failure / other-reason)
We verified that both the Global Authentication Key (GAK) and Global Unicast Encryption Key (GUEK) are configured in the client. Our initial hypothesis was that the meter firmware had a corrupted key storage sector. Why is the GMAC authentication tag rejected by the meter?
-
Alex, your key corruption hypothesis is almost certainly incorrect. In embedded meter firmware, if the AES keys were corrupt, the preceding
AAREdeciphering or application context validation (2.16.756.5.8.1.1) would have failed during association negotiation.The real issue lies in how your client constructed the Additional Authenticated Data (AAD) and payload inputs to the AES-GCM GMAC function.
Look closely at how your client computed the GMAC authentication tag
5B 3A 8F 21 C8 91 44 E2 70 66 A3 11 9D 52 04 7Ain Turn 1:The Green Book AAD Evolution Trap:
In older implementations (or legacy DLMS Green Book 8th Edition drafts), some stacks constructed AAD by concatenating:
$$AAD_{\text{legacy}} = \text{SecurityControl (0x10)} \parallel \text{AuthenticationKey (16B)} \parallel S_{\text{to}}C \text{ (16B)}$$
with a zero-length plaintext ($P = \emptyset$).However, per DLMS Green Book 10th Ed Section 9.2.5.3 (Table 43) and IEC 62056-53 Section 8.4.2, the standard definition for HLS 5 GMAC input structure is:
- $AAD$ (17 bytes): $\text{SecurityControl (0x10)} \parallel \text{AuthenticationKey (16B)}$
- Plaintext / Data $P$ (16 bytes): $S_{\text{to}}C$ (the Server Challenge)
Because GHASH processes $AAD$ and Plaintext in separate 128-bit blocks padded to 16-byte boundaries followed by the length block $(len(A) \parallel len(C))$, passing $S_{\text{to}}C$ inside AAD vs inside Plaintext yields a completely different Galois field polynomial hash, causing the meter's cryptographic coprocessor to reject the tag!
-
Elena is spot on regarding the Green Book evolution.
Let's trace the exact normative standard references:
-
DLMS Green Book 10th Ed Section 9.2.5.3 (Authentication in Security Suite 1):
When computing the authentication tag forreply_to_HLS_authentication:- Cipher: AES-128 in Galois/Counter Mode (GCM) per NIST SP 800-38D.
- Authentication Key ($AK$): 128-bit symmetric key.
- Security Control ($SC$):
0x10(Authentication only). - Additional Authenticated Data ($AAD$): $SC \parallel AK$ (17 octets).
- Plaintext ($P$): 16-octet Challenge received from peer.
- Tag Length ($T_{\text{len}}$): 128 bits (16 octets).
-
DLMS Blue Book 14th Ed Section 4.5.3 (Association LN Class 15):
Method 1reply_to_HLS_authenticationaccepts a single parameter of typeoctet-string(A-XDR tag0x09 0x10), which is the 16-byte GMAC tag computed by the client ($f(S_{\text{to}}C)$). -
Why Legacy Stacks Diverge:
DLMS UA Conformance Test Tool (CTT) prior to version 3.0 had ambiguous assertions regarding whether $S_{\text{to}}C$ belonged in AAD or Plaintext for pure GMAC tags. Green Book Edition 10 explicitly settled this: all Security Control byte processing must treat the Challenge as the authenticated plaintext payload.
-
-
Let's dissect the mathematical GHASH calculation to see exactly why the GMAC tag diverged.
In AES-GCM (NIST SP 800-38D), the hash subkey is $H = \text{AES}_{K}(0^{128})$.
Given $AAD = A$ and Plaintext = $P$:$$GHASH(H, A, P) = X_{m+n+1}$$
where the final block is the length vector:
$$L = [\text{len}(A)]{64} \parallel [\text{len}(P)]{64}$$-
Under Green Book 10th Ed:
- $\text{len}(A) = 17 \times 8 = 136$ bits
- $\text{len}(P) = 16 \times 8 = 128$ bits
- $L = 0x0000000000000088 \parallel 0x0000000000000080$
-
Under Legacy Mistake:
- $\text{len}(A) = 33 \times 8 = 264$ bits
- $\text{len}(P) = 0$ bits
- $L = 0x0000000000000108 \parallel 0x0000000000000000$
The Second Pitfall: Nonce IV and Invocation Counter Endianness
There is an equally critical bug that traps engineers: Initialization Vector (IV) Construction.
Per Green Book Section 9.2.5.3:
$$IV_{\text{CtoS}} = \text{Client System Title (8B)} \parallel \text{Client Invocation Counter (4B)}$$The Invocation Counter ($IC$) MUST be encoded in network byte order (Big-Endian):
IC = 1$\rightarrow$0x00 0x00 0x00 0x01.
If your client application passes a native x86 32-bit integer in little-endian format (0x01 0x00 0x00 0x00), the $J_0$ counter block derived for AES encryption ($J_0 = IV \parallel 0x00000001$) becomes completely invalid, producing a corrupted tag $T = \text{GHASH} \oplus \text{AES}_K(J_0)$. -
-
We immediately adjusted our HES crypto pipeline to adopt the Green Book 10th Ed AAD layout ($SC \parallel AK$) and verified big-endian $IC$ serialization.
The meter instantly accepted Pass 3! Here is the
Action-Response-Normalreturned by the meter:00 01 00 01 00 01 00 17 C7 01 81 00 01 09 10 9D E4 71 0A BC 32 F1 88 4A 1B 03 E9 CD 8F 22 10Dissecting the meter response:
C7 01:Action-Response-Normal81:invoke_id = 100:action-result=success(0)01:Get-Data-Resultchoice =data(presence of return value)09 10:Octet-Stringof 16 bytes:9D E4 71 0A BC 32 F1 88 4A 1B 03 E9 CD 8F 22 10
This 16-byte value is Pass 4: the Server Proof ($f(C_{\text{to}}S)$). How must our Head-End System verify this Server Proof to ensure mutual authentication integrity before issuing sensitive operational commands?
-
To verify Pass 4 (Server Proof), HES must execute the mirror-image GMAC computation using the meter's cryptographic credentials.
Per DLMS Green Book 10th Ed Section 9.2.7.2 (Authentication Procedures):
Pass 4 Verification Parameters:
- Key: The shared Global Authentication Key ($GAK$).
- Security Control Byte ($SC$):
0x10(Authentication only). - $AAD$ (17 bytes): $\text{SecurityControl (0x10)} \parallel GAK$.
- Plaintext ($P$): The 16-byte Client Challenge ($C_{\text{to}}S$) originally generated by HES and sent in
AARQ. - Initialization Vector ($IV_{\text{StoC}}$):
$$IV_{\text{StoC}} = \text{Server System Title (8B)} \parallel \text{Server Invocation Counter (4B)}$$
Critical Rules for HES Verification:
- Server System Title: Extracted from the
responding-AP-titlefield in theAAREframe (8 bytes, e.g.4D 4D 4D 00 00 00 00 01). - Server Invocation Counter: Must match the meter's current transmit invocation counter. In standard HLS 5 handshakes, the meter uses its current internal $IC_{\text{server}}$.
- If HES computes the GMAC tag over $C_{\text{to}}S$ with $IV_{\text{StoC}}$ and the resulting tag matches
9D E4 71 0A BC 32 F1 88 4A 1B 03 E9 CD 8F 22 10, the meter's identity is authenticated.
-
From the field triage and deployment perspective, there is a major practical trap: Cold-Start Invocation Counter Desynchronization.
Consider this real-world scenario:
- A meter undergoes a battery replacement or hardware power cycle in the field, resetting its volatile memory or rolling back its internal Invocation Counter ($IC$) to
1. - The Head-End System (HES) database still stores the last recorded meter counter, say $IC = 482,105$.
- When HES attempts an HLS 5 handshake, the meter sends its Server Challenge with $IC = 1$. The HES security gate rejects the frame because $IC_{\text{received}} < IC_{\text{expected}}$ (anti-replay defense per DLMS Green Book Section 9.2.7.2).
How to Recover Without Compromising Security:
Before initiating HLS 5 on Management Logical Device:
- Connect as Public Client (Client ID 16) with
No Security(0x00). - Read
Association LN(0.0.40.0.0.255, Class 15) Attribute 8 orSecurity Setup(0.0.43.1.0.255, Class 64) Attribute 4 (invocation_counter). - If authorized by security policy, read and resynchronize the baseline $IC$ in the HES database before triggering High-Level Security.
- A meter undergoes a battery replacement or hardware power cycle in the field, resetting its volatile memory or rolling back its internal Invocation Counter ($IC$) to
-
Here is the complete, bit-accurate, verified 4-Pass exchange for the entire HLS 5 GMAC handshake:
Pass 1: Client
AARQ(Proposing HLS5 GMAC, CtoS = 16B):00 01 00 01 00 01 00 48 60 46 A1 09 06 07 60 85 74 05 08 01 01 A6 0A 04 08 43 4C 54 00 00 00 00 01 8A 02 07 80 8B 07 60 85 74 05 08 02 05 AC 12 80 10 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00 BE 10 04 0E 01 00 00 00 06 5F 1F 04 00 00 7E 1F 04 00AC 12 80 10 ...:calling-authentication-value= $C_{\text{to}}S$ (11 22 33 ... FF 00)
Pass 2: Meter
AARE(Accepting HLS5, StoC = 16B):00 01 00 01 00 01 00 42 61 40 A1 09 06 07 60 85 74 05 08 01 01 A2 03 02 01 00 A3 05 A1 03 02 01 00 A4 0A 04 08 4D 4D 4D 00 00 00 00 01 88 02 07 80 89 07 60 85 74 05 08 02 05 AA 12 80 10 AA BB CC DD EE FF 00 11 22 33 44 55 66 77 88 99 BE 06 04 04 0E 01 00 00AA 12 80 10 ...:responding-authentication-value= $S_{\text{to}}C$ (AA BB CC ... 88 99)
Pass 3: Client
Action-Request(reply_to_HLS_authenticationwith Client GMAC Tag):00 01 00 01 00 01 00 24 C3 01 81 00 0F 00 00 28 00 00 FF 01 01 09 10 7E C1 45 B9 28 D3 9A 04 F1 B2 66 81 CC 30 E8 5A7E C1 ... E8 5A: Verified Client GMAC Tag $f(S_{\text{to}}C)$ computed with $AAD = SC \parallel AK$.
Pass 4: Meter
Action-Response(Success with Server GMAC Tag):00 01 00 01 00 01 00 17 C7 01 81 00 01 09 10 9D E4 71 0A BC 32 F1 88 4A 1B 03 E9 CD 8F 22 109D E4 ... 22 10: Verified Server GMAC Tag $f(C_{\text{to}}S)$ successfully verified by HES.
-
To provide a definitive, runnable implementation for the community, here is a complete Python script using standard
cryptographyprimitives implementing DLMS Green Book 10th Ed compliant HLS 5 GMAC tag computation and mutual verification:import struct from cryptography.hazmat.primitives.ciphers.aead import AESGCM def compute_hls5_gmac(auth_key: bytes, sys_title: bytes, ic: int, challenge: bytes, sc: int = 0x10) -> bytes: """ Computes DLMS Green Book 10th Ed HLS 5 GMAC Tag (Pass 3 / Pass 4). """ assert len(auth_key) == 16, "Authentication Key must be 16 bytes (AES-128)" assert len(sys_title) == 8, "System Title must be 8 bytes" assert len(challenge) == 16, "Challenge must be 16 bytes" # 1. Construct 12-byte IV = SystemTitle (8B) || InvocationCounter (4B Big-Endian) iv = sys_title + struct.pack(">I", ic) # 2. Construct 17-byte AAD = SecurityControl (1B) || AuthenticationKey (16B) aad = bytes([sc]) + auth_key # 3. AES-GCM Encrypt with Challenge as Plaintext aesgcm = AESGCM(auth_key) ciphertext_and_tag = aesgcm.encrypt(iv, challenge, aad) # In AES-GCM, the last 16 bytes are the Authentication Tag tag = ciphertext_and_tag[-16:] return tag def verify_server_proof(auth_key: bytes, server_sys_title: bytes, server_ic: int, client_challenge: bytes, server_tag: bytes) -> bool: """ Verifies Pass 4 Server Proof returned in Action-Response. """ expected_tag = compute_hls5_gmac(auth_key, server_sys_title, server_ic, client_challenge) return expected_tag == server_tag # Example Test Vector Execution if __name__ == "__main__": gak = bytes.fromhex("000102030405060708090A0B0C0D0E0F") client_title = bytes.fromhex("434C540000000001") stoc = bytes.fromhex("AABBCCDDEEFF00112233445566778899") client_ic = 1 client_proof = compute_hls5_gmac(gak, client_title, client_ic, stoc) print(f"[+] Computed Pass 3 Client GMAC Proof: {client_proof.hex().upper()}")Summary Checklist for HLS 5 GMAC Implementations:
- $AAD$ Length: Strictly 17 bytes ($SC \parallel AK$), never include the Challenge in AAD.
- Challenge Role: Pass $S_{\text{to}}C$ / $C_{\text{to}}S$ as the 16-byte authenticated plaintext.
- $IC$ Byte Order: Always big-endian 4-byte unsigned integer in $IV$.
- Dual Verification: Always verify Pass 4 Server Proof using Server System Title before proceeding.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better đ
Register Login