使用AES/CBC/NoPadding算法解密字符串

6
我想在C# Windows Phone 8应用程序中使用AES/CBC/NoPadding解密加密的字符串。我的字符串在IsolatedStorage文件中。我粘贴了这个链接,它是垃圾。
从这篇文章中,我正在使用AesManaged类进行解密。 但如何将填充设置为NoPadding,因为默认情况下填充设置为PKCS7,来自这里
        string fileName = "titlepage.xhtml";

        if (fileStorage.FileExists(fileName))
        {
            IsolatedStorageFileStream someStream = fileStorage.OpenFile(fileName, System.IO.FileMode.Open, FileAccess.Read);
            using (StreamReader reader = new StreamReader(someStream))
            {
                str1 = reader.ReadToEnd();

                MessageBox.Show(str1);

                try
                {
                    string text = Decrypt(str1, "****************", "****************");

                    MessageBox.Show(text);
                }
                catch (CryptographicException cryptEx)
                {
                    MessageBox.Show(cryptEx.Message, "Encryption Error", MessageBoxButton.OK);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "General Error", MessageBoxButton.OK);
                }
            }
        }

    public string Decrypt(string dataToDecrypt, string password, string salt)
    {
        AesManaged aes = null;
        MemoryStream memoryStream = null;

        try
        {
            //Generate a Key based on a Password and HMACSHA1 pseudo-random number generator
            //Salt must be at least 8 bytes long
            //Use an iteration count of at least 1000
            Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), 10000);               

            //Create AES algorithm
            aes = new AesManaged();
            //Key derived from byte array with 32 pseudo-random key bytes
            aes.Key = rfc2898.GetBytes(32);
            //IV derived from byte array with 16 pseudo-random key bytes
            aes.IV = rfc2898.GetBytes(16);

            //Create Memory and Crypto Streams
            memoryStream = new MemoryStream();
            CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write);
            
            byte[] data = Convert.FromBase64String(dataToDecrypt);
            cryptoStream.Write(data, 0, data.Length);
            cryptoStream.FlushFinalBlock();

            //Return Decrypted String
            byte[] decryptBytes = memoryStream.ToArray();

            //Dispose
            if (cryptoStream != null)
                cryptoStream.Dispose();

            //Retval
            return Encoding.UTF8.GetString(decryptBytes, 0, decryptBytes.Length);
        }
        finally
        {
            if (memoryStream != null)
                memoryStream.Dispose();

            if (aes != null)
                aes.Clear();
        }            
    }

编辑1:

当我在这行代码中解密加密字符串时

 byte[] data = Convert.FromBase64String(dataToDecrypt);

移动到最终块并获取异常:输入不是有效的Base-64字符串,因为它包含非Base-64字符、超过两个填充字符或填充字符中存在非法字符。

在Windows Phone中支持哪个类解密有点混淆。

如果我完全错误,请建议我有关Windows Phone算法的文章网址。

编辑2:

如下回答所建议的"我将cyperText作为字节获取,解密时没问题。但是它会抛出一个带有描述的异常。

       [Cryptography_SSD_InvalidDataSize]
    Arguments: 
    Debugging resource strings are unavailable. Often the key and arguments provide 
sufficient information to diagnose the problem

我认为问题出在IV[salt key]或将填充设置为AesManged上。 但我无法在Windows Phone中将填充属性更改为AesManaged。 默认情况下,AesManged的填充是PKCS7。我想要更改为NoPadding。因为我的cyperText是使用AES/CBC/NoPadding算法加密的。

