我一直在为dotnet核心开发一个简单的帮助程序,该程序应该根据用户提供的密码(密钥)和盐对字符串进行编码和解码。在dotnet核心中使用密码和salt对字符串进行编码和解码
与完整的.NET Framework相反,dotnet核心目前没有RijndaelManaged类的实现。几乎所有适用于C#的正确加密/解密示例都基于此类,使其对dotnet核心无用。在CoreFX repo on GitHub上有很多辩论。
然而,随着双方的(旧) MSDN article on AesManaged组合和article by Troy Hunt - 更不用说一些重构 - 我能酿造以下半工作示例:
internal class CryptographyHelpers
{
internal static string Decrypt(string password, string salt, string encrypted_value)
{
string decrypted;
using (var aes = Aes.Create())
{
var keys = GetAesKeyAndIV(password, salt, aes);
aes.Key = keys.Item1;
aes.IV = keys.Item2;
// create a decryptor to perform the stream transform.
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// create the streams used for encryption.
var encrypted_bytes = ToByteArray(encrypted_value);
using (var memory_stream = new MemoryStream(encrypted_bytes))
{
using (var crypto_stream = new CryptoStream(memory_stream, decryptor, CryptoStreamMode.Read))
{
using (var reader = new StreamReader(crypto_stream))
{
decrypted = reader.ReadToEnd();
}
}
}
}
return decrypted;
}
internal static string Encrypt(string password, string salt, string plain_text)
{
string encrypted;
using (var aes = Aes.Create())
{
var keys = GetAesKeyAndIV(password, salt, aes);
aes.Key = keys.Item1;
aes.IV = keys.Item2;
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (var memory_stream = new MemoryStream())
{
using (var crypto_stream = new CryptoStream(memory_stream, encryptor, CryptoStreamMode.Write))
{
using (var writer = new StreamWriter(crypto_stream))
{
writer.Write(plain_text);
}
var encrypted_bytes = memory_stream.ToArray();
encrypted = ToString(encrypted_bytes);
}
}
}
return encrypted;
}
private static byte[] ToByteArray(string input)
{
return Encoding.Unicode.GetBytes(input);
}
private static string ToString(byte[] input)
{
return Encoding.Unicode.GetString(input);
}
private static Tuple<byte[], byte[]> GetAesKeyAndIV(string password, string salt, SymmetricAlgorithm symmetricAlgorithm)
{
const int bits = 8;
var key = new byte[16];
var iv = new byte[16];
var derive_bytes = new Rfc2898DeriveBytes(password, ToByteArray(salt));
key = derive_bytes.GetBytes(symmetricAlgorithm.KeySize/bits);
iv = derive_bytes.GetBytes(symmetricAlgorithm.BlockSize/bits);
return new Tuple<byte[], byte[]>(key, iv);
}
}
我说半工作的例子,这意味着它的工作... 有时。这就是问题所在。当我随意运行我的测试时,大部分时间都会成功。随机,约四分之一的时候,它失败,出现以下消息:
Message: Expected string to be "lorem ipsum dom dolor sit amet", but " �R��k6��o��do�Lr sit amet" differs near ">�R" (index 0).
它看起来像一个统一的问题,但将助手Encoding.UTF8的Encoding.Unicode,导致错误:
System.Security.Cryptography.CryptographicException : The input data is not a complete block.
更改编码Encoding.ASCII导致以下错误:
System.Security.Cryptography.CryptographicException : Specified padding mode is not valid for this algorithm.
我使用的测试是:
[Fact]
public void Decrypt_Should_Decode_An_Encrypted_String()
{
// arrange
var key = Guid.NewGuid().ToString();
var salt = Guid.NewGuid().ToString();
var original_value = "lorem ipsum dom dolor sit amet";
var encrypted_value = CryptographyHelpers.Encrypt(key, salt, original_value);
// act
var target = CryptographyHelpers.Decrypt(key, salt, encrypted_value);
// assert
target.Should().NotBeNullOrEmpty();
target.Should().Be(original_value);
}
所以,我的主要问题是:为什么我的执行(测试)有时会失败?
我绝对不是密码学方面的专家,但在高层面上我意识到一些基本概念。此外,还发布了类似的问题"How to use Rijndael encryption with a .Net Core class library?",但唯一的答案停留在概念层面,缺乏具体的实施。
任何有关这是否足够好的实现的提示和论据也将非常感谢。
谢谢!
如果你来到这个答案后,但是不知道为什么你的块大小错误,尝试与_salt_为使用新Rfc2898DeriveBytes一个字节数组,作为一个int它不会给IV一致的结果。 –