Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CryptographicException: Bad PKCS7 padding

I am seeing a small percentage of production users randomly report this exception related to encrypting/decrypting strings with Xamarin.Android but unfortunately I cannot reproduce it.

What could cause this and/or how could I reproduce the exception so that I can figure out a fix/workaround?

[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

EDIT: Here's the code I am using to encrypt/decrypt

    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;
            }
        }

EDIT 2:

The encryption key is always the local Device ID. Here's how I am getting this:

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

Here's an example of how it's called:

string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
like image 958
Le-roy Staines Avatar asked Jan 23 '15 05:01

Le-roy Staines


People also ask

What is PKCS7 Padding?

PKCS7 padding is a generalization of PKCS5 padding (also known as standard padding). PKCS7 padding works by appending N bytes with the value of chr(N) , where N is the number of bytes required to make the final block of data the same size as the block size.

How do you use PKCS 7 Padding?

The rules for PKCS padding are very simple: Padding bytes are always added to the clear text before it is encrypted. Each padding byte has a value equal to the total number of padding bytes that are added. For example, if 6 padding bytes must be added, each of those bytes will have the value 0x06.


1 Answers

You have not provided much details about your use case but I would say this is happening because you are not using the same cipher settings during the encryption and decryption operations. Symmetric ciphers require you to use exactly the same settings/parameters during the data encryption and also decryption. For example for AES CBC you would need to use exactly the same key, IV, cipher mode and padding on both devices. It is best to set these setting explicitly in the code:

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;

If you are sure you are using the same settings then you should also consider scenario that some data get corrupted or altered during the network transfer.

Edit after some code fragments have been provided:

Decryption method you have provided does not work for me at all so I have put together all your samples and turned them into the code which does the same thing as yours but uses IMO a slightly cleaner approach. For example this code uses more robust "key derivation" (please forgive me cryptoguys) and it has also passed basic code analysis.

You should be able to easily use public methods to do what you need:

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

Implementation and private methods follows:

/// <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);
    }
}

It is really hard to tell what exactly was problem with your code because your decryption method did not work for me at all - most likely because it is using CryptoStream in write mode for decryption which seems a little odd to me.

So much for the code. Now let's get to encryption which is really really weak. It is more just an obfuscation that should protect the data from being accidentally displayed in plain text form (some people use BASE64 encoding for the same thing). The main cause of this is relatively old encryption algorithm and easily predictable encryption key. AFAIK every application running on the same device can read device ID without any privileges. That means any application can decrypt your data. Of course your SQLite database is probably accessible only to your application but that can no longer be true if you remove the SD card or root your phone. To make this a little better you could for example ask user to provide a password and then use it to derive unique encryption key but that is completely different problem. Anyway I am not really sure what you are trying to achieve with this encryption - it may be fully sufficient for your needs even if it can be considered to be weak.

Hope this helps.

like image 158
jariq Avatar answered Oct 18 '22 21:10

jariq