使用AesCryptoServiceProvider获取错误的解密值

3
我有以下使用AesCryptoServiceProvider进行加密和解密的代码。用于加密和解密的ivkey是相同的。但是,解密后的值与原始字符串不同。
  1. 需要进行哪些更正才能在解密后获得原始值?
  2. inputString =“Test”时,此代码仅在inputValue = valid128BitString时有效。我收到以下异常Padding is invalid and cannot be removed.。我们如何进行更正?

更新的问题

根据@jbtule答案,以下内容将解决问题。

encyptedValue.IV = result.IV;

加密结果中的IV值会发生变化。假设加密是在单独的进程中完成的,我们如何知道解密时所需的IV呢?有没有一种方法可以使其恒定或已知?

答案:您的另一个选项是将IV传递到加密中,在开始加密转换之前分配它,而不是让aesProvider为您生成随机IV。- @Scott Chamberlain

 aesProvider.IV = Convert.FromBase64String("4uy34C9sqOC9rbV4GD8jrA==");

更新:请参考如何为Base64应用填充。我们可以使用UTF8来编码源输入和结果输出。密钥和IV可以保留在Base64中。
使用Base64作为源输入会导致一些值出现问题,例如“ MyTest”,其中字符串长度不是4的倍数。 相关要点: 要解密使用SymmetricAlgorithm类之一加密的数据,您必须将Key属性和IV属性设置为加密时使用的相同值。
SymmetricAlgorithm.IV属性:来自前一个块的信息被混入到加密下一个块的过程中。因此,两个相同的纯文本块的输出是不同的。由于这种技术使用前一个块来加密下一个块,因此需要初始化向量来加密第一个数据块。(根据SymmetricAlgorithm.IV Property MSDN文章)
有效的Key大小为:128、192、256位(根据How many characters to create a byte array for my AES method?
class Program
{
    static void Main(string[] args)
    {
        string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
        string inputValue = valid128BitString;
        string keyValue = valid128BitString;
        string iv = valid128BitString;

        byte[] byteValForString = Convert.FromBase64String(inputValue);
        EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
        EncryptResult encyptedValue = new EncryptResult();
        encyptedValue.IV = iv;
        encyptedValue.EncryptedMsg = result.EncryptedMsg;

        string finalResult = Convert.ToBase64String(Aes128Utility.DecryptData(encyptedValue, keyValue));
        Console.WriteLine(finalResult);

        if (String.Equals(inputValue, finalResult))
        {
            Console.WriteLine("Match");
        }
        else
        {
            Console.WriteLine("Differ");
        }

        Console.ReadLine();
    }
}

AES实用工具

public static class Aes128Utility
{
    private static byte[] key;

    public static EncryptResult EncryptData(byte[] rawData, string strKey)
    {
        EncryptResult result = null;
        if (key == null)
        {
            if (!String.IsNullOrEmpty(strKey))
            {
                key = Convert.FromBase64String((strKey));
                result = Encrypt(rawData);
            }
        }
        else
        {
            result = Encrypt(rawData);
        }

        return result; 

    }

    public static byte[] DecryptData(EncryptResult encryptResult, string strKey)
    {
        byte[] origData = null;
        if (key == null)
        {
            if (!String.IsNullOrEmpty(strKey))
            {
                key = Convert.FromBase64String(strKey);
                origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
            }
        }
        else
        {
            origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
        }

        return origData; 
    }

    private static EncryptResult Encrypt(byte[] rawData)
    {
        using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
        {
            aesProvider.Key = key;
            aesProvider.Mode = CipherMode.CBC;
            aesProvider.Padding = PaddingMode.PKCS7;
            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream encStream = new CryptoStream(memStream, aesProvider.CreateEncryptor(), CryptoStreamMode.Write);
                encStream.Write(rawData, 0, rawData.Length);
                encStream.FlushFinalBlock();
                EncryptResult encResult = new EncryptResult();
                encResult.EncryptedMsg = Convert.ToBase64String(memStream.ToArray());
                encResult.IV = Convert.ToBase64String(aesProvider.IV);
                return encResult;
            }
        }
    }

    private static byte[] Decrypt(byte[] encryptedMsg, byte[] iv)
    {
        using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
        {
            aesProvider.Key = key;
            aesProvider.IV = iv;
            aesProvider.Mode = CipherMode.CBC;
            aesProvider.Padding = PaddingMode.PKCS7;
            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream decStream = new CryptoStream(memStream, aesProvider.CreateDecryptor(), CryptoStreamMode.Write);
                decStream.Write(encryptedMsg, 0, encryptedMsg.Length);
                decStream.FlushFinalBlock();
                return memStream.ToArray();
            }
        }
    }

}

