在 dotnet 核心中使用密码和盐对字符串进行编码和解码
Encode and decode a string, with password and salt, in dotnet core
我一直在为 dotnet core 开发一个简单的助手,它应该根据用户提供的密码(密钥)和盐对字符串进行编码和解码。
与完整的 .NET Framework 相矛盾的是,dotnet 核心目前没有 RijndaelManaged class 的实现。几乎每个像样的 C# encrypt/decrypt 示例都基于这个 class,因此它对 dotnet 核心毫无用处。 CoreFX repo on GitHub.
上对此有很多争论
然而,结合(旧的) MSDN article on AesManaged and an 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).
看起来像是 unicode 问题,但是将 helpers 中的 Encoding.Unicode 更改为 Encoding.UTF8,导致错误:
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?",但只是停留在概念层面的回答,缺乏具体的实现。
关于这是否是 'good enough' 实现的任何提示和论据也将非常感激。
谢谢!
您的问题与密码学无关,是由这对函数引起的
private static byte[] ToByteArray(string input)
{
return Encoding.Unicode.GetBytes(input);
}
private static string ToString(byte[] input)
{
return Encoding.Unicode.GetString(input);
}
不允许对任意字节数组调用GetString
,否则会导致信息丢失。您需要使用允许任意字节数组的编码,例如 Base64:
private static byte[] ToByteArray(string input)
{
return Convert.FromBase64String(input);
}
private static string ToString(byte[] input)
{
return Convert.ToBase64String(input);
}
我一直在为 dotnet core 开发一个简单的助手,它应该根据用户提供的密码(密钥)和盐对字符串进行编码和解码。
与完整的 .NET Framework 相矛盾的是,dotnet 核心目前没有 RijndaelManaged class 的实现。几乎每个像样的 C# encrypt/decrypt 示例都基于这个 class,因此它对 dotnet 核心毫无用处。 CoreFX repo on GitHub.
上对此有很多争论然而,结合(旧的) MSDN article on AesManaged and an 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).
看起来像是 unicode 问题,但是将 helpers 中的 Encoding.Unicode 更改为 Encoding.UTF8,导致错误:
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?",但只是停留在概念层面的回答,缺乏具体的实现。
关于这是否是 'good enough' 实现的任何提示和论据也将非常感激。
谢谢!
您的问题与密码学无关,是由这对函数引起的
private static byte[] ToByteArray(string input)
{
return Encoding.Unicode.GetBytes(input);
}
private static string ToString(byte[] input)
{
return Encoding.Unicode.GetString(input);
}
不允许对任意字节数组调用GetString
,否则会导致信息丢失。您需要使用允许任意字节数组的编码,例如 Base64:
private static byte[] ToByteArray(string input)
{
return Convert.FromBase64String(input);
}
private static string ToString(byte[] input)
{
return Convert.ToBase64String(input);
}