解密 AES 更改最后一个字母

Decrypt AES changing the last letter

我需要在客户端应用程序中加密一些数据,稍后在服务器应用程序中对其进行验证。 我假设如果消息被解密,那么它来自有效的客户端,因为密钥是创建有效的加密字符串所必需的。

我正在使用来自 MSDN 的 AES 实现 https://docs.microsoft.com/pt-br/dotnet/api/system.security.cryptography.aes?view=netframework-4.8

我选择 AES 是因为在我的测试中它生成了一个短字符串。这对我来说是一个重要的问题。

public static void Main()
    {
        string original = "message to secure";

        using (Aes myAes = Aes.Create())
        {
            myAes.Key = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw=="); 

            byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);

            var encryptedString = Convert.ToBase64String(encrypted);

            string roundtrip = DecryptStringFromBytes_Aes(Convert.FromBase64String(encryptedString), myAes.Key, myAes.IV);

            Console.WriteLine("Encrypted: " + encryptedString); 
            Console.WriteLine("Decrypted: " + roundtrip); 
        }

        Console.ReadKey();
    }

    static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
    { 
        if (plainText == null || plainText.Length <= 0)
            throw new ArgumentNullException("plainText");
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV");
        byte[] encrypted;

        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Key = Key;
            aesAlg.IV = IV;

            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }

        return encrypted;
    }

    static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
    { 
        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");

        string plaintext = null;

        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Key = Key;
            aesAlg.IV = IV;

            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }

但我注意到,如果最后一个字符(等号之前)发生变化,则字符串会被解密,因为没有任何变化。

例如:

HdPAmfHTxkMmj8D3Ve​​lWjH2A8iGm6gnzzPYGNT5NR14= 已生成,我将其更改为 HdPAmfHTxkMmj8D3Ve​​lWjH2A8iGm6gnzzPYGNT5NR15= 并得到了相同的结果。

有人可以指导我如何保证生成的字符串被更改后无法解密吗?

Solomon 的所有评论或多或少都一针见血。

I'm assuming that if the message was decrypted then it was from a valid client, since the key is necessary to create a valid crypted string.

这个基本假设实际上是错误的。在许多情况下(在未经身份验证的操作模式下),即使密文已被修改,解密也可以成功 - 导致明文与最初加密的明文不同。

回想一下,AES 是一种分组密码。它将一个 128 位块转换为另一个 128 位块。唯一的其他变量是使用的密钥和操作(例如加密或解密)。没有机制来检测传入的 128 位块是否自某些先前操作以来已被修改——AES 不知道这一点。它只是一个键控转换功能。

为避免此问题,请使用经过身份验证的操作模式(如 GCM)或使用 HMAC。有关在 C# 中使用 GCM 的示例,请参阅 this repository 中的示例。


关于第二期:

But I noticed that if there is a change in the last caracter (before equals sign) the string is decrypted as nothing was changed.

从技术上讲,没有任何变化 - 这是 "feature"。每个 base64 字符代表原始数据的 6 位。这意味着,除非您的密文长度可以被 8 和 6 整除,否则会有位 "left over"。请参阅下面我们编码 16 位的示例:

Raw                      : { 0x00, 0x01 }
Binary                   : 00000000 00000001

Base64                   : AAE=
Binary (6 Digit Grouping): 000000 000000 000100
Binary (8 Digit Grouping): 00000000 00000001 00
                                             ^^ these bits are irrelevant

本质上,没什么好担心的。