Dohsan
Dohsan

Reputation: 361

C# System.Security.Cryptography - Exception thrown when decrypting with incorrect key - How does padding work?

I've been trying to learn some more about System.Security.Cryptography in c# with online docs & samples. So far I have been able to encrypt/decrypt both text and files successfully; however, i got an exception thrown when i tried to decrypt with an incorrect key.

Blockquote System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.'

Encryption

private static byte[] EncryptData(SymmetricAlgorithms symmetricAlgorithm, byte[] inputBytes, byte[] key)
    {
        byte[] output;

        using (SymmetricAlgorithm algorithm = SymmetricAlgorithmFactory(symmetricAlgorithm))
        {
            byte[] encrypted;
            byte[] salt = new byte[PBKDF2_SaltBytes];
            int maxKeySize = GetLegalKeySizes(algorithm).Max();

            _rng.GetBytes(salt); //
            using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(key, salt, PBKDF2_Iterations))
            {
                algorithm.Key = pbkdf2.GetBytes(maxKeySize);
            }

            //algorithm.Padding = PaddingMode.PKCS7;

            using (ICryptoTransform cryptoTransform = algorithm.CreateEncryptor())
            {
                using (MemoryStream inputStream = new MemoryStream(inputBytes), transformedStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(inputStream, cryptoTransform, CryptoStreamMode.Read))
                    {
                        cryptoStream.CopyTo(transformedStream);
                    }

                    encrypted = transformedStream.ToArray();
                }
            }

            output = new byte[salt.Length + algorithm.IV.Length + encrypted.Length];
            Buffer.BlockCopy(salt, 0, output, 0, salt.Length);
            Buffer.BlockCopy(algorithm.IV, 0, output, salt.Length, algorithm.IV.Length);
            Buffer.BlockCopy(encrypted, 0, output, salt.Length + algorithm.IV.Length, encrypted.Length);
        }

        return output;
    }

Decryption

private static byte[] DecryptData(SymmetricAlgorithms symmetricAlgorithm, byte[] inputBytes, byte[] key)
    {
        using (SymmetricAlgorithm algorithm = SymmetricAlgorithmFactory(symmetricAlgorithm))
        {
            byte[] salt = new byte[PBKDF2_SaltBytes];
            byte[] iv = new byte[algorithm.IV.Length];
            byte[] encryptedData = new byte[inputBytes.Length - salt.Length - iv.Length];

            int maxKeySize = GetLegalKeySizes(algorithm).Max();
            Buffer.BlockCopy(inputBytes, 0, salt, 0, salt.Length);
            Buffer.BlockCopy(inputBytes, salt.Length, iv, 0, iv.Length);
            Buffer.BlockCopy(inputBytes, salt.Length + iv.Length, encryptedData, 0, encryptedData.Length);

            algorithm.IV = iv;
            //algorithm.Padding = PaddingMode.PKCS7;

            using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(key, salt, PBKDF2_Iterations))
            {
                algorithm.Key = pbkdf2.GetBytes(maxKeySize);
            }

            using(ICryptoTransform cryptoTransform = algorithm.CreateDecryptor())
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(encryptedData, 0, encryptedData.Length);
                        cryptoStream.FlushFinalBlock();                         
                    }
                    return memoryStream.ToArray();
                }
            }
        }
    }

SymmetricAlgorithmFactory returns an SymmetricAlgorithm instance

private static SymmetricAlgorithm SymmetricAlgorithmFactory(SymmetricAlgorithms symmetricAlgorithm)
    {
        switch (symmetricAlgorithm)
        {
            case SymmetricAlgorithms.AES:
                return new AesCryptoServiceProvider();
            case SymmetricAlgorithms.DES:
                return new DESCryptoServiceProvider();
            case SymmetricAlgorithms.RC2:
                return new RC2CryptoServiceProvider();
            case SymmetricAlgorithms.Rijndael:
                return new RijndaelManaged();
            case SymmetricAlgorithms.TripleDES:
                return new TripleDESCryptoServiceProvider();
            default:
                throw new Exception("The provided Symmetric algorithm is not supported.");
        }
    }

public enum SymmetricAlgorithms
{
    AES,
    DES,
    RC2,
    Rijndael,
    TripleDES
}

Extra Class properties

private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
    private const int PBKDF2_SaltBytes = 8; //PBKDF2 recommends 64 bits minimum. 64 / 8 = 8 bytes
    private const int PBKDF2_Iterations = 10000;
    private static Encoding encoding = Encoding.UTF32;

Using AES to test, I then tried to change the padding property and got some different messages

PaddingMode.None - Encryption method threw exception

Blockquote System.Security.Cryptography.CryptographicException: 'The input data is not a complete block.'

PaddingMode.PKCS7 + PaddingMode.ANSIX923 + PaddingMode.ISO10126 - Decryption method threw exception

Blockquote System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.'

PaddingMode.Zeros - This seemed to work with both a correct and incorrect key

Would anyone be able to help my understanding of the padding and why certain modes fail? Would it be a situation where different padding is needed for each of the different Symmetric Algorithms?

Upvotes: 0

Views: 1912

Answers (1)

rossum
rossum

Reputation: 15693

Wikipedia has an article on Cryptographic padding which might help.

Block ciphers encrypt data in block sized chunks. Most messages are not an exact number of blocks, so padding is needed to fill out the last block to the right size. On decryption the padding is removed automatically.

In your case the decryption is not recognising the padding and complaining.

Possible causes are many. An incorrect key will mangle everything, including the padding. Indeed any decryption error will munge the padding, causing this problem.

Obviously if the encryption function applies one type of padding and the decryption function expects a different type then you will get this problem. Check that both sides are using the same padding. Unless you have a good reason otherwise, use PKCS#7.

When you set PaddingMode.None the encryption method did not add padding, so the last block was unpadded and hence the wrong size. Thus the encryption side flagged it. That mode is only useful for fixed size messages that are already a whole number of blocks.

PaddingMode.Zeros might work if there are trailing zeros on the last block. The problem is that it will lose all trailing zeros in the original message if present. Not recommended.

Using multiple padding modes is very unusual; one is sufficient. I suspect that the decryption method picked the wrong mode of the three to try first.

Upvotes: 2

Related Questions