8

I am currently working on AES implementation in C#. The encryption method has two parameters: a string and a password. I am taking the supplied string and converting it to an array of bytes, so I can use it later for writing data to a stream with BinaryWriter.

The problem is that when I use Convert.FromBase64String(string) I get FormatException: Invalid length.and when I use Encoding.UTF8.GetBytes(string) my decryption method throws and invalid PKCS7.Padding exception.

I have been trying to solve this problem for the last couple of days. I have read near infinite questions in stackoverflow.com and other websites, but I still don't know what is the most reliable way to solve this problem.

Strings that will be used in this program are limited to sentences (ex. "Something to encrypt.") and numbers (ex. "12345").

Thank you in advance, here is the code I have at this point in time:

    public class AESProvider {

    public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
    {
        // Check arguments. 
        if (plainText == null || plainText.Length <= 0)
            throw new ArgumentNullException("plainText");
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        byte[] plainTextInBytes = Convert.FromBase64String(plainText);
        byte[] encrypted;

        //Create an Aes object
        //with the specified key and IV.

        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.GenerateIV();
            byte[] IV = aesAlg.IV;
            //The Salt will be the first 8 bytes of the IV.
            byte[] theSalt = new byte[8];
            Array.Copy(IV,theSalt,8);
            //A key for AES is generated by expanding the password using the following method.
            Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
            byte[] aesKey = keyGen.GetBytes(16);
            aesAlg.Key = aesKey;

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

            // Create the streams used for encryption. 
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (BinaryWriter swEncrypt = new BinaryWriter(csEncrypt))
                    {

                        //Write all data to the stream.
                        swEncrypt.Write(plainTextInBytes);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
            // Prepend the IV to the ciphertext so it can be used in the decryption process.
            using (MemoryStream ivPlusCipher = new MemoryStream())
            {
                using (BinaryWriter tBinaryWriter = new BinaryWriter(ivPlusCipher))
                {
                    tBinaryWriter.Write(IV);
                    tBinaryWriter.Write(encrypted);
                    tBinaryWriter.Flush();
                }
                return ivPlusCipher.ToArray();
            }
        }
    }

    public byte[] DecryptStringFromBytes_Aes(byte[] cipherText, string Key)
    {
        // Check arguments. 
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException("cipherText");
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        // Declare the string used to hold 
        // the decrypted text. 
        byte[] decrypted;

        // Create an Aes object 
        // with the specified key and IV. 

        // Create the streams used for decryption. 

        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Mode = CipherMode.CBC;
            aesAlg.Padding = PaddingMode.PKCS7;
            //Grab IV from ciphertext
            byte[] IV = new byte[16];
            Array.Copy(cipherText,0,IV,0,16);
            //Use the IV for the Salt
            byte[] theSalt = new byte[8];
            Array.Copy(IV,theSalt,8);
            Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
            byte[] aesKey = keyGen.GetBytes(16);
            aesAlg.Key = aesKey;

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

            using (MemoryStream msDecrypt = new MemoryStream())
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
                {
                    using (BinaryWriter srDecrypt = new BinaryWriter(csDecrypt))
                    {
                        //Decrypt the ciphertext
                        srDecrypt.Write(cipherText, IV.Length, (cipherText.Length - IV.Length));
                    }
                    decrypted = msDecrypt.ToArray();
                    return decrypted;
                }
            }   
        }
    }
}
Geronimo Rodriguez
  • 189
  • 1
  • 4
  • 9

3 Answers3

11

You need to convert between bytes and strings before and after encryption/decryption. This is not the same operation, and you should not use the same method.

When encrypting you start out with an arbitrary string. Convert this to a byte[] using Encoding.UTF8.GetBytes(). Encrypt it. The resulting byte[] can now be converted to a string using Convert.ToBase64String().

When decrypting you now start out with a Base64 encoded string. Decode this to a byte[] using Convert.FromBase64String(). Decrypt it. You now have the UTF-8 encoding of your original string, which you can decode using Encoding.UTF8.GetString().

Remember:

  • Encoding.UTF8 works to convert arbitrary strings to byte-arrays (but it can only convert byte-arrays that contain actual UTF8-encodings back).
  • Convert.[To/From]Base64String works to convert arbitrary byte-arrays to strings (but it can only convert strings that contain actual Base64-encodings back).
Rasmus Faber
  • 48,631
  • 24
  • 141
  • 189
  • 1
    "To every badly redacted, very long question posted in Stack Overflow there is always a very simple answer that makes you feel very stupid." Albert Einstein. Thank you for the reply, it worked beautifully. – Geronimo Rodriguez Oct 20 '14 at 19:10
