首页 > 解决方案 > 如何在 C# 中使用 AES-128-GCM 解密 HEX 字符串

问题描述

我正在尝试从我的卡姆鲁普电能表中检索数据并将其传递给家庭助理。我正在从电能表获取数据,但它是加密的。根据文档,它被加密如下:“使用 AES-GCM-128 的数据传输和密钥传输 AES-128 密钥包装(DLMS/COSEM 套件 0)。

我收到了 HEX 格式的加密密钥和身份验证密钥。但不确定如何使用它。他们只在 Python 中提供了一个非常简短的示例,但我距离将其翻译成 C# 确实还有很长的路要走。

微软在这里有一个使用 AES 解密的示例:AES 解密 我试图以此为基础来解密数据 - 但它不起作用。我只得到一个奇怪的字符串,如“(oƒ¸Ž\u001aãÀ™\0:⫳\u0081)Ù7ÈS\u001bj\u0004OÏÜ.œ\u007f¨...”(缩写但大约 480 个字符长)。

作为密钥和 IV,我传递来自身份验证密钥和加密密钥的字节。不确定这有多正确。

卡姆鲁普提供了一个示例,其中使用身份验证密钥:“AFB3F93E3E7204EDB3C27F96DBD51AE0”和加密密钥“5AD84121D9D20B364B7A11F3C1B5827F”来解密以下文本:

DB 08 4B 41 4D 45 01 A4 DC 52 82 01 D0 30 00 07 88 E1 A0 39 B2 D1 4C 71 2D D4 D8 C8 44 0D 53 68 E4 33 BD 70 B7 36 81 E9 A9 EF FE 38 F1 75 A3 7D E9 CD E6 4E 8F 78 0D 8F 18 B4 3F C0 59 D8 79 02 F3 D7 47 B8 14 BC D0 6A 47 00 68 78 01 BD 5D 06 61 20 54 50 7D 44 E7 66 98 CC 3E 35 CC 9D E6 2C 28 D 4 A6 35 B9 BF C5 6C E1 FE 5A 3A 1E 5E 27 0B 0C 18 1A CF 02 15 1F CC 59 21 34 D0 4F 02 92 B5 A3 53 38 D7 B7 81 BB 1F 2A 7E 40 71 81 5C EE D5 D4 BA C6 AA AF F9 79 EF 96 9D 0D B4 6F 51 E5 E0 FC 00 F5 AD 10 5F BE F9 5F C5 F8 85 46 0B 56 32 55 4A C0 5D 9E B4 F6 5F F2 23 97 2A 47 CF D4 34 B5 F5 E2 D8 53 EA 4C 14 72 75 86 F0 E2 1C 6E E1 25 26 8C B4 DC 7E C5 B1 0F 84 83 C0 10 C8 E6 88 DF 86 58 4D 7C 29 D8 17 31 A4 E0 96 91 41 B6 AD D9 42 E4 0A 96 E3 E2 DC 2F 90 20 BF 9D 58 02 A1 8D C9 85 BB 54 22 F4 70 C0 62 9D 22 DB 6F 16 B6 64 7D B3 C9 F7 27 C1 70 C2 DB 4C9A 23 0F 82 83 46 E8 6F 56 D3 47 B6 2B FE 28 A5 07 AE 2A 21 9A CC 63 AD 5E E0 6E CB 94 9D C4 FD C2 D2 F8 08 02 7A 4A 4C 67 7C 93 C4 C8 90 9D D 73 3 B8 B3 79 18 36 C5 55 5E 74 E2 54 82 A4 4A 5F 6D 35 8D CE 84 50 47 8F 8B 2C 5D 56 66 C2 CF 8B D1 90 D4 87 4E 70 7F 5D 4B CD E9 4E 391 35 5A 8 1C D5 F0 88 99 B6 5E E9 B5 9F C6 03 72 E4 F9 2B C9 98 26 B8 C1 47 F2 09 5F B3 8A 89 14 09 AA 81 E2 27 07 6C 21 CB 7C AF 73 B3 E8 A4 E2 56 C7 0D 95 4 A9 14 F3 9C 16 93 76 BF 92 2B 08 06 4E C4 FB 31 D4 4E CD 72 1D 1A 15 1E 4E 68 4F 0B 26 85 C4 B6 9D 96 F5 FB 52 D0 B8 12 7981 39 C3 E4 1C D5 F0 88 99 B6 5E E9 B5 9F C6 03 72 E4 F9 2B C9 98 26 B8 C1 47 F2 09 5F B3 8A 89 14 09 AA 81 E2 27 07 6C 21 CB 7C AF 73 B3 E8 A4 E2 56 C7 0D 95 47 A9 14 F3 9C 16 93 76 BF 92 2B 08 06 4E C4 FB 31 D4 4E CD 72 1D 1A 15 1E 4E 68 4F 0B 26 85 C4 B6 9D 96 F5 FB 52 D0 B8 12 7981 39 C3 E4 1C D5 F0 88 99 B6 5E E9 B5 9F C6 03 72 E4 F9 2B C9 98 26 B8 C1 47 F2 09 5F B3 8A 89 14 09 AA 81 E2 27 07 6C 21 CB 7C AF 73 B3 E8 A4 E2 56 C7 0D 95 47 A9 14 F3 9C 16 93 76 BF 92 2B 08 06 4E C4 FB 31 D4 4E CD 72 1D 1A 15 1E 4E 68 4F 0B 26 85 C4 B6 9D 96 F5 FB 52 D0 B8 12 79

