You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# AES加解密结果异常:密钥与IV一致仍无法正确解密

问题描述

我一直绞尽脑汁,想搞清楚为什么解密后的字节数组和流跟未加密的不一致。一开始以为是不小心把IV也加密了,但修改代码确保IV明文写入和读取后,结果还是偏差极大,读取的数据全错。我还怀疑IV还在数据开头干扰ReadBinaryData函数,但事实不是这样。比如解密后得到的文件大小有数百万字节、映射大小数千万,而实际应该是约252k字节、映射大小为5。

保存方法

private bool SaveEncryptedResources(string filePath, string key)
{
    //filePath和key的验证已提前完成
    using (var aes = Aes.Create())
    {
        aes.Key = TransformKey(key);
        aes.Padding = PaddingMode.ISO10126;
        aes.Mode = CipherMode.CBC;
        //根据Etienne的建议移除了密钥大小设置
        aes.GenerateIV();

        byte[] binaryBuffer;

        //必须先写入字节数组,因为WriteBinaryData里有跳转操作
        using (var ms = new MemoryStream())
        {
            using (var bw = new BinaryWriter(ms))
            {
                var startPosition = aes.IV.Length;
                WriteBinaryData(bw, startPosition);
                ms.Seek(0, SeekOrigin.Begin);
                binaryBuffer = ms.ToArray();
            }
        }

        var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        
        using (var fs = new FileStream(filePath, FileMode.OpenOrCreate))
        {
            fs.Write(aes.IV, 0, aes.IV.Length);
            using (var cs = new CryptoStream(fs, encryptor, CryptoStreamMode.Write))
            {
                using (var bw = new BinaryWriter(cs))
                {
                    bw.Write(binaryBuffer);
                    bw.Flush();
                    cs.FlushFinalBlock();
                }
            }
        }
    }

    return true;
}

加载方法

private void LoadEncryptedResources(string filePath, string key)
{
    byte[] binaryBuffer;
    using (var fs = new FileStream(filePath, FileMode.Open))
    {
        using (var aes = Aes.Create())
        {
            aes.Key = TransformKey(key);
            aes.Padding = PaddingMode.ISO10126;
            aes.Mode = CipherMode.CBC;
            //根据Etienne的建议移除了密钥大小设置
            var iv = new byte[aes.IV.Length];
            var numBytesToRead = aes.IV.Length;
            var currentByte = 0;
            //读取IV
            while (numBytesToRead > 0)
            {
                var n = fs.Read(iv, currentByte, numBytesToRead);
                if (n == 0) break;

                currentByte += n;
                numBytesToRead -= n;
            }

            aes.IV = iv;

            using (var cs = new CryptoStream(fs, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read))
            {
                binaryBuffer = new byte[(int)fs.Length];
                
                //Read返回读取的字节数,可用于验证是否将整个文件读入缓冲区
                var bytesRead  = cs.Read(binaryBuffer, 0, (int)fs.Length);
            }
        }
    }
    //将二进制数据加载到类的Stream对象中
    ResourceStream = new MemoryStream(binaryBuffer);
}

读取方法

private void ReadBinaryData(int startPosition = 0)
{
    //IV实际上不在二进制数据中,所以不需要起始位置

    var br = new BinaryReader(ResourceStream);
    var fileSize = br.ReadInt32();
    var mapSize = br.ReadInt32();
    //*** 不再抛出异常,但得到乱码数据,比如"E:\Proj�� \C#\PixelSharper\PixelSharper.Core\CoreConfiguration.cs" ***//
    for (var i = 0; i < mapSize; i++)
    {
        var resourceName = br.ReadString();
        var resourceSize = br.ReadInt32();
        var resourceOffset = br.ReadInt32();
        //用文件数据填充fileMap
        FileMap[resourceName] = new ResourceFile(resourceSize, resourceOffset);
    }
}

密钥转换方法

private static readonly byte[] Salt =
    new byte[] { 2, 3, 16, 125, 21, 232, 4, 189 };

private static byte[] TransformKey(string password, int keyBytes = 32)
{
    //TODO: 可配置化
    const int iterations = 655360;
    var keyGenerator = new Rfc2898DeriveBytes(password, Salt,
                                              iterations, HashAlgorithmName.SHA512);
    //KeyBytes为32*8=256位,适配AES
    return keyGenerator.GetBytes(keyBytes);
}

WriteBinaryData方法

private void WriteBinaryData(BinaryWriter bw, int startPosition = 0)
{
    //BinaryWriter本身知道类型大小,会写入预期的字节数

    var resourceKeys = new Dictionary<string, int>();
    var resourceFileSize = 0;
    bw.Write(resourceFileSize);
    var mapSize = FileMap.Count;
    bw.Write(mapSize);

    //写入文件映射
    foreach (var resource in FileMap)
    {
        //C#的BinaryWriter会给字符串前缀长度
        bw.Write(resource.Key);
        bw.Write(resource.Value.ResourceSize);
        //记录资源偏移的起始位置,后续用来填充实际文件数据的起始位置
        resourceKeys.Add(resource.Key, (int)bw.BaseStream.Position);
        bw.Write(resource.Value.ResourceOffset);
        bw.Flush();
    }

    //将文件数据写入资源文件
    var position = bw.BaseStream.Position;
    var keys = FileMap.Keys.ToList();
    foreach (var resource in keys)
    {
        var resourceFile = FileMap[resource];
        //现在IV的写入和读取方式不需要再关心起始位置
        resourceFile.ResourceOffset = (int)position;
        FileMap[resource] = resourceFile;

        //TODO: 替换为流式文件读取,避免文件过大无法存入内存
        var fileBytes = File.ReadAllBytes(resource);
        bw.Write(fileBytes);
        position = bw.BaseStream.Position;
        bw.Flush();
    }


    //是否要从资源文件总大小中排除IV?
    resourceFileSize = (int)bw.BaseStream.Length;
    
    //将资源文件大小从0更新为当前长度
    bw.Seek(startPosition, SeekOrigin.Begin);
    bw.Write(resourceFileSize);
    bw.Flush();

    //在resourceKeys字典记录的位置更新资源文件偏移,使用FileMap中存储的更新后偏移值
    foreach (var resource in FileMap)
    {
        var offsetPosition = resourceKeys[resource.Key];
        bw.Seek(offsetPosition, SeekOrigin.Begin);
        bw.Write(resource.Value.ResourceOffset);
        bw.Flush();
    }
    
}

内容的提问来源于stack exchange,提问作者Propagating

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:45:09