2

Convert.FromBase64String(string); is expected to receive a string generated by Convert.ToBase64String(byte[]); passing in a arbitrary string will not work.

The easiest solution is replace the BinaryWriter and BinaryReader with a StreamWriter and a StreamReader and not do any conversion at all.

public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
{
    // Check arguments. 
    if (plainText == null || plainText.Length <= 0)
        throw new ArgumentNullException("plainText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");


    //Create an Aes object
    //with the specified key and IV.

    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.GenerateIV();
        byte[] IV = aesAlg.IV;
        //The Salt will be the first 8 bytes of the IV.
        byte[] theSalt = new byte[8];
        Array.Copy(IV,theSalt,8);
        //A key for AES is generated by expanding the password using the following method.
        Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
        byte[] aesKey = keyGen.GetBytes(16);
        aesAlg.Key = aesKey;

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

        // Create the streams used for encryption. 
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            //You can write the IV here and not need to do it later.
            msEncrypt.Write(IV, 0, IV.Length);

            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter (csEncrypt))
                {    
                    //Write all data to the stream.
                    swEncrypt.Write(plainText);
                }
            }

            //Move this outside of the using statement for CryptoStream so it is flushed and dipsoed.
            return msEncrypt.ToArray();
        }
    }
}

Also, your decryption function is actually trying to encrypt the text a 2nd time, you need to pass the byte array in to the constructor of msDecrypt and put it in decryption mode.

public string DecryptStringFromBytes_Aes(byte[] cipherText, string Key)
{
    // Check arguments. 
    if (cipherText == null || cipherText.Length <= 0)
        throw new ArgumentNullException("cipherText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");

    // Create an Aes object 
    // with the specified key and IV. 

    // Create the streams used for decryption. 

    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Mode = CipherMode.CBC;
        aesAlg.Padding = PaddingMode.PKCS7;
        //Grab IV from ciphertext
        byte[] IV = new byte[16];
        Array.Copy(cipherText,0,IV,0,16);
        //Use the IV for the Salt
        byte[] theSalt = new byte[8];
        Array.Copy(IV,theSalt,8);
        Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
        byte[] aesKey = keyGen.GetBytes(16);
        aesAlg.Key = aesKey;

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

        //You can chain using statements like this to make the code easier to read.
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) //Notice this is Read mode not Write mode.
        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
        {
            //Decrypt the ciphertext
            return srDecrypt.ReadToEnd();
        }  
    }
}

There may be other errors with your code, but at least this gets you on the right track.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Thank you for the reply. But I think that there is a problem with this approach(correct me if I am wrong since I don't know a lot about streams in C#)... In the encryption process you encrypt the IV along with the message, so in the decryption process when the Array.Copy method is called the IV will not be the original one, but it will be the first 16 bytes of the ciphertext. So this will be a problem. Also when decrypting the ciphertext your code will decrypt the IV along with the message (easy to solve but still I don't know if there is a better way). – Geronimo Rodriguez Oct 20 '14 at 18:39
  • Is there a way in which I can use StreamReader/Writer and still provide the same functionality that my original code had regarding the IV? – Geronimo Rodriguez Oct 20 '14 at 18:41
  • Yes, I just consolidated your code, the Stream reader and writer has no effect on the IV portion. Just use a Binary Writer for your IV portion if you want (I just think it is rather inefficient way of doing it as you are creating extra byte[] in memory behind the sciences that are unnecessary.). Just delete `msEncrypt.Write(IV, 0, IV.Length);` and then put back in your old block where you copied the IV in. – Scott Chamberlain Oct 20 '14 at 19:23
2

Looking at your lines

public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
byte[] plainTextInBytes = Convert.FromBase64String(plainText);

Arbitrary plain text will not be a base 64 encoded string. Even if it is supposed to be base 64 encoded text, your error message indicates that the length is not divisible by 4

FormatException
The length of s, ignoring white-space characters, is not zero or a multiple of 4. -or- The format of s is invalid. s contains a non-base-64 character, more than two padding characters, or a > non-white space-character among the padding characters.

http://msdn.microsoft.com/en-us/library/system.convert.frombase64string(v=vs.110).aspx

If it is a base 64 encoded string, you need to pad it accorgingly

http://en.wikipedia.org/wiki/Base64

Eric J.
  • 147,927
  • 63
  • 340
  • 553