我正在try 在中实现一些数据加密.NET提供的密码.据我所知,我用一个对称密钥加密文件,并用用户生成的另一个密钥加密该密钥.这意味着密码更改不需要更改数据,只需更新加密密钥即可.

在测试AES函数时,我可以加密我的256位密钥,但在解密时,我只能从中获得前16个字节.净值:

public static byte[] Salt = new byte[64];
public static byte[] IV = new byte[16];
public static string Password1 = "PWD";
public static byte[] Key = new byte[32];

static void Main(string[] args)
{
    Salt = RandomNumberGenerator.GetBytes(64);
    IV = RandomNumberGenerator.GetBytes(16);
    Key = RandomNumberGenerator.GetBytes(32);
    var pwdK1 = RandomNumberGenerator.GetBytes(32);

    byte[] aKey1 = new byte[32];
    byte[] bKey1 = new byte[32];

    using (Aes aes = Aes.Create())
    {
        aes.Mode = CipherMode.CBC;
        aes.Key = pwdK1;        //use key generated by user pwd
        aes.IV = IV;

        var str = new MemoryStream(Key);

        using (var crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read))
        {
            int i = crypStr.Read(aKey1, 0, 32);
        }
    }

    using (Aes aes = Aes.Create())
    {
        aes.Mode = CipherMode.CBC;
        aes.Key = pwdK1;        //use key generated by user pwd
        aes.IV = IV;

        var str = new MemoryStream(aKey1);

        using (var crypStr = new CryptoStream(str, aes.CreateDecryptor(), CryptoStreamMode.Read))
        {
            int i = crypStr.Read(bKey1, 0, 32);
            var p = bKey1.ToArray();
        }
    }
    //we should have Key in p/bKey1, but we only have the first 16 bytes of Key.
}

在这里,pwdK1实际上是使用第三方Argon2库生成的,这篇文章修改了代码.

使用的密钥和IV相同,模式相同,但在解密阶段读取解密的密钥时,我只看到存储在变量p中的Key中的前16个字节.对于前crypStr.Read,我返回了完整的32个字节,但解密读取只返回I中的16个字节.其余16个字节都是0.

你知道我做错了什么吗?

推荐答案

代码中有两个错误:

  • 在里面NET 6,Read()的行为发生了变化.新的行为不再保证读取,直到传递的缓冲区被完全填满或到达流的末尾,s.herehere.因此,必须实现读循环(正如第一条注释中所建议的那样).或者,可以使用CopyTo()

    以下是比较中的选项(用于加密).请注意,与发布的代码一样,Key是要加密的明文:

    Read()-loop:

    using MemoryStream str = new MemoryStream(Key);
    using CryptoStream crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read);
    int i = 0;
    while (i < aKey1.Length) 
        i += crypStr.Read(aKey1, i, aKey1.Length - i);
    

    CopyTo():

    using MemoryStream str = new MemoryStream(Key);
    using CryptoStream crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read);
    using MemoryStream cpyStr = new MemoryStream();
    crypStr.CopyTo(cpyStr); 
    aKey1 = cpyStr.ToArray();
    

    TransformFinalBlock():

    aKey1 = aes.CreateEncryptor().TransformFinalBlock(Key, 0, Key.Length); 
    
  • Aes实现在默认情况下应用PKCS#7填充,即如果明文为32字节,则会附加一个完整的块(带Aes的16字节),因此密文的缓冲区需要48字节的大小.在解密过程中,再次移除填充,以便解密数据的缓冲区可能仍然对应于32字节的明文大小

    可以使用aes.Padding = PaddingMode.None;禁用填充.


使用TransformFinalBlock()无填充的完整代码:

byte[] IV = RandomNumberGenerator.GetBytes(16);
byte[] Key = RandomNumberGenerator.GetBytes(32);
byte[] pwdK1 = RandomNumberGenerator.GetBytes(32);

byte[] aKey1 = new byte[32];
byte[] bKey1 = new byte[32];

using (Aes aes = Aes.Create())
{
    aes.Mode = CipherMode.CBC;
    aes.Key = pwdK1;        
    aes.IV = IV;
    aes.Padding = PaddingMode.None; // Fix: Disable padding

    aKey1 = aes.CreateEncryptor().TransformFinalBlock(Key, 0, Key.Length);  // Fix: TransformFinalBlock 
}

using (Aes aes = Aes.Create())
{
    aes.Mode = CipherMode.CBC;
    aes.Key = pwdK1;       
    aes.IV = IV;
    aes.Padding = PaddingMode.None;

    bKey1 = aes.CreateDecryptor().TransformFinalBlock(aKey1, 0, aKey1.Length);
}

Console.WriteLine(Convert.ToHexString(Key));
Console.WriteLine(Convert.ToHexString(aKey1));
Console.WriteLine(Convert.ToHexString(bKey1));

Edit:如 comments 中所述,自.NET 6还有EncryptCbc()DecryptCbc(),这使得它更加紧凑:

using (Aes aes = Aes.Create())
{
    aes.Key = pwdK1;
    aKey1 = aes.EncryptCbc(Key, IV, PaddingMode.None);
}

与发布的代码一样,pwdK1是加密密钥,Key是要加密的明文.

.net相关问答推荐

在 F# 中处理 Option - Some(null) 的好策略是什么

为什么脚本不从 _Layout 加载并且必须添加到部分视图中?

为什么解码后的字节数组与原始字节数组不同?

当 Func 委托需要接口作为参数时,它是如何工作的?

在 Git for Visual Studio 2012 中恢复到以前的提交

无法加载文件或程序集 不支持操作. (来自 HRESULT 的异常:0x80131515)

在 C# 中获取 log4net 日志(log)文件

在 C# 中将字符串转换为 colored颜色

如何根据新的安全策略在 .Net 中发送邮箱?

如何在 WPF 中创建/制作圆角按钮?

Java 和 .NET 技术/框架的类似物

如何使用反射在 .NET 中调用重载方法

无法在 Windows 10 上安装 Windows SDK 7.1

.NET 事件 - 什么是对象发送者和 EventArgs e?

使用 XmlDocument 读取 XML 属性

资源(.resx)文件有什么好处?

我应该如何删除 DbSet 中的所有元素?

如何比较泛型类型的值?

在 IIS 中访问 .svc 文件时出现 HTTP 404

通过反射获取公共静态字段的值