DTO类

public class EncryptResult
{
    public string EncryptedMsg { get; set; }
    public string IV { get; set; }
}

参考资料

  1. 如何为我的AES方法创建字节数组?
  2. 指定的密钥不是此算法的有效大小
  3. AES-256和初始化向量加密
  4. Base-64字符数组的无效长度
  5. 在编码方面,UTF8 / UTF16和Base64有什么区别?

1
实际的异常类型是什么?你是在解密还是加密时遇到了这个异常?我想知道,因为 byte[] byteValForString = Convert.FromBase64String("Test"); 对于你的明文是无效的。 - jbtule
你能否尝试在Encrypt()和Decrypt()函数中都加上 aesProvider.Padding = PaddingMode.None?看看是否有效?不知道为什么使用相同的填充方式会出现错误? - Amitd
@jbtule 我在Decrypt()方法中得到了异常 - decStream.FlushFinalBlock(); 语句。填充无效,不能被删除。 - LCJ
请参考https://dev59.com/nG7Xa4cB1Zd3GeqPsKMg,以获取字符串长度作为密钥和IV。 - LCJ
1个回答

3

使用加密原语时很容易犯错,人们经常会犯错,最好如果可以的话使用高级库

我有一个片段代码,我试图保持其经过审核并更新到最新状态,它与您正在做的工作非常接近。它还对密码文本进行身份验证,如果存在任何方式使对手能够向您的解密实现发送所选密码文本,则我建议您这样做。与修改密码文本相关的侧信道攻击有很多。

然而,您遇到的问题与填充无关,如果您的密码文本与您的密钥和iv不匹配,并且您没有验证iv和密码文本,通常会出现填充错误(如果客户端出现这种情况,它被称为填充预言机)。您需要更改主要语句为:

    string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
    string inputValue = "Test";
    string keyValue = valid128BitString;


    byte[] byteValForString = Encoding.UTF8.GetBytes(inputValue);
    EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
    EncryptResult encyptedValue = new EncryptResult();
    encyptedValue.IV = result.IV; //<--Very Important
    encyptedValue.EncryptedMsg = result.EncryptedMsg;

    string finalResult =Encoding.UTF8.GetString(Aes128Utility.DecryptData(encyptedValue, keyValue));

因此,您需要使用与加密相同的IV来解密。


1
@Amitd,你是对的,问题在于解密时IV错误了。 - jbtule
1
@Lijo IV不需要保密,只有密钥需要。通常加密协议会将IV作为前缀添加到加密块的前面进行保存/传输。像这样的事情是为什么最好使用像jbtule建议的高级库,它可以为您处理这些问题。 - Scott Chamberlain
@Lijo 将你的辅助函数拆分开来。你在加密之前就知道 IV,但是你正在 private static Encrypt(byte[] rawData) 中生成随机 IV、将其分配给加密转换器,并开始加密,所以在开始处理数据之前你看不到 IV。 - Scott Chamberlain
1
@Lijo,你的另一个选择是在开始加密转换之前将IV传递给“Encrypt”并进行分配,而不是让“aesProvider”为您生成随机IV。 - Scott Chamberlain
3
然而,我仍强调您应该使用更高级的库,因为您正在跳过重要步骤,如确保加密消息未被篡改等等。去看看可用的许多库,并使用其中之一。 - Scott Chamberlain
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接