使用 C# 的 AES 加密
AES Encryption using C#
我是密码学的新手,为了学习它,我尝试 encrypt/decrypt with AES
in C#
.
遗憾的是我意识到,这并不像我想象的那么容易。
所以我一直在寻找一个更简单的解决方案。
后来我找到了一些 code snippets 包括一些解释。
我复制了代码并尝试将其实现到一个小应用程序中。
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==
如你所见,这两个结果是不同的。
不幸的是,我不知道为什么会这样。
谢谢你的帮助
乔纳斯
P.S 不知何故我删除了一些写在这个问题中的行。希望新词能说明我的问题是什么
我看到的一些问题是自定义的 IV
和奇怪的 blocksize
,编辑:与您拥有的在线工具相比,您可能记错了密码值填写ComputeHash函数计算出的密码。
看看这个简单的 MSDN Example
您没有说明哪些在线工具成功复制了您的结果,或者没有成功复制您的结果,因此这是一个笼统的答案,而不是具体的答案。
//AES blocksize (AES 192 etc.) (min 128)
crypt.BlockSize = BlockSize;
AES的BlockSize为128.Always(对比原始算法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
,而不是 Encoding.Unicode
)。更好的答案是使用适当的基于密码的密钥派生函数,例如 PBKDF2(出于...原因,在 .NET 中称为 Rfc2898DeriveBytes
)。
[When I encrypt I like to keep my secrets
I get an answer that is twice as long as online tools.]
您正在加密该字符串的 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;
}
}
我是密码学的新手,为了学习它,我尝试 encrypt/decrypt with AES
in C#
.
遗憾的是我意识到,这并不像我想象的那么容易。 所以我一直在寻找一个更简单的解决方案。
后来我找到了一些 code snippets 包括一些解释。
我复制了代码并尝试将其实现到一个小应用程序中。
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==
如你所见,这两个结果是不同的。 不幸的是,我不知道为什么会这样。
谢谢你的帮助
乔纳斯
P.S 不知何故我删除了一些写在这个问题中的行。希望新词能说明我的问题是什么
我看到的一些问题是自定义的 IV
和奇怪的 blocksize
,编辑:与您拥有的在线工具相比,您可能记错了密码值填写ComputeHash函数计算出的密码。
看看这个简单的 MSDN Example
您没有说明哪些在线工具成功复制了您的结果,或者没有成功复制您的结果,因此这是一个笼统的答案,而不是具体的答案。
//AES blocksize (AES 192 etc.) (min 128)
crypt.BlockSize = BlockSize;
AES的BlockSize为128.Always(对比原始算法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
,而不是 Encoding.Unicode
)。更好的答案是使用适当的基于密码的密钥派生函数,例如 PBKDF2(出于...原因,在 .NET 中称为 Rfc2898DeriveBytes
)。
[When I encrypt
I like to keep my secrets
I get an answer that is twice as long as online tools.]
您正在加密该字符串的 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;
}
}