并得到

0f000000000c07e40107020e2f14ff80000002410a0e4b616d73747275705f563030303109060101010800ff06000d394c09060101020800ff060000000009060101030800ff060000452c09060101040800ff060000000009060101000001ff0601a4dc5209060101010700ff060000000009060101020700ff060000000009060101030700ff060000000009060101040700ff060000000009060001010000ff090c07e40107020e2f14ff80000009060101200700ff1200e009060101340700ff1200df09060101480700ff1200df090601011f0700ff060000000009060101330700ff060000000009060101470700ff060000000009060101150700ff060000000009060101290700ff0600000000090601013d0700ff060000000009060101210700ff12006409060101350700ff12006409060101490700ff120064090601010d0700ff12006409060101160700ff0600000000090601012a0700ff0600000000090601013e0700ff060000000009060101160800ff0600000000090601012a0800ff0600000000090601013e0800ff060000000009060101150800ff06000468a409060101290800ff060004678c090601013d0800ff060004691b

然后他们建议使用“GuruX DLMS Translator”从解密的字符串中获取 XML 数据。

在加密/解密方面,我真的是一个新手,所以真的不确定我在解密时做错了什么。乍一看,它在我看来是输出中的错误编码。但我不知道在哪里更改编码。我花了几个小时现在几乎一无所获。

我还尝试了不同的唯一解密工具,例如“ scadacore.com ”,以了解该过程。但我也无法完成这项工作。

到目前为止用于解密的代码。

static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
    {
        // Check arguments.
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException("cipherText");
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV");

        // Declare the string used to hold
        // the decrypted text.
        string plaintext = null;

        // Create an Aes object
        // with the specified key and IV.
        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Key = Key;
            aesAlg.IV = IV;
            aesAlg.BlockSize = 128;
            aesAlg.Padding = PaddingMode.None;
            //aesAlg.Mode = CipherMode.
            
            // Create a decryptor to perform the stream transform.
            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for decryption.
            using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
                {
                    csDecrypt.Write(cipherText, 0, cipherText.Length);

                    //using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    //{

                    //    // Read the decrypted bytes from the decrypting stream
                    //    // and place them in a string.
                    //    plaintext = srDecrypt.ReadToEnd();
                    //}
                }

                plaintext = System.Text.Encoding.Default.GetString(msDecrypt.ToArray());
                var plainTextLength = plaintext.Length;
            }
        }

        return plaintext;
    }

你们中的任何人都可以指出我正确的方向吗?任何帮助将不胜感激。我去哪儿都快。

谢谢!

编辑。 似乎缺少有关安全标签和随机数的一些信息。这是有关如何将字符串组合在一起的文档。

标签:1 字节 - 系统标题:len + 8 字节 - 长度:x 字节 - 安全标头:1 或 5 字节 - 密文:y 字节 - 身份验证标签:12 字节

