首页 > 解决方案 > 使用 C# 的 AES 加密

问题描述

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

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

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

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

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace aes
{
    class Program
    {

        public static string passwd = null;
        public static string content = null;
        public static string encryptedcontent = null;

        public static byte[] IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
        public static int BlockSize = 128;


        static void Encrypt()
        {

            if (passwd == "") return;
            //Content to Byte Array
            byte[] bytes = Encoding.Unicode.GetBytes(content);
            //Encrypt
            //Init AES
            SymmetricAlgorithm crypt = Aes.Create();
            //Init md5 hash
            HashAlgorithm hash = MD5.Create();
            //AES blocksize (AES 192 etc.) (min 128)
            crypt.BlockSize = BlockSize;
            //Generating Key
            crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
            //Initialize Vectors
            crypt.IV = IV;

            //CryptoStram is used for encryption
            //The required Encryptor is based on the algorithm above
            //Cryptostream sends data of the encrypted byte array to Memorystream
            //The memory stream is then converted into a Base64 string and made readable
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream =
                   new CryptoStream(memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cryptoStream.Write(bytes, 0, bytes.Length);
                }

                encryptedcontent = Convert.ToBase64String(memoryStream.ToArray());
            }
        }


        static void Main(string[] args)
        {
//Set Password
            Console.WriteLine("Passwort angeben");
            Console.Write("> ");
                 passwd = Console.ReadLine();

//Set content to encrypt (String)
            Console.WriteLine("Zu verschlüsselner Text angeben");
            Console.Write("> ");
                 content = Console.ReadLine();


            Encrypt();

            Console.WriteLine(encryptedcontent);

            Console.ReadLine();
        }
    }
}

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

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

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

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

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

如您所见,这两个结果是不同的。不幸的是,我不知道为什么会这样。

谢谢你的帮助

乔纳斯

PS不知何故,我删除了这个问题中写的一些行。我希望新词可以澄清我的问题。

标签: c#encryptioncryptographyaes

解决方案


你没有说什么在线工具成功或没有成功复制你的结果,所以这是一个一般性的答案,而不是具体的。

//AES blocksize (AES 192 etc.) (min 128)
crypt.BlockSize = BlockSize;

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

AES-128/AES-192/AES-256 是关于 KeySize,而不是 BlockSize。

crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));

您正在使用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;
    }
}

推荐阅读