你知道用于加密的算法吗? - Karthik AMR
@KarthikAMR,我编辑了这篇帖子。请检查一下。 - Kumar
根据您的描述,错误与Base64解码有关,而不是解密有关。您尝试过跟踪dataToDecrypt吗? - clausc
在我的问题中,我提到了我的字符串链接。这是它:[http://pastebin.com/gYHbSy5d] - Kumar
@OlegEstekhin,你不能更改Windows Phone的填充。请查看您链接中的平台部分[http://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged.padding.aspx#platformsTitleToggle]。 - Kumar
显示剩余3条评论
2个回答

7
如果我理解得没错的话,您有一些数据已经以AES CBC模式加密,没有填充。但在您想要解密数据的手机上,唯一的选择是PKCS#7填充。

好消息是:您可以使用PKCS#7填充解密密文。您需要做的就是在手机上将填充添加到密文中,然后进行解密。

要在事后添加填充,您需要加密少量数据并将其附加到密文中。然后,您解密修改后的密文,并将那小段数据删除,这样就得到了原始明文。

以下是操作步骤:

  1. 在手机上获取一个密文。即使没有填充,它也必须是16字节的倍数。因为AES密文始终是16字节的倍数。

  2. 将密文的最后16个字节放在一边,并将其设置为AES ENCRYPT的IV(加密,而不是解密)。使用您稍后要使用的相同密钥。

  3. 现在加密少于16个字节的内容,例如字符“$”。手机将对此添加PKCS#7填充。

  4. 将步骤1中的原始密文与结果为16个字节的密文附加在一起,现在您就有了一个适当的PKCS#7填充的密文,其中包括原始明文和添加的“$”。

  5. 使用原始IV和相同的密钥,现在解密这个组合的密文。您可以删除出现在明文末尾的“$”(或者您在步骤3中添加的任何内容)。

当使用原始密文的最后16个字节加密小段数据时,您实际上是以真正的AES CBC模式扩展了密文,而且恰好使用了PKCS#7填充,因此您现在可以解密整个密文并将小段数据删除。您将得到没有填充的原始明文。

以下是代码示例:

var rfc2898 = new Rfc2898DeriveBytes("password", new byte[8]);

using (var aes = new AesManaged())
{
    aes.Key = rfc2898.GetBytes(32);
    aes.IV = rfc2898.GetBytes(16);

    var originalIV = aes.IV; // keep a copy

    // Prepare sample plaintext that has no padding
    aes.Padding = PaddingMode.None;
    var plaintext = Encoding.UTF8.GetBytes("this plaintext has 32 characters");
    byte[] ciphertext;
    using (var encryptor = aes.CreateEncryptor())
    {
        ciphertext = encryptor.TransformFinalBlock(plaintext, 0, plaintext.Length);
        Console.WriteLine("ciphertext: " + BitConverter.ToString(ciphertext));
    }

    // From this point on we do everything with PKCS#7 padding
    aes.Padding = PaddingMode.PKCS7;

    // This won't decrypt -- wrong padding
    try
    {
        using (var decryptor = aes.CreateDecryptor())
        {
            var oops = decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("caught: " + e.Message);
    }

    // Last block of ciphertext is used as IV to encrypt a little bit more
    var lastBlock = new byte[16];
    var modifiedCiphertext = new byte[ciphertext.Length + 16];

    Array.Copy(ciphertext, ciphertext.Length - 16, lastBlock, 0, 16);
    aes.IV = lastBlock;

    using (var encryptor = aes.CreateEncryptor())
    {
        var dummy = Encoding.UTF8.GetBytes("$");
        var padded = encryptor.TransformFinalBlock(dummy, 0, dummy.Length);

        // Set modifiedCiphertext = ciphertext + padded
        Array.Copy(ciphertext, modifiedCiphertext, ciphertext.Length);
        Array.Copy(padded, 0, modifiedCiphertext, ciphertext.Length, padded.Length);
        Console.WriteLine("modified ciphertext: " + BitConverter.ToString(modifiedCiphertext));
    }

    // Put back the original IV, and now we can decrypt...
    aes.IV = originalIV;

    using (var decryptor = aes.CreateDecryptor())
    {
        var recovered = decryptor.TransformFinalBlock(modifiedCiphertext, 0, modifiedCiphertext.Length);
        var str = Encoding.UTF8.GetString(recovered);
        Console.WriteLine(str);

        // Now you can remove the '$' from the end
    }
}

我无法检查您粘贴的密文,因为缺少密钥、IV和明文。请加密一些明文,并提供原始明文、密钥、IV和密文以及另一端的密文、密钥、IV和恢复的明文。任何简单的消息和任意随机密钥都可以用来比较结果。 - Jim Flood
以下是程序相关的内容,需要从英语翻译成中文。只返回翻译后的文本:这里是解密后的结果,看起来像垃圾文件[http://1drv.ms/1o4nInB]。如果您想检查该文件,请尝试下载。我在加密和解密时使用了相同的IV和密钥,都在txt文件中。如果您想了解更多信息,请让我知道。 - Kumar
哦,我的错...这是我的错误。我本应该为测试目的创建一个新的密钥。 我想在创建密钥时错放了一个字符。一般来说,我使用16字节的密钥和16字节的IV。 这是我的新的普通文件、加密文件、解密结果文件和密钥 [http://1drv.ms/1zbWXGp] - Kumar
@JimFlood 嘿..我在尝试几种情况..但是我仍然得到解密后的字符串是垃圾的。 - Kumar
我有机会时会看一下。您能否在两端输出密钥的字节:加密者,解密者,密钥的实际字节。您的Key New显示为“Z9zyDkLQWinDowrt”,但那是16个ASCII字符代码还是32个UTF-16代码(大端或小端?)或者是提供给密码到密钥派生算法?我可以看到上面的C#代码确切地做了什么,但加密代码做了什么,密钥字节值是否相同? - Jim Flood
显示剩余6条评论

1
你链接的字符串不是Base-64编码。看起来像是原始加密字节,被解释为字符。要么在加密端工作,将原始字节输出为Base-64字符串编码,要么在解密端工作,将密文读取为原始字节,而不是文本,并且忽略删除Base-64。通常最好在加密端工作,因为传递Base-64文本比传递原始字节出错的可能性小得多。

我在解密端进行了更改,但没有成功;请查看我的问题中的Edit 2: - Kumar
查看C#文档中的AESManaged。您有一个用于填充属性的getter和setter。PaddingMode.None似乎是您所需的。 - rossum
对于Windows手机,它不受支持。请查看AesManaged.Padding属性文档中的平台列表。[http://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged.padding.aspx#platformsTitleToggle] - Kumar
然后你需要 1) 在加密方面进行更改,2) 在解密方面使用不同的平台,或者 3) 从基础开始构建自己的AES/CBC/NoPadding解码函数。如果您有一个能够处理单个块的AES/ECB模式,那么这并不太困难。 - rossum
谢谢您的建议。让我尝试这些案例。但是由于文件已经加密,我无法在加密方面进行更改。 - Kumar

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