使用 C# 进行 AES 加密

Jon*_*nze 5 c# encryption cryptography aes

我是密码学的新手,为了学习它,我尝试使用AESin进行加密/解密C#

\n\n

遗憾的是我意识到,这并不像我想象的那么容易。\n所以我正在寻找一个更简单的解决方案。

\n\n

后来我发现了一些代码片段,其中包括一些解释。

\n\n

我复制了代码并尝试将其实现到一个小应用程序中。

\n\n
using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace aes\n{\n    class Program\n    {\n\n        public static string passwd = null;\n        public static string content = null;\n        public static string encryptedcontent = null;\n\n        public static byte[] IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };\n        public static int BlockSize = 128;\n\n\n        static void Encrypt()\n        {\n\n            if (passwd == "") return;\n            //Content to Byte Array\n            byte[] bytes = Encoding.Unicode.GetBytes(content);\n            //Encrypt\n            //Init AES\n            SymmetricAlgorithm crypt = Aes.Create();\n            //Init md5 hash\n            HashAlgorithm hash = MD5.Create();\n            //AES blocksize (AES 192 etc.) (min 128)\n            crypt.BlockSize = BlockSize;\n            //Generating Key\n            crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));\n            //Initialize Vectors\n            crypt.IV = IV;\n\n            //CryptoStram is used for encryption\n            //The required Encryptor is based on the algorithm above\n            //Cryptostream sends data of the encrypted byte array to Memorystream\n            //The memory stream is then converted into a Base64 string and made readable\n            using (MemoryStream memoryStream = new MemoryStream())\n            {\n                using (CryptoStream cryptoStream =\n                   new CryptoStream(memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))\n                {\n                    cryptoStream.Write(bytes, 0, bytes.Length);\n                }\n\n                encryptedcontent = Convert.ToBase64String(memoryStream.ToArray());\n            }\n        }\n\n\n        static void Main(string[] args)\n        {\n//Set Password\n            Console.WriteLine("Passwort angeben");\n            Console.Write("> ");\n                 passwd = Console.ReadLine();\n\n//Set content to encrypt (String)\n            Console.WriteLine("Zu verschl\xc3\xbcsselner Text angeben");\n            Console.Write("> ");\n                 content = Console.ReadLine();\n\n\n            Encrypt();\n\n            Console.WriteLine(encryptedcontent);\n\n            Console.ReadLine();\n        }\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n\n

随后我想用一些测试数据尝试该程序。\n我实际上得到了一个看似加密的字符串。

\n\n

密码:supersecretpassword内容:I like to keep my secrets结果:SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==

\n\n

我尝试使用一些在线工具来解密并检查我的结果。\n遗憾的是,大多数网络工具无法解密我的结果。

\n\n

如果我I like to keep my secrets使用在线工具加密该句子,我会得到如下结果:\n7IWuebm0T8HdrGdtkBjt5zgjbdEqYfidNZVvfgtOjH4=

\n\n

我的结果SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==

\n\n

正如您所看到的,两个结果是不同的。\n不幸的是,我不知道为什么会出现这种情况。

\n\n

谢谢你的帮助

\n\n

乔纳斯

\n\n

PS 不知怎的,我删除了这个问题中写的一些行。我希望新词能澄清我的问题所在。

\n

bar*_*njs 8

您没有说明哪些在线工具成功或没有成功复制您的结果,因此这是一个笼统的答案,而不是具体的答案。

//AES blocksize (AES 192 etc.) (min 128)
Run Code Online (Sandbox Code Playgroud)
crypt.BlockSize = BlockSize;
Run Code Online (Sandbox Code Playgroud)

AES 的 BlockSize 是 128。始终(与原始算法 Rijndael 相比,它允许 BlockSize 更改)。

AES-128/AES-192/AES-256 是关于密钥大小,而不是块大小。

crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
Run Code Online (Sandbox Code Playgroud)

您正在使用MD5(UTF16(password))密钥导出函数(KDF)。也许您可以找到使用此功能的在线示例,但他们更有可能使用MD5(UTF8(password))(来自Encoding.UTF8, vs Encoding.Unicode)。更好的答案是使用适当的基于密码的密钥派生函数,例如 PBKDF2(出于Rfc2898DeriveBytes...原因在 .NET 中调用它)。

[当我加密时I like to keep my secrets,我得到的答案是在线工具的两倍。]

您正在加密该字符串的 UTF-16 表示形式。该字符串由 25 个 Unicode 代码点值组成,全部来自 US-ASCII 范围。因此,UTF-16 表示只是码点长度 * 2(50 字节)。

50 个字节分为 3 个 16 字节(128 位)块,加上剩余的 2 个字节。添加填充,变成 4 个 AES-CBC-PKCS#7 输出块(64 字节)。64 字节转换为 Base64 为 21 个完整值(3 个字节 -> 4 个字符),剩余 1 个字节,因此 Base64 值以 2 个=填充字符结尾,总长度为 88 个字符。这符合你的描述,万岁:)。

另一方面,如果您使用 UTF-8 编码,则需要 25 个字节进行加密,这将变成 2 个输出块(32 个字节),这将变成 10 个完整的 Base64 转换,剩余 2 个字节,因此=一次总共 44 个字符...这看起来很像在线工具所使用的。

每次使用相同的密钥加密时,您还应该生成一个新的 IV。IV 不是密钥,但更改 IV 会导致相同的秘密输入以不同的方式加密,因此可以看到您的加密数据的人无法判断您发送了与刚刚发送的同一消息。(至少,这是 CBC 块模式的目的,在其他块模式中有时它有更重要的目的)。IV 可以与消息一起传输……事实上应该如此,除非您有双方都同意的其他方式(无需对其进行硬编码)。

当然,您应该丢弃所有一次性物品。将编码更改为 UTF-8,但不更改 KDF,最好是

private static string Encrypt(string content, string password)
{
    byte[] bytes = Encoding.UTF8.GetBytes(content);

    using (SymmetricAlgorithm crypt = Aes.Create())
    using (HashAlgorithm hash = MD5.Create())
    using (MemoryStream memoryStream = new MemoryStream())
    {
        crypt.Key = hash.ComputeHash(Encoding.UTF8.GetBytes(password));
        // This is really only needed before you call CreateEncryptor the second time,
        // since it starts out random.  But it's here just to show it exists.
        crypt.GenerateIV();
    
        using (CryptoStream cryptoStream = new CryptoStream(
            memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cryptoStream.Write(bytes, 0, bytes.Length);
        }
    
        string base64IV = Convert.ToBase64String(crypt.IV);
        string base64Ciphertext = Convert.ToBase64String(memoryStream.ToArray());

        return base64IV + "!" + base64Ciphertext;
    }
}
Run Code Online (Sandbox Code Playgroud)