c# - 在 dotnet core 中使用密码和盐对字符串进行编码和解码

标签 c# encoding aes .net-core decoding

我一直在为 dotnet core 开发一个简单的助手,它应该根据用户提供的密码( key )和盐来编码和解码字符串。

与完整的 .NET Framework 相矛盾,dotnet core 目前没有 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).

它看起来像一个 unicode 问题,但是将帮助程序中的 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?"已发布,但唯一的答案停留在概念层面,缺乏具体的实现。

任何关于这是否是“足够好”实现的提示和论点也将非常感激。

谢谢!

最佳答案

您的问题与密码学无关,而是由这对函数引起的

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);
}

关于c# - 在 dotnet core 中使用密码和盐对字符串进行编码和解码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42459487/

相关文章:

java - 阔叶 UTF-8 编码

Java - 部分修改文件

java - Android到服务器的AES加密/解密

GCM、CCM 或 CBC-MAC 模式下的 AES 密码?

c# - Oracle ODP.net 非托管 12.2 (Oracle.DataAccess.dll) 需要哪些依赖项/dll?

php - 相同字符串的 base64_encode() 值是否不同?

c# - ASP.NET 中的自定义项目符号列表项

json - Go 嵌套 Json Marshall 或编码

c# - 在 C# 中将字符串存储为 UTF8

c# - DirectShow ISampleGrabber.SetCallback 在显示 C# MessageBox 后抛出 InvalidCastException