3

I have following code that uses AesCryptoServiceProvider for encrypting and decrypting. The iv and key used are same for both encryption and decryption. Still the decrypted value differ from the source string.

  1. What need to be corrected to get the original value after decrypt?
  2. This code is working when inputValue = valid128BitString. But when the inputString = “Test” I am getting the following exception Padding is invalid and cannot be removed.. How can we correct it?

UPDATED QUESTION

The following will do the trick based on @jbtule answer.

encyptedValue.IV = result.IV;

The IV value from encryption result changes. Suppose encryption is done in a separate process, how can we know the IV for decryption? Is there a way to make it constant or known?

Answer: Your other option is pass a IV in to Encrypt and assign it before you begin your crypto transform, instead of letting aesProvider generate a random one for you. – @Scott Chamberlain

 aesProvider.IV = Convert.FromBase64String("4uy34C9sqOC9rbV4GD8jrA==");

Update: Refer How to apply padding for Base64. We can use UTF8 for encoding the source input and result output. The key and IV may remain in Base64.

Using Base64 for source input will cause issues with some values, for example, "MyTest" where length of string is not a multiple of 4

Relevant points:

To decrypt data that was encrypted using one of the SymmetricAlgorithm classes, you must set the Key property and IV property to the same values that were used for encryption.

SymmetricAlgorithm.IV Property: Information from the previous block is mixed into the process of encrypting the next block. Thus, the output of two identical plain text blocks is different. Because this technique uses the previous block to encrypt the next block, an initialization vector is needed to encrypt the first block of data. (As per SymmetricAlgorithm.IV Property MSDN article)

The valid Key sizes are: 128, 192, 256 bits (as per How many characters to create a byte array for my AES method?)

Main Program

class Program
{
    static void Main(string[] args)
    {
        string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
        string inputValue = valid128BitString;
        string keyValue = valid128BitString;
        string iv = valid128BitString;

        byte[] byteValForString = Convert.FromBase64String(inputValue);
        EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
        EncryptResult encyptedValue = new EncryptResult();
        encyptedValue.IV = iv;
        encyptedValue.EncryptedMsg = result.EncryptedMsg;

        string finalResult = Convert.ToBase64String(Aes128Utility.DecryptData(encyptedValue, keyValue));
        Console.WriteLine(finalResult);

        if (String.Equals(inputValue, finalResult))
        {
            Console.WriteLine("Match");
        }
        else
        {
            Console.WriteLine("Differ");
        }

        Console.ReadLine();
    }
}

AES Utility

public static class Aes128Utility
{
    private static byte[] key;

    public static EncryptResult EncryptData(byte[] rawData, string strKey)
    {
        EncryptResult result = null;
        if (key == null)
        {
            if (!String.IsNullOrEmpty(strKey))
            {
                key = Convert.FromBase64String((strKey));
                result = Encrypt(rawData);
            }
        }
        else
        {
            result = Encrypt(rawData);
        }

        return result; 

    }

    public static byte[] DecryptData(EncryptResult encryptResult, string strKey)
    {
        byte[] origData = null;
        if (key == null)
        {
            if (!String.IsNullOrEmpty(strKey))
            {
                key = Convert.FromBase64String(strKey);
                origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
            }
        }
        else
        {
            origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
        }

        return origData; 
    }

    private static EncryptResult Encrypt(byte[] rawData)
    {
        using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
        {
            aesProvider.Key = key;
            aesProvider.Mode = CipherMode.CBC;
            aesProvider.Padding = PaddingMode.PKCS7;
            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream encStream = new CryptoStream(memStream, aesProvider.CreateEncryptor(), CryptoStreamMode.Write);
                encStream.Write(rawData, 0, rawData.Length);
                encStream.FlushFinalBlock();
                EncryptResult encResult = new EncryptResult();
                encResult.EncryptedMsg = Convert.ToBase64String(memStream.ToArray());
                encResult.IV = Convert.ToBase64String(aesProvider.IV);
                return encResult;
            }
        }
    }

    private static byte[] Decrypt(byte[] encryptedMsg, byte[] iv)
    {
        using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
        {
            aesProvider.Key = key;
            aesProvider.IV = iv;
            aesProvider.Mode = CipherMode.CBC;
            aesProvider.Padding = PaddingMode.PKCS7;
            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream decStream = new CryptoStream(memStream, aesProvider.CreateDecryptor(), CryptoStreamMode.Write);
                decStream.Write(encryptedMsg, 0, encryptedMsg.Length);
                decStream.FlushFinalBlock();
                return memStream.ToArray();
            }
        }
    }

}

DTO Class

public class EncryptResult
{
    public string EncryptedMsg { get; set; }
    public string IV { get; set; }
}

References

  1. How many characters to create a byte array for my AES method?
  2. Specified key is not a valid size for this algorithm
  3. Encryption with AES-256 and the Initialization Vector
  4. Invalid length for a Base-64 char array
  5. What's the difference between UTF8/UTF16 and Base64 in terms of encoding
Community
  • 1
  • 1