尝试1:

以下两种尝试均失败。第一个失败:“计算的身份验证标签与输入的身份验证标签不匹配。” BouncyCastle 解决方案失败并显示:“GCM 中的 mac 检查失败”。

我得到了一些帮助来启动和运行 Python 示例,它完全根据文档解密数据。我已经检查了 Python 代码中的所有值与 C# 中的值。一切都是一样的。这似乎是 auth 标签给出了问题 - 但它在 Python 和 C# 中是相同的,所以我假设它与 C# 代码有关。失败:“aesGcm.Decrypt(nonce, cipherBytes, authTag, plainBytes);”

    namespace AesGcmNetCoreTestApp
{
    internal class Program
    {
        static void Main(string[] args)
    {
        try
        {
            var cipherText = @"DB 08 4B 41 4D 45 01 A4 DC 52 82 01 D0 30 00 07 88 E1 A0 39 B2 D1 4C 71 2D D4 D8 C8 44 0D 53 68 E4 33 BD 70 B7 36 81 E9 A9 EF FE 38 F1 75 A3 7D E9 CD E6 4E 8F 78 0D 8F 18 B4 3F C0 59 D8 79 02 F3 D7 47 B8 14 BC D0 6A 47 00 68 78 01 BD 5D 06 61 20 54 50 7D 44 E7 66 98 CC 3E 35 CC 9D E6 2C 28 4C 0D EE A6 35 B9 BF C5 6C E1 FE 5A 3A 1E 5E 27 0B 0C 18 1A CF 02 15 1F CC 59 21 34 D0 4F 02 92 B5 A3 53 38 D7 B7 81 BB 1F 2A 7E 40 71 81 5C EE D5 D4 BA C6 EF AA AF F9 79 EF 96 9D 0D B4 6F 51 E5 E0 FC 00 F5 AD 10 5F BE F9 5F C5 F8 85 46 0B 56 32 55 4A C0 5D 9E B4 F6 5F F2 23 97 2A 47 CF D4 34 B5 F5 E2 D8 53 EA 4C 14 72 75 86 F0 E2 1C 6E E1 25 26 8C B4 DC 7E C5 B1 0F 84 83 C0 10 C8 E6 88 DF 86 58 4D 7C 29 D8 17 31 A4 E0 96 91 41 B6 AD D9 42 E4 0A 96 E3 E2 DC 2F 90 20 BF 9D 58 02 A1 8D C9 85 BB 54 22 F4 70 C0 62 9D 22 DB 6F 16 B6 64 7D B3 C9 F7 27 C1 70 C2 DB 4C 9A 23 0F 82 83 46 E8 6F 56 D3 47 B6 2B FE 28 A5 07 AE 2A 21 9A CC 63 AD 5E E0 6E CB 94 9D C4 FD C2 D2 F8 08 02 7A 4A 4C 67 7C 93 C4 C8 90 9D 73 36 D8 B8 B3 79 18 36 C5 55 5E 74 E2 54 82 A4 4A 5F 6D 35 8D CE 84 50 47 8F 8B 2C 5D 56 66 C2 CF 8B D1 90 D4 87 4E 70 7F 5D 4B CD E9 4E 91 35 5A 81 39 C3 E4 1C D5 F0 88 99 B6 5E E9 B5 9F C6 03 72 E4 F9 2B C9 98 26 B8 C1 47 F2 09 5F B3 8A 89 14 09 AA 81 E2 27 07 6C 21 CB 7C AF 73 B3 E8 A4 E2 56 C7 0D 95 47 A9 14 F3 9C 16 93 76 BF 92 2B 08 06 4E C4 FB 31 D4 4E CD 72 1D 1A 15 1E 4E 68 4F 0B 26 85 C4 B6 9D 96 F5 FB 52 D0 B8 12 79";
            cipherText = cipherText.Replace(" ", "");
            var cipherTextBytes = ConvertHexStringToByteArray(cipherText);

            var encryptionKey = "5AD84121D9D20B364B7A11F3C1B5827F";
            var encryptionKeyBytes = ConvertHexStringToByteArray(encryptionKey);
            var authenticationKey = "AFB3F93E3E7204EDB3C27F96DBD51AE0";
            var authenticationKeyBytes = ConvertHexStringToByteArray(authenticationKey);
            var peek = ConvertByteArrayToString(encryptionKeyBytes);

            var systemTitle = cipherTextBytes.SelectIndexRange(2, 10);
            var initializationVector = systemTitle.MergeWith(cipherTextBytes.SelectIndexRange(14, 18));
            var additionalAuthenticatedData = cipherTextBytes.SelectIndexRange(13, 14).MergeWith(authenticationKeyBytes);
            var authenticationTag = cipherTextBytes.SelectIndexRange(cipherTextBytes.Length - 12, cipherTextBytes.Length);

            var res = DecryptGcm(cipherTextBytes, encryptionKeyBytes, authenticationTag, initializationVector);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.ReadLine();
        }
    }


