views:

808

answers:

3

I know other questions have been asked on this but none so far have provided a solution or are exactly the issue I have.

The class below handles the encryption and decryption of strings, the key and vector passed in are ALWAYS the same.

The strings being encrypted and decrypted are always numbers, most work but the occasional one fails when decrypting (but only on the production server). I should mention that both local and production environments are in IIS6 on Windows Server 2003, the code that uses the class sits in a .ashx handler. The example that fails on the production server is "0000232668"

The error message is

System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed. at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)

And for the code

 public class Aes
    {
        private byte[] Key;
        private byte[] Vector;

        private ICryptoTransform EncryptorTransform, DecryptorTransform;
        private System.Text.UTF8Encoding UTFEncoder;

        public Aes(byte[] key, byte[] vector)
        {
            this.Key = key;
            this.Vector = vector;

            // our encyption method
            RijndaelManaged rm = new RijndaelManaged();

            rm.Padding = PaddingMode.PKCS7;

            // create an encryptor and decyptor using encryption method. key and vector
            EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
            DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);

            // used to translate bytes to text and vice versa
            UTFEncoder = new System.Text.UTF8Encoding();
        }

        /// Encrypt some text and return a string suitable for passing in a URL. 
        public string EncryptToString(string TextValue)
        {
            return ByteArrToString(Encrypt(TextValue));
        }

        /// Encrypt some text and return an encrypted byte array. 
        public byte[] Encrypt(string TextValue)
        {
            //Translates our text value into a byte array. 
            Byte[] bytes = UTFEncoder.GetBytes(TextValue);
            Byte[] encrypted = null;

            //Used to stream the data in and out of the CryptoStream. 
            using (MemoryStream memoryStream = new MemoryStream())
            {                
                using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);                    
                }

                encrypted = memoryStream.ToArray();                
            }

            return encrypted;
        }

        /// The other side: Decryption methods 
        public string DecryptString(string EncryptedString)
        {
            return Decrypt(StrToByteArray(EncryptedString));
        }

        /// Decryption when working with byte arrays.     
        public string Decrypt(byte[] EncryptedValue)
        {
            Byte[] decryptedBytes = null;

            using (MemoryStream encryptedStream = new MemoryStream())
            {
                using (CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write))
                {
                    decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
                }

                decryptedBytes = encryptedStream.ToArray();
            }

            return UTFEncoder.GetString(decryptedBytes);
        }

        /// Convert a string to a byte array.  NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so). 
        //      System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); 
        //      return encoding.GetBytes(str); 
        // However, this results in character values that cannot be passed in a URL.  So, instead, I just 
        // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100). 
        public byte[] StrToByteArray(string str)
        {
            if (str.Length == 0)
                throw new Exception("Invalid string value in StrToByteArray");

            byte val;
            byte[] byteArr = new byte[str.Length / 3];
            int i = 0;
            int j = 0;
            do
            {
                val = byte.Parse(str.Substring(i, 3));
                byteArr[j++] = val;
                i += 3;
            }
            while (i < str.Length);
            return byteArr;
        }

        // Same comment as above.  Normally the conversion would use an ASCII encoding in the other direction: 
        //      System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); 
        //      return enc.GetString(byteArr);     
        public string ByteArrToString(byte[] byteArr)
        {
            byte val;
            string tempStr = "";
            for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
            {
                val = byteArr[i];
                if (val < (byte)10)
                    tempStr += "00" + val.ToString();
                else if (val < (byte)100)
                    tempStr += "0" + val.ToString();
                else
                    tempStr += val.ToString();
            }
            return tempStr;
        }

EDIT: Thankyou for all of your help however your answers did not un-cover the problem, which turned out to be something stupidly simple. I was generating an encrypted string on one server and handing it over to a handler on another server for decrpytion and processing, but it turns out that the results of encryption differ when run on different servers, hence the receiving server could not decrypt it. One of the answers stumbled across the hint at this by accident, which is why I accepted it

+1  A: 

You will sometimes get a message about invalid padding when encryption and decryption for whatever reason have used the same key or initialisation vector. Padding is a number of bytes added to the end of your plaintext to make it up to a full number of blocks for the cipher to work on. In PKCS7 padding each byte is equal to the number of bytes added, so it can always be removed after decryption. Your decryption has led to a string where the last n bytes are not equal to the value n of the last byte (hope that sentence makes sense). So I would double check all your keys.

