You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

适配AesCryptoServiceProvider时初始化向量(IV)长度不匹配问题求解

解决AesCryptoServiceProvider初始化向量(IV)长度不匹配问题

这个问题我之前在迁移FIPS合规加密代码时也碰到过,核心原因是AES的块大小固定为128位(16字节),而AesCryptoServiceProvider作为FIPS合规的实现,会严格校验IV的长度必须和块大小一致——之前的RijndaelManaged允许自定义块大小(你代码里定义的BlockSize=128其实是符合要求的,但问题大概率出在IV的生成或读取环节)。

你需要从以下几个方面修改代码:

1. 确保IV严格是16字节(128位)

AES的IV长度必须等于块大小,也就是16字节。检查你生成IV的逻辑:

  • 如果是用Rfc2898DeriveBytes生成密钥和IV,要明确指定获取16字节的IV:
    var deriveBytes = new Rfc2898DeriveBytes(passPhrase, saltBytes, DerivationIterations);
    byte[] keyBytes = deriveBytes.GetBytes(KeySize / 8); // 256位密钥对应32字节
    byte[] ivBytes = deriveBytes.GetBytes(BlockSize / 8); // 128位块大小对应16字节IV
    
  • 绝对不能使用长度不等于16字节的IV,否则会直接抛出长度不匹配的异常。

2. 加密时正确存储IV(解密时提取)

IV不需要保密,但解密时必须和加密时用的IV完全一致。通常的做法是把IV前缀到密文前面,解密时先提取前16字节作为IV,剩下的部分作为真正的密文:

加密方法修正示例

public static string Encrypt(string plainText, string passPhrase)
{
    // 生成随机盐值(16字节)
    byte[] saltBytes = new byte[16];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(saltBytes);
    }

    var deriveBytes = new Rfc2898DeriveBytes(passPhrase, saltBytes, DerivationIterations);
    byte[] keyBytes = deriveBytes.GetBytes(KeySize / 8);
    byte[] ivBytes = deriveBytes.GetBytes(BlockSize / 8); // 确保生成16字节IV

    using (var aes = new AesCryptoServiceProvider())
    {
        aes.Mode = CipherMode.CBC; // 推荐使用CBC模式,ECB安全性极低
        aes.Key = keyBytes;
        aes.IV = ivBytes;

        var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

        using (var ms = new MemoryStream())
        {
            // 先写入盐值和IV,解密时需要读取这两部分
            ms.Write(saltBytes, 0, saltBytes.Length);
            ms.Write(ivBytes, 0, ivBytes.Length);

            using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
            {
                byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
                cs.Write(plainTextBytes, 0, plainTextBytes.Length);
                cs.FlushFinalBlock();
            }

            return Convert.ToBase64String(ms.ToArray());
        }
    }
}

解密方法修正示例

public static string Decrypt(string cipherText, string passPhrase)
{
    byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

    using (var ms = new MemoryStream(cipherTextBytes))
    {
        // 先读取前16字节作为盐值
        byte[] saltBytes = new byte[16];
        ms.Read(saltBytes, 0, saltBytes.Length);

        // 再读取接下来16字节作为IV
        byte[] ivBytes = new byte[16];
        ms.Read(ivBytes, 0, ivBytes.Length);

        var deriveBytes = new Rfc2898DeriveBytes(passPhrase, saltBytes, DerivationIterations);
        byte[] keyBytes = deriveBytes.GetBytes(KeySize / 8);

        using (var aes = new AesCryptoServiceProvider())
        {
            aes.Mode = CipherMode.CBC;
            aes.Key = keyBytes;
            aes.IV = ivBytes;

            var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

            using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
            {
                using (var sr = new StreamReader(cs, Encoding.UTF8))
                {
                    return sr.ReadToEnd();
                }
            }
        }
    }
}

3. 确认块大小设置的合理性

AesCryptoServiceProvider的块大小固定为128位,你代码里定义的BlockSize=128是符合要求的,不需要额外修改(默认就是128),如果之前有设置其他块大小的逻辑,必须删除。

额外注意事项

  • 加密和解密必须使用相同的加密模式(比如都用CBC,禁止混合ECB和CBC)
  • 盐值必须是随机生成的,不能硬编码,否则会大幅降低加密安全性
  • 不要复用IV,每次加密都应该生成新的IV

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

火山引擎 最新活动