How to apply padding for Base64

2020-08-09 07:05发布

问题:

I have following code that works fine when I use a four letter word as input, E.g. “Test”. When the input is not a multiple of 4, it fails E.g. “MyTest”.

Eexception: Invalid length for a Base-64 char array.

QUESTIONS

  1. Is it guaranteed that the encrypted result will be always compatible to a Unicode string (without any loss). If yes I can use UTF encoding instead of Base64? What's the difference between UTF8/UTF16 and Base64 in terms of encoding
  2. How can add padding so that we get correct result (after decryption) even if the input is not a multiple of 4?

MAIN PRGORAM

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


        byte[] byteValForString = Convert.FromBase64String(inputValue);
        EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
        EncryptResult encyptedValue = new EncryptResult();
        string resultingIV = "4uy34C9sqOC9rbV4GD8jrA==";
        if (String.Equals(resultingIV,result.IV))
        {
            int x = 0;
        }

        encyptedValue.IV = resultingIV;
        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 Crypto 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;
            aesProvider.IV = Convert.FromBase64String("4uy34C9sqOC9rbV4GD8jrA==");
            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

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

REFERENCES:

  1. How to create byte[] with length 16 using FromBase64String
  2. Getting incorrect decryption value using AesCryptoServiceProvider

回答1:

Base64 is a way of representing binary values as text so that you do not conflict with common control codes like \x0A for newline or \0 for a string terminator. It is NOT for turning typed text in to binary.

Here is how you should be passing the text in and getting it back out. You can replace UTF8 with whatever encoding you want, but you will need to make sure the Encoding.Whatever.GetBytes is the same encoding as the Encoding.Whatever.GetString

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

        //Turns our text in to binary data
        byte[] byteValForString = Encoding.UTF8.GetBytes(inputValue);

        EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
        EncryptResult encyptedValue = new EncryptResult();

        //(Snip)

        encyptedValue.IV = resultingIV;
        encyptedValue.EncryptedMsg = result.EncryptedMsg;

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

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

        Console.ReadLine();
    }
}


回答2:

If you are encrypting, then encoding Base64 for me doesn't add anything useful, instead it brings the problems you face.

As for the padding, a solution i have seen is to create a new byte[] that is indeed a multiple of 4 and copy the source byte[] to that new byte[].

So, something like this:

 if (rawdata.Length % 16 !=0)
 {
      newSource = new byte[source.Length + 16 - source.Length % 16];
      Array.Copy(source, newSource, source.Length);
 }