CryptographicException:错误的PKCS7填充

Le-*_*nes 8 encryption android 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
Run Code Online (Sandbox Code Playgroud)

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

    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;
            }
        }
Run Code Online (Sandbox Code Playgroud)

编辑2:

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

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

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

string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
Run Code Online (Sandbox Code Playgroud)

jar*_*riq 4

您没有提供有关您的用例的更多详细信息,但我想说这种情况发生是因为您在加密和解密操作期间没有使用相同的密码设置。对称密码要求您在数据加密和解密过程中使用完全相同的设置/参数。例如,对于 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;
Run Code Online (Sandbox Code Playgroud)

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

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

您提供的解密方法对我来说根本不起作用,所以我将您的所有样本放在一起,并将它们转换成与您的代码执行相同操作的代码,但在我看来使用了一种稍微干净的方法。例如,这段代码使用了更强大的“密钥派生”(请原谅我的密码专家),并且它还通过了基本的代码分析。

您应该能够轻松使用公共方法来完成您需要的操作:

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

实现和私有方法如下:

/// <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);
    }
}
Run Code Online (Sandbox Code Playgroud)

真的很难判断您的代码到底出了什么问题,因为您的解密方法根本不适合我 - 很可能是因为它在写入模式下使用 CryptoStream 进行解密,这对我来说似乎有点奇怪。

代码就讲这么多。现在让我们来看看加密技术,它的强度真的很弱。它更像是一种混淆,可以保护数据不被意外地以纯文本形式显示(有些人使用 BASE64 编码来实现同样的目的)。造成这种情况的主要原因是加密算法相对陈旧,加密密钥容易预测。AFAIK 在同一设备上运行的每个应用程序都可以读取设备 ID,无需任何权限。这意味着任何应用程序都可以解密您的数据。当然,您的 SQLite 数据库可能只能由您的应用程序访问,但如果您移除 SD 卡或 root 手机,情况就不再如此。为了使这一点更好一点,您可以例如要求用户提供密码,然后使用它来派生唯一的加密密钥,但这是完全不同的问题。无论如何,我不太确定您想通过这种加密实现什么目的 - 即使它可以被认为很弱,它也可能完全足以满足您的需求。

希望这可以帮助。