    static byte[] DecryptGcm(byte[] cipherBytes, byte[] encKey, byte[] authTag, byte[] nonce)
    {
        byte[] plainBytes = new byte[cipherBytes.Length];

        using (AesGcm aesGcm = new AesGcm(encKey))
        {
            aesGcm.Decrypt(nonce, cipherBytes, authTag, plainBytes);
        }

        return plainBytes;
    }

    public static string ConvertByteArrayToString(byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);
        foreach (byte b in ba)
            hex.AppendFormat("{0:x2}", b);
        return hex.ToString();
    }

    public static byte[] ConvertHexStringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
            .Where(x => x % 2 == 0)
            .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
            .ToArray();
    }



}
public static class ExtensionMethods
{
    public static byte[] SelectIndexRange(this byte[] bytes, int from, int to)
    {
        if (bytes == null) throw new ArgumentNullException("Bytes array cannot be empty.");
        if (bytes.Length == 0) throw new ArgumentException("Bytes array cannot be empty.");

        if (bytes.Length < from) throw new ArgumentException("From index cannot be lower than length!");
        if (bytes.Length < to) throw new ArgumentException("To index cannot be higher than length.");

        var res = bytes.Skip(from).Take(to - from).ToArray();
        return res;
    }

    public static byte[] MergeWith(this byte[] origByte, byte[] mergeWithBytes)
    {
        if (origByte.Length == 0 && mergeWithBytes.Length == 0) return Array.Empty<byte>();

        var totalLength = origByte.Length + mergeWithBytes.Length;

        var resBytes = new byte[totalLength];

        for (int i = 0; i < origByte.Length; i++)
        {
            resBytes[i] = origByte[i];
        }

        for (int i = 0; i < mergeWithBytes.Length; i++)
        {
            var index = i + origByte.Length;
            resBytes[index] = mergeWithBytes[i];
        }

        return resBytes;
    }

}
}

我还尝试了具有相同输入的 BouncyCastle。失败:“cipher.DoFinal(plaintextBytes, offset);”

private static string DecryptWithBouncyCastle(byte[] ciphertext, byte[] nonce, byte[] tag, byte[] key)
    {
        var plaintextBytes = new byte[ciphertext.Length];

        var cipher = new GcmBlockCipher(new AesEngine());
        var parameters = new AeadParameters(new KeyParameter(key), tag.Length * 8, nonce);
        cipher.Init(false, parameters);

        var bcCiphertext = ciphertext.Concat(tag).ToArray();

        var offset = cipher.ProcessBytes(bcCiphertext, 0, bcCiphertext.Length, plaintextBytes, 0);
        cipher.DoFinal(plaintextBytes, offset);

        return Encoding.UTF8.GetString(plaintextBytes);
    }

标签: c#securityencryptionhexaes-gcm

解决方案


没有足够的信息,但我确定您使用了错误的 API。对于 AES-GCM,您应该使用AesGcm类,如下所示:

static byte[] Decrypt(byte[] cipherBytes, byte[] encKey, byte[] authTag, byte[] nonce)
{
    byte[] plainBytes = new byte[cipherBytes.Length];

    using (AesGcm aesGcm = new AesGcm(encKey))
    {
        aesGcm.Decrypt(nonce, cipherBytes, authTag, plainBytes);
    }

    return plainBytes;
}

您没有提供随机数和身份验证标签,因此我无法使用您的示例数据对其进行测试。


推荐阅读