LCJ
  • 20,854
  • 59
  • 228
  • 387
  • 1
    What is the actual exception type, are you getting this on decrypt or encrypt? Would like to know because `byte[] byteValForString = Convert.FromBase64String("Test");` wouldn't be valid for your plaintext. – jbtule Feb 18 '13 at 15:32
  • Can you try `aesProvider.Padding = PaddingMode.None` in both Encrypt() and Decrypt() .if it works? Wonder why it gives error if same padding is used? – Amitd Feb 18 '13 at 15:51
  • @jbtule I am getting exception in Decrypt() method - decStream.FlushFinalBlock(); statement. Padding is invalid and cannot be removed. – LCJ Feb 18 '13 at 16:00
  • Refer http://stackoverflow.com/questions/14954248/how-to-create-byte-with-length-16-using-frombase64string too for length of string as Key and IV – LCJ Feb 19 '13 at 12:40

1 Answers1

3

It is easy to make implementation mistakes with cryptographic primitives, people do it all the time, it's best to use a high level library if you can.

I have a snippet that I try to keep reviewed and up to date, that works pretty close to what you're doing. It also does authentication on the cipher text, which I would recommend if there is anyway an adversary could send chosen ciphertext to your decryption implementation, there are a lot of side channel attacks related to modifying the ciphertext.

However, the problem you're having does not have any thing to do with padding, if your ciphertext doesn't matchup to your key and iv, and you didn't authenticate your iv and ciphertext, you'll typically get a padding error (if this is bubbled up a client it's called a padding oracle). You need to change your main statement to:

    string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
    string inputValue = "Test";
    string keyValue = valid128BitString;


    byte[] byteValForString = Encoding.UTF8.GetBytes(inputValue);
    EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
    EncryptResult encyptedValue = new EncryptResult();
    encyptedValue.IV = result.IV; //<--Very Important
    encyptedValue.EncryptedMsg = result.EncryptedMsg;

    string finalResult =Encoding.UTF8.GetString(Aes128Utility.DecryptData(encyptedValue, keyValue));

So you use the same IV to decrypt as you did to encrypt.

Community
  • 1
  • 1
jbtule
  • 29,889
  • 11
  • 91
  • 126
  • any clue why it caused an exception? FlushFinalBlock was called in encrypt – Amitd Feb 18 '13 at 16:30
  • 1
    @Amitd you are right the issue was the IV was wrong when decrypting. – jbtule Feb 18 '13 at 19:41
  • Thanks.. The IV value from encryption result changes. Suppose encryption is done in a separate process, how can I know the IV for decryption? Is there a way to make it constant or known? – LCJ Feb 19 '13 at 04:55
  • 1
    @Lijo The IV does not need to be kept secret, only the key. Often Crytpo protocols will just prepend the IV to the front of the crypto blob as it is being saved/transmitted. Things like this is why it is better to use higher level libraries like jbtule suggested, it just takes care of it for you. – Scott Chamberlain Feb 19 '13 at 05:36
  • @ScottChamberlain I am fine with keeping the IV public. But how do I know the `IV` during decryption that was used for encryption? The IV seems to be changing. How can we deduce the value of IV used for encryption? – LCJ Feb 19 '13 at 05:39
  • @Lijo split your helper functions up. You know the IV before you start encrypting however you are: generating a random IV, assigning it to the crypto transform, and start encrypting all within `private static EncryptResult Encrypt(byte[] rawData)` so you don't get to see the IV before you start processing the data. – Scott Chamberlain Feb 19 '13 at 05:42
  • 1
    @Lijo Your other option is pass a IV in to `Encrypt` and assign it before you begin your crypto transform, instead of letting `aesProvider` generate a random one for you. – Scott Chamberlain Feb 19 '13 at 05:46
  • @ScottChamberlain, Is it guaranteed that the result of AES encryption will be a `Unicode` string? If it is not `Unicode`, we can't use `UTF-8` isn't it? – LCJ Feb 19 '13 at 07:49
  • @Lijo No, it will not be a string at all, it will be a byte array. You can change to to a string representation by using `ToBase64String`. So to encrypt "String to String" it would be `decrypted String -> Encoding.UTF8.GetBytes -> Encryptor -> Convert.ToBase64String -> "Encrypted string"`. To get your original message back you go `"Encrypted string" -> Convert.FromBase64String -> Decryptor -> Encoding.UTF8.GetString -> decrypted string` The important thing is use the same encoding for the first step in encrypting and the last step in decrypting. – Scott Chamberlain Feb 19 '13 at 07:56
  • 3
    @Lijo However I still stress that you should be using a higher level library, you are skipping important steps like making sure your encrypted message was not tampered with and much more. Go look at the many libraries available and use one of those. – Scott Chamberlain Feb 19 '13 at 08:00
  • @ScottChamberlain Thanks for the suggestions. I am just trying to learn... I have a related question posted in http://stackoverflow.com/questions/14952546/how-to-apply-padding-for-base64 – LCJ Feb 19 '13 at 08:03