CryptographicException:错误的PKCS7填充

时间:2015-01-23 05:36:04

标签: android encryption xamarin

我看到一小部分生产用户随机报告了与使用Xamarin.Android加密/解密字符串相关的此异常,但不幸的是我无法重现它。

可能导致此问题的原因和/或如何重现异常,以便找出修复/解决方法?

[CryptographicException: Bad PKCS7 padding. Invalid length 147.]
    Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0
    Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
    Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
    System.Security.Cryptography.CryptoStream.FlushFinalBlock():0
    com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0

编辑:这是我用来加密/解密的代码

    private string EncryptWithByteArray(string inPassword, string inByteArray)
    {

        byte[] tmpKey = new byte[20];
        tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8));
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();
        byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword);
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write);
        cs.Write(inputArray, 0, inputArray.Length);
        cs.FlushFinalBlock();
        return Convert.ToBase64String(ms.ToArray());

    }

        private string DecryptWithByteArray (string strText, string strEncrypt)
        {

            try
            {
                byte[] tmpKey = new byte[20];
                tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8));
                DESCryptoServiceProvider des = new DESCryptoServiceProvider ();
                Byte[] inputByteArray = Convert.FromBase64String (strText);
                MemoryStream ms = new MemoryStream ();
                CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write);
                cs.Write (inputByteArray, 0, inputByteArray.Length);
            try {
                cs.FlushFinalBlock();
            } catch (Exception ex) {
                throw(ex);
            }
            System.Text.Encoding encoding = System.Text.Encoding.UTF8;
            return encoding.GetString(ms.ToArray());
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

编辑2:

加密密钥始终是本地设备ID。以下是我如何得到这个:

        TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
        string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId;

这是一个如何调用它的例子:

string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);

1 个答案:

答案 0 :(得分:3)

您没有提供有关您的用例的详细信息,但我会说这种情况正在发生,因为您在加密和解密操作期间没有使用相同的密码设置。对称密码要求您在数据加密和解密期间使用完全相同的设置/参数。例如,对于AES CBC,您需要在两个设备上使用完全相同的密钥,IV,密码模式和填充。最好在代码中明确设置这些设置:

System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged();
aes.Key = new byte[] { ... };
aes.IV = new byte[] { ... };
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

如果您确定使用相同的设置,那么您还应该考虑在网络传输过程中某些数据被破坏或更改的情况。

在提供了一些代码片段后进行编辑:

你提供的解密方法对我来说根本不起作用,所以我把所有的样本放在一起,把它们变成了与你的相同的代码,但是使用IMO的方法稍微清晰一些。例如,这段代码使用了更强大的“密钥派生”(请原谅我的密码学),它也通过了基本的代码分析。

您应该能够轻松使用公共方法来做您需要的事情:

string plainData = "This information should be encrypted";
string encryptedData = EncryptStringified(plainData);
string decryptedData = DecryptStringified(encryptedData);
if (plainData != decryptedData)
    throw new Exception("Decryption failed");

实施和私人方法如下:

/// <summary>
/// Encrypts string with the key derived from device ID
/// </summary>
/// <returns>Base64 encoded encrypted data</returns>
/// <param name="stringToEncrypt">String to encrypt</param>
public string EncryptStringified(string stringToEncrypt)
{
    if (stringToEncrypt == null)
        throw new ArgumentNullException("stringToEncrypt");

    byte[] key = DeviceIdToDesKey();
    byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt);
    byte[] encryptedData = Encrypt(key, plainData);
    return Convert.ToBase64String(encryptedData);
}

/// <summary>
/// Decrypts Base64 encoded data with the key derived from device ID
/// </summary>
/// <returns>Decrypted string</returns>
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param>
public string DecryptStringified(string b64DataToDecrypt)
{
    if (b64DataToDecrypt == null)
        throw new ArgumentNullException("b64DataToDecrypt");

    byte[] key = DeviceIdToDesKey();
    byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt);
    byte[] decryptedData = Decrypt(key, encryptedData);
    return Encoding.UTF8.GetString(decryptedData);
}

private byte[] DeviceIdToDesKey()
{
    TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
    string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE";

    // Compute hash of device ID so we are sure enough bytes have been gathered for the key
    byte[] bytes = null;
    using (SHA1 sha1 = SHA1.Create())
        bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId));

    // Get last 8 bytes from device ID hash as a key
    byte[] desKey = new byte[8];
    Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length);
    return desKey;
}

private byte[] Encrypt(byte[] key, byte[] plainData)
{
    if (key == null)
        throw new ArgumentNullException("key");

    if (plainData == null)
        throw new ArgumentNullException("plainData");

    using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
    {
        if (!desProvider.ValidKeySize(key.Length * 8))
            throw new CryptographicException("Key with invalid size has been specified");
        desProvider.Key = key;
        // desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created
        desProvider.Mode = CipherMode.CBC;
        desProvider.Padding = PaddingMode.PKCS7;

        using (MemoryStream encryptedStream = new MemoryStream())
        {
            // Write IV at the beginning of memory stream
            encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length);

            // Perform encryption and append encrypted data to the memory stream
            using (ICryptoTransform encryptor = desProvider.CreateEncryptor())
            {
                byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
                encryptedStream.Write(encryptedData, 0, encryptedData.Length);
            }

            return encryptedStream.ToArray();
        }
    }
}

private byte[] Decrypt(byte[] key, byte[] encryptedData)
{
    if (key == null)
        throw new ArgumentNullException("key");

    if (encryptedData == null)
        throw new ArgumentNullException("encryptedData");

    using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
    {
        if (!desProvider.ValidKeySize(key.Length * 8))
            throw new CryptographicException("Key with invalid size has been specified");
        desProvider.Key = key;
        if (encryptedData.Length <= desProvider.IV.Length)
            throw new CryptographicException("Too short encrypted data has been specified");
        // Read IV from the beginning of encrypted data
        // Note: New byte array needs to be created because data written to desprovider.IV are ignored
        byte[] iv = new byte[desProvider.IV.Length];
        Array.Copy(encryptedData, 0, iv, 0, iv.Length);
        desProvider.IV = iv;
        desProvider.Mode = CipherMode.CBC;
        desProvider.Padding = PaddingMode.PKCS7;

        // Remove IV from the beginning of encrypted data and perform decryption
        using (ICryptoTransform decryptor = desProvider.CreateDecryptor())
            return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length);
    }
}

很难说你的代码到底有什么问题,因为你的解密方法对我来说根本不起作用 - 很可能是因为它在写模式下使用CryptoStream进行解密,这对我来说有点奇怪。

代码太多了。现在让我们进行真正非常弱的加密。它更像是一种混淆,应该保护数据不被意外地以纯文本形式显示(有些人使用BASE64编码来做同样的事情)。其主要原因是相对较旧的加密算法和易于预测的加密密钥。 AFAIK在同一设备上运行的每个应用程序都可以读取设备ID而无需任何权限。这意味着任何应用程序都可以解密您的数据。当然,您的SQLite数据库可能只对您的应用程序可访问,但如果您移除SD卡或root您的手机,则不再适用。为了使这更好一些,您可以例如要求用户提供密码,然后使用它来获取唯一的加密密钥,但这是完全不同的问题。无论如何,我不确定你要用这种加密方法实现什么 - 即使可以认为它很弱,它也可能完全满足你的需求。

希望这有帮助。