为什么此 AES 加密代码始终返回不同的密文



注意:

以下代码示例仅用于演示目的,并实现不安全的方案。如果您正在寻找安全方案,请查看 https://stackoverflow.com/a/10177020/40347

我正在使用AESCryptoServiceProvider类来测试一些加密概念。到目前为止,在所有示例和文章中,它们都会生成一个随机密钥用于加密,然后立即用于解密。当然,它工作正常,因为您在那里使用密钥,但是如果您加密,请保存文本,稍后您想要解密它,您将需要相同的密钥。为此,也相同的IV。

现在,在此代码中,我在多次传递中使用相同的键和 IV,每次运行批处理时,该批处理都会给出相同的结果(如预期的那样)。但是后来我关闭了测试应用程序并重新运行相同的代码而不进行更改,并且对于相同的输入参数,生成的(Base64 编码)密码文本是不同的,为什么?

我从上次运行中"保存"了一个 B64 编码的密码器并将其提供给 TestDecrypt 方法,正如预期的那样,它抛出了一个加密异常,提到了一些关于填充的内容,尽管我确信这与以下事实有关:对于相同的 Key、IV、纯文本和参数,它在应用程序的每次单独运行时都会给出不同的结果。

为了加密,我有这个:

    public string Test(string password, Guid guid, string text)
    {
        const int SaltSize = 16;
        string b64Cryptogram;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);
        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(text);
                    }
                    b64Cryptogram = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            Console.WriteLine("E: {0}", b64Cryptogram);
            aes.Clear();
        }
        return b64Cryptogram;
    }

请注意,我没有使用 RFC2898DeriveBytes,因为它会随机派生一些我不再记得的东西:)加密它的想法正是因为我知道我用来加密它的东西。

解密方法如下所示:

    public void TestDecrypt(string password, Guid guid, string ciphertextB64)
    {
        const int SaltSize = 16;
        byte[] cipher = Convert.FromBase64String(ciphertextB64);
        string plaintext;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);
        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream(cipher))
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader swEncrypt = new StreamReader(csEncrypt))
                    {
                        plaintext = swEncrypt.ReadToEnd();
                    }
                }
            }
            Console.WriteLine("D: {0}", plaintext);
            aes.Clear();
        }
    }

现在,只需将其放入控制台应用程序中并运行它。然后退出并再次运行它,您将看到对于相同的模式、填充、IV、密钥和纯文本数据,每个应用程序运行的输出密码将不同。只要在应用程序的同一运行中重复运行该方法,它们将是相同的。

如果不是很明显,这是我用来测试的控制台代码:

        Guid guid = Guid.NewGuid();
        string plain = "Text to be encrypted 123458970";
        string password = "This is a test of the emergency broadcast system";
        TestDecrypt(password, guid, Test(password, guid, plain));
        TestDecrypt(password, guid, Test(password, guid, plain));
        Test(password, guid, plain);
        Test(password, guid, plain);
        Test(plain, guid, password);
        TestDecrypt(password, guid, "W4Oi0DrKnRpxFwtE0xVbYJwWgcA05/Alk6LrJ5XIPl8=");
    }    

这里的解决方案是从存储或常量Guid中提取。叫

Guid.NewGuid();

每次都会返回不同的结果。从文档中:

这是一个方便的静态方法,可以调用它来获取新的 Guid。该方法包装对 Windows CoCreateGuid 函数的调用。返回的 Guid 保证不等于 Guid.Empty。

或者,在测试时,您可以使用 Guid.Empty,它将返回所有零。

或者,您可以使用其字符串构造函数重载将其存储为这样:

var guid = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e");

最新更新