Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Dark

Collapse
SHARUR
Community Wiki DLMS/COSEM Tools
Search Sign in

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?

Scheduled Pinned Locked Moved General Discussion
dlmscosemhls5gmacsecuritysuite1
9 Posts 5 Posters 57 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
  • A Offline
    A Offline
    alex_turner
    wrote on last edited by
    #1

    [!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 (0x60 AARQ $\rightarrow$ 0x61 AARE) 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-Normal calling Method 1 reply_to_HLS_authentication on Association 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 7A
    

    Meter Error Response:

    00 01 00 01 00 01 00 05 C7 01 81 01 01
    

    Dissecting the response:

    • C7 01: Action-Response-Normal
    • 81: invoke-id-and-priority
    • 01: 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?

    1 Reply Last reply
    0
    • E Offline
      E Offline
      elena_rostova
      wrote on last edited by
      #2

      Alex, your key corruption hypothesis is almost certainly incorrect. In embedded meter firmware, if the AES keys were corrupt, the preceding AARE deciphering 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 7A in 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!

      1 Reply Last reply
      0
      • D Offline
        D Offline
        david_miller
        wrote on last edited by
        #3

        Elena is spot on regarding the Green Book evolution.

        Let's trace the exact normative standard references:

        1. DLMS Green Book 10th Ed Section 9.2.5.3 (Authentication in Security Suite 1):
          When computing the authentication tag for reply_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).
        2. DLMS Blue Book 14th Ed Section 4.5.3 (Association LN Class 15):
          Method 1 reply_to_HLS_authentication accepts a single parameter of type octet-string (A-XDR tag 0x09 0x10), which is the 16-byte GMAC tag computed by the client ($f(S_{\text{to}}C)$).

        3. 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.

        1 Reply Last reply
        0
        • A Offline
          A Offline
          alex_turner
          wrote on last edited by
          #4

          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}$$

          1. 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$
          2. 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)$.

          1 Reply Last reply
          0
          • M Offline
            M Offline
            marcus_vance
            wrote on last edited by
            #5

            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-Normal returned 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 10
            

            Dissecting the meter response:

            • C7 01: Action-Response-Normal
            • 81: invoke_id = 1
            • 00: action-result = success (0)
            • 01: Get-Data-Result choice = data (presence of return value)
            • 09 10: Octet-String of 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?

            1 Reply Last reply
            0
            • E Offline
              E Offline
              elena_rostova
              wrote on last edited by
              #6

              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:

              1. Key: The shared Global Authentication Key ($GAK$).
              2. Security Control Byte ($SC$): 0x10 (Authentication only).
              3. $AAD$ (17 bytes): $\text{SecurityControl (0x10)} \parallel GAK$.
              4. Plaintext ($P$): The 16-byte Client Challenge ($C_{\text{to}}S$) originally generated by HES and sent in AARQ.
              5. 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-title field in the AARE frame (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.
              1 Reply Last reply
              0
              • C Offline
                C Offline
                carlos_mendez
                wrote on last edited by
                #7

                From the field triage and deployment perspective, there is a major practical trap: Cold-Start Invocation Counter Desynchronization.

                Consider this real-world scenario:

                1. 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.
                2. The Head-End System (HES) database still stores the last recorded meter counter, say $IC = 482,105$.
                3. 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 or Security 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.
                1 Reply Last reply
                0
                • D Offline
                  D Offline
                  david_miller
                  wrote on last edited by
                  #8

                  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 00
                  
                  • AC 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 00
                  
                  • AA 12 80 10 ...: responding-authentication-value = $S_{\text{to}}C$ (AA BB CC ... 88 99)

                  Pass 3: Client Action-Request (reply_to_HLS_authentication with 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 5A
                  
                  • 7E 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 10
                  
                  • 9D E4 ... 22 10: Verified Server GMAC Tag $f(C_{\text{to}}S)$ successfully verified by HES.
                  1 Reply Last reply
                  0
                  • A Offline
                    A Offline
                    alex_turner
                    wrote on last edited by
                    #9

                    To provide a definitive, runnable implementation for the community, here is a complete Python script using standard cryptography primitives 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:

                    1. $AAD$ Length: Strictly 17 bytes ($SC \parallel AK$), never include the Challenge in AAD.
                    2. Challenge Role: Pass $S_{\text{to}}C$ / $C_{\text{to}}S$ as the 16-byte authenticated plaintext.
                    3. $IC$ Byte Order: Always big-endian 4-byte unsigned integer in $IV$.
                    4. Dual Verification: Always verify Pass 4 Server Proof using Server System Title before proceeding.
                    1 Reply Last reply
                    0

                    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
                    Reply
                    • Reply as topic
                    Log in to reply
                    • Oldest to Newest
                    • Newest to Oldest
                    • Most Votes


                    • Login

                    • Don't have an account? Register

                    • Login or register to search.
                    Powered by NodeBB Contributors
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • World
                    • Users
                    • Groups