Alternatively, in your case, I would suggest making sure that you create and dispose an instance of RijndaelManagedTransform for each encryption and decryption operation, initialising it with the key and vector. This problem could very well be caused by reusing this transform object, which means that after the first use, it is no longer in the right initial state.

David M
Err yeah sorry about the comments, it's "borrowed code" from anothe SO question."Where did it come from"The string or the code?
Nick Allen - Tungle139
The string. Glad it's borrowed code, I don't have to tread as carefully...
David M
Also why would it work in the local environment if that's the case? Not questioning your answer, just seems weird
Nick Allen - Tungle139
The strings are codes for images coming out of an image library
Nick Allen - Tungle139
OK, but what is going the encoding for these? Is this an encrypted string from the code above, that you are then trying to decrypt using the code above? Just need a bit more context.
David M
No the string is the original id string for an image, which I am getting from a webservice. I then encrypt the string using the encrypttostring method above, which turns it into 105162040231029115202011159160207173133005046245. When I then decrypt the long number I get the padding error. Thanks for looking at this BTW
Nick Allen - Tungle139
The reason I wanted the long number format is because I need to pass the encrypted image id in a querystring
Nick Allen - Tungle139
And does EncryptToString with the same input give you this same output in both production and development environments? Because that certainly doesn't have valid PKCS7 padding as it is...
David M
Just ran a test and no it doesn't! it gives me. 112012252134066051251251177029075026100201077033
Nick Allen - Tungle139
In fact, motive for my question was wrong, so please ignore my last comment, but it does seem to have been the right question to ask!
David M
LOL. Okay being a novice with encryption, why is this happening?
Nick Allen - Tungle139
Check my edited answer
David M
Check my edit to see why I accepted your answer
Nick Allen - Tungle139
Well, glad we got you there Nick, even if it was somewhat by fluke...
David M
A: 

this results in character values that cannot be passed in a URL

Is there reason why you are using your own encoding, StrToByteArray, instead of Base64 encoding?

If you make these changes:

public string EncryptToString(string TextValue)
{
  return Convert.ToBase64String(Encrypt(TextValue));
}

public string DecryptToString(string TextValue)
{
  return Decrypt(Convert.FromBase64String(TextValue));
}

then things should work a lot better.

Edit:
Regarding problem with ToBase64String and QueryString:
If you do your own QueryString parsing then you need to make sure you only Split on the first =-sign.

var myURL = "http://somewhere.com/default.aspx?encryptedID=s9W/h7Sls98sqw==&amp;someKey=someValue";
var myQS = myURL.SubString(myURL.IndexOf("?") + 1);
var myKVPs = myQS.Split("&");
foreach (var kvp in myKVPs) {
  // It is important you specify a maximum number of 2 elements
  // since the Base64 encoded string might contain =-signs.
  var keyValue = kvp.Split("=", 2);
  var key = keyValue[0];
  var value = keyValue[1];
  if (key == "encryptedID")
    var decryptedID = myAES.DecryptToString(value);
}

This way you don't need to replace any characters in your QueryString when it's Base64 encoded.

Sani Huttunen
Nick Allen - Tungle139
You shouldn't need to do any replacement when using the result in a querystring. I've been using ToBase64String for many years without any problems in querystrings.
Sani Huttunen
Umm I don't see how you could pass and =, + or / characters in querystring values and not break the url
Nick Allen - Tungle139
URL and QueryString parsers are smart. They work similar to the example above. If a / or a + is after the ? then it is regarded as part of the QueryString. There can be only one =-sign in a KeyValuePair string, the first one. The rest are regarded as part of the value.
Sani Huttunen
+1  A: 

I tend to explicitly call the FlushFinalBlock method on CryptoStream before closing it. That would mean doing the following in your encrypt method:

using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
{
    cs.Write(bytes, 0, bytes.Length);
    cs.FlushFinalBlock();        
}

If you don't do this, it may be that the encrypted data is being truncated - this would result in an "invalid padding" scenario. Padding is always present when using PKCS7, even if the data being encrypted is aligned to the block length of the cipher.

Dave Cluderay
FlushFinalBlock() is called when CryptoStream is disposed, which happens automatically with a using statement
Nick Allen - Tungle139
Maybe they fixed that flaw, but it certainly was once the case that CryptoStream.Dispose did NOT call FlushFinalBlock! http://bytes.com/topic/c-sharp/answers/255847-encrypt-string-string-vice-versa
Dave Cluderay
Looks like they fixed it in .NET 2.0, but I would definitely suggest getting to the habit of flushing the final block yourself.
Dave Cluderay