根据我的问题Aes Encryption... missing an important piece,我现在已经了解到我在字符串上创建可逆加密的假设有点过时了。我现在有
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
这会产生一致的结果;但是,如果不知道/设置初始化向量,我将无法解密。我真的不想将三个值传递给这个方法(在IV上),这让我硬编码IV或从密钥中导出它。我想知道这是不是一个好的做法,或者它是否会使加密的值容易受到某种程度的攻击......或者我是否真的过度思考这个并且应该只对硬盘进行硬编码?
更新 根据铱星的建议,我尝试了类似的东西:
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
if (string.IsNullOrEmpty(toEncrypt)) throw new ArgumentException("toEncrypt");
if (encryptionKey == null || encryptionKey.Length == 0) throw new ArgumentException("encryptionKey");
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
ms.Write(provider.IV, 0, 16);
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var ms = new MemoryStream(encryptedString))
{
byte[] buffer;
ms.Read(buffer, 0, 16);
provider.IV = buffer;
using (var decryptor = provider.CreateDecryptor(provider.Key, provider.IV))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
byte[] decrypted = new byte[encryptedString.Length];
var byteCount = cs.Read(decrypted, 0, encryptedString.Length);
return Encoding.UTF8.GetString(decrypted, 0, byteCount);
}
}
}
}
}
然而,这在我的单元测试中显示出一些奇怪的东西:
[TestMethod]
public void EncryptionClosedLoopTest()
{
var roundtrip = "This is the data I am encrypting. There are many like it but this is my encryption.";
var encrypted = Encryption.EncryptString(roundtrip, encryptionKey);
var decrypted = Encryption.DecryptString(encrypted, encryptionKey);
Assert.IsTrue(roundtrip == decrypted);
}
我的解密文本显示为“92ʪ F” ,hpv0 我正在加密。有许多人喜欢它,但这是我的加密。“似乎几乎是正确但当然完全错误。看起来我很接近。我是否错过了内存流上的偏移?
答案 0 :(得分:44)
对于每次加密方法的运行,IV应该是随机且唯一的。从密钥/消息中导出或硬编码它是不够安全的。 IV可以在此方法中生成,而不是传递给它,并在加密数据之前写入输出流。
解密时,可以在加密数据之前从输入读取IV。
答案 1 :(得分:11)
加密时,生成您的IV并将其预先粘贴到密文(如下所示)
using (var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
var input = Encoding.UTF8.GetBytes(originalPayload);
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
//Prepend IV to data
//tBinaryWriter.Write(iv); This is the original broken code, it encrypts the iv
cipherStream.Write(iv); //Write iv to the plain stream (not tested though)
tBinaryWriter.Write(input);
tCryptoStream.FlushFinalBlock();
}
string encryptedPayload = Convert.ToBase64String(cipherStream.ToArray());
}
}
解密后,获取前16个字节,并在加密流
中使用它var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
//get first 16 bytes of IV and use it to decrypt
var iv = new byte[16];
Array.Copy(input, 0, iv, 0, iv.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(cs))
{
//Decrypt Cipher Text from Message
binaryWriter.Write(
input,
iv.Length,
input.Length - iv.Length
);
}
return Encoding.Default.GetString(ms.ToArray());
}
答案 2 :(得分:5)
我修改了你的解密方法,如下所示:
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
您的实现问题是您在CryptoStream
中读取了太多字节。你真的需要阅读encryptedText.Length - 16
。使用StreamReader
可以简化这一过程,因为您不再需要担心任何地方的偏移。
答案 3 :(得分:2)
为了解决提供者的IV设置(如Iridium指出的那样):
ms.Read(provider.IV, 0, 16);
我在您的代码中添加了以下内容:
var iv = new byte[provider.IV.Length];
memoryStream.Read(iv, 0, provider.IV.Length);
using (var decryptor = provider.CreateDecryptor(key, iv);
已授予,每次运行时,提供程序都不会设置我的密钥。我生成了一次然后存储它。对于每次加密,IV都是从提供者处随机生成的。
答案 4 :(得分:1)
可接受的答案是正确的,但没有提供如何获得随机IV的良好示例。
事实证明,这比人们试图做到的要容易 很多 。 NET中的AesCryptoServiceProvider每次构造一个IV时都会自动生成一个加密随机IV。而且,如果您需要对多个加密使用同一实例,则可以调用GenerateIV()
您还可以在返回值之前将IV放在加密值之前,并让解密端将其拉出
private static void Main(string[] args) {
var rnd = new Random();
var key = new byte[32]; // For this example, I'll use a random 32-byte key.
rnd.NextBytes(key);
var message = "This is a test";
// Looping to encrypt the same thing twice just to show that the IV changes.
for (var i = 0; i < 2; ++i) {
var encrypted = EncryptString(message, key);
Console.WriteLine(encrypted);
Console.WriteLine(DecryptString(encrypted, key));
}
}
public static string EncryptString(string message, byte[] key) {
var aes = new AesCryptoServiceProvider();
var iv = aes.IV;
using (var memStream = new System.IO.MemoryStream()) {
memStream.Write(iv, 0, iv.Length); // Add the IV to the first 16 bytes of the encrypted value
using (var cryptStream = new CryptoStream(memStream, aes.CreateEncryptor(key, aes.IV), CryptoStreamMode.Write)) {
using (var writer = new System.IO.StreamWriter(cryptStream)) {
writer.Write(message);
}
}
var buf = memStream.ToArray();
return Convert.ToBase64String(buf, 0, buf.Length);
}
}
public static string DecryptString(string encryptedValue, byte[] key) {
var bytes = Convert.FromBase64String(encryptedValue);
var aes = new AesCryptoServiceProvider();
using (var memStream = new System.IO.MemoryStream(bytes)) {
var iv = new byte[16];
memStream.Read(iv, 0, 16); // Pull the IV from the first 16 bytes of the encrypted value
using (var cryptStream = new CryptoStream(memStream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) {
using (var reader = new System.IO.StreamReader(cryptStream)) {
return reader.ReadToEnd();
}
}
}
}
[编辑:我修改了答案,包括如何在加密值中传递IV并在解密时获取它。我还对示例进行了重构]
答案 5 :(得分:1)
乡亲们的宝贵意见。我从ankurpatel和Konstantin那里得到了答案,并进行了清理,并添加了一些方便的方法替代。自2019年6月起在.NET Core 2.2中有效。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
private const int AesKeySize = 16;
public static void Main()
{
// the data to encrypt
var message = "Here is some data to encrypt!";
// create KeySize character key
var key = "g(KMDu(EEw63.*V`";
// encrypt the string to a string
var encrypted = AesEncrypt(message, key);
// decrypt the string to a string.
var decrypted = AesDecrypt(encrypted, key);
// display the original data and the decrypted data
Console.WriteLine($"Original: text: {encrypted}");
Console.WriteLine($"Round Trip: text: {decrypted}");
}
static string AesEncrypt(string data, string key)
{
return AesEncrypt(data, Encoding.Default.GetBytes(key));
}
static string AesDecrypt(string data, string key)
{
return AesDecrypt(data, Encoding.Default.GetBytes(key));
}
static string AesEncrypt(string data, byte[] key)
{
return Convert.ToBase64String(AesEncrypt(Encoding.Default.GetBytes(data), key));
}
static string AesDecrypt(string data, byte[] key)
{
return Encoding.Default.GetString(AesDecrypt(Convert.FromBase64String(data), key));
}
static byte[] AesEncrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
// prepend IV to data
cipherStream.Write(iv);
tBinaryWriter.Write(data);
tCryptoStream.FlushFinalBlock();
}
var cipherBytes = cipherStream.ToArray();
return cipherBytes;
}
}
}
static byte[] AesDecrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
// get first KeySize bytes of IV and use it to decrypt
var iv = new byte[AesKeySize];
Array.Copy(data, 0, iv, 0, iv.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(cs))
{
// decrypt cipher text from data, starting just past the IV
binaryWriter.Write(
data,
iv.Length,
data.Length - iv.Length
);
}
var dataBytes = ms.ToArray();
return dataBytes;
}
}
}
答案 6 :(得分:-1)
在我的情况下,为了生成IV,我使用类似这样的东西
/// <summary>
/// Derives password bytes
/// </summary>
/// <param name="Password">password</param>
/// <returns>derived bytes</returns>
private Rfc2898DeriveBytes DerivePass(string Password)
{
byte[] hash = CalcHash(Password);
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, hash, _KEY_ITER);
return pdb;
}
/// <summary>
/// calculates the hash of the given string
/// </summary>
/// <param name="buffer">string to hash</param>
/// <returns>hash value (byte array)</returns>
private byte[] CalcHash(string buffer)
{
RIPEMD160 hasher = RIPEMD160.Create();
byte[] data = Encoding.UTF8.GetBytes(buffer);
return hasher.ComputeHash(data);
}
也就是说,我使用RIPEMD160计算密码哈希并使用它来生成派生字节,此时,当初始化加密/解密时我只是使用这样的东西
Rfc2898DeriveBytes pdb = DerivePass(Password);
SymmetricAlgorithm alg = _engine;
alg.Key = pdb.GetBytes(_keySize);
alg.IV = pdb.GetBytes(_IVSize);
我不知道它是否“正确”(可能这里的加密大师会向我开枪:D),但是,至少,它给了我一个体面的IV,我不必将它存储在“某处”只需输入正确的密码即可返回所需的IV值;作为注释,上面示例中的_engine被声明为“SymmetricAlgorithm”并使用类似的东西进行初始化
_engine = Rijndael.Create();
_keySize = (_engine.KeySize / 8);
_IVSize = (_engine.BlockSize / 8);
创建所需的加密对象并初始化键和IV大小
答案 7 :(得分:-1)
要生成随机IV,您需要一个真正随机的数字。无论您使用哪种语言特定的API来生成随机数,都应该生成真正的随机数。 android和ios都有api,可根据传感器数据生成随机数。
我最近使用随机IV(使用真正的随机数生成)和哈希密钥实现了AES 256。对于更安全(随机IV +哈希键)跨平台(android,ios,c#)的AES实现,请参阅我的答案 - https://stackoverflow.com/a/24561148/2480840