Modern applications increasingly use plain text to store and share data, especially in XML and SOAP formats. However, binary data cannot be represented directly in plain text, so one popular method is to convert binary to Base64 format.

What is Base64?

Base64 converts binary data to plain text using 64 case-sensitive, printable ASCII characters: A-Z, a-z, 0-9, plus sign (+) and forward slash (/), and may be terminated with 0-2 “padding” characters represented by the equal sign (=). For example, the eight-byte binary data in hex “35 71 4d 8e 4c 5f db 42” converts to Base64 text as “NXFNjkxf20I=”.

.NET Convert Methods

Generally, to convert between Base64 and plain text, you should use the .NET methods Convert.ToBase64String and Convert.FromBase64String.

Custom Conversions

However, there may be instances when you want to modify the Base64 standard conversion behavior. For example, applications may use Base64 in file paths or URLs to represent globally unique IDs and other binary data. However, the forward slash is an invalid character in file paths. In URLs, the ‘+’ and ‘/’ characters translate into special percent-encoded hexadecimal sequences (‘+’ = ‘%2B’ and ‘/’ = ‘%2F’), and databases may choke on the % character because it represents a wildcard in ANSI SQL. Therefore, a modified “Base64 for URL” variant exists, where no ‘=’ padding is used, and the ‘+’ and ‘/’ characters are replaced with the hyphen ‘-‘ and underscore ‘_’, respectively.

Base64 Conversion Code

Below is C# code for a Base64 encoder that you can customize to your needs. It is based on a CodeProject article but improved to be significantly more efficient. Even so, the static ToBase64 method that converts from an array of bytes to a Base64 string is about 2-3 slower than .NET’s Convert.ToBase64String. That’s only about one extra second per 100K iterations. Please comment if you can see any optimizations that I can apply. The FromBase64 method converts from a Base64 string to an array of bytes and its performance is on par with .NET’s Convert.FromBase64String.

Base64 Encoder

Below is the C# code for the custom Base64 encoder:

using System;

namespace Base64
{
    class Base64Encoder
    {
        static public string ToBase64( byte[] data )
        {
            int length = data == null ? 0 : data.Length;
            if (length == 0)
                return String.Empty;

            int padding = length % 3;
            if (padding > 0)
                padding = 3 - padding;
            int blocks = (length - 1) / 3 + 1;

            char[] s = new char[blocks * 4];

            for (int i = 0; i < blocks; i++)
            {
                bool finalBlock = i == blocks - 1;
                bool pad2 = false;
                bool pad1 = false;
                if (finalBlock)
                {
                    pad2 = padding == 2;
                    pad1 = padding > 0;
                }

                int index = i * 3;
                byte b1 = data[index];
                byte b2 = pad2 ? (byte)0 : data[index + 1];
                byte b3 = pad1 ? (byte)0 : data[index + 2];

                byte temp1 = (byte)((b1 & 0xFC) >> 2);

                byte temp = (byte)((b1 & 0x03) << 4);
                byte temp2 = (byte)((b2 & 0xF0) >> 4);
                temp2 += temp;

                temp = (byte)((b2 & 0x0F) << 2);
                byte temp3 = (byte)((b3 & 0xC0) >> 6);
                temp3 += temp;

                byte temp4 = (byte)(b3 & 0x3F);

                index = i * 4;
                s[index] = SixBitToChar( temp1 );
                s[index+1] = SixBitToChar( temp2 );
                s[index+2] = pad2 ? '=' : SixBitToChar( temp3 );
                s[index+3] = pad1 ? '=' : SixBitToChar( temp4 );
            }

            return new string( s );
        }
        static private char SixBitToChar( byte b )
        {
            char c;
            if (b < 26)
            {
                c = (char)((int)b + (int)'A');
            }
            else if (b < 52)
            {
                c = (char)((int)b - 26 + (int)'a');
            }
            else if (b < 62)
            {
                c = (char)((int)b - 52 + (int)'0');
            }
            else if (b == 62)
            {
                c = s_CharPlusSign;
            }
            else
            {
                c = s_CharSlash;
            }
            return c;
        }

        static public byte[] FromBase64( string s )
        {
            int length = s == null ? 0 : s.Length;
            if (length == 0)
                return new byte[0];

            int padding = 0;
            if (length > 2 && s[length - 2] == '=')
                padding = 2;
            else if (length > 1 && s[length - 1] == '=')
                padding = 1;

            int blocks = (length - 1) / 4 + 1;
            int bytes = blocks * 3;

            byte[] data = new byte[bytes-padding];

            for (int i = 0; i < blocks; i++)
            {
                bool finalBlock = i == blocks - 1;
                bool pad2 = false;
                bool pad1 = false;
                if (finalBlock)
                {
                    pad2 = padding == 2;
                    pad1 = padding > 0;
                }

                int index = i * 4;
                byte temp1 = CharToSixBit( s[index] );
                byte temp2 = CharToSixBit( s[index + 1] );
                byte temp3 = CharToSixBit( s[index + 2] );
                byte temp4 = CharToSixBit( s[index + 3] );

                byte b = (byte)(temp1 << 2);
                byte b1 = (byte)((temp2 & 0x30) >> 4);
                b1 += b;

                b = (byte)((temp2 & 0x0F) << 4);
                byte b2 = (byte)((temp3 & 0x3C) >> 2);
                b2 += b;

                b = (byte)((temp3 & 0x03) << 6);
                byte b3 = temp4;
                b3 += b;

                index = i * 3;
                data[index] = b1;
                if (!pad2)
                    data[index + 1] = b2;
                if (!pad1)
                    data[index + 2] = b3;
            }

            return data;
        }
        static private byte CharToSixBit( char c )
        {
            byte b;
            if (c >= 'A' && c <= 'Z')
            {
                b = (byte)((int)c - (int)'A');
            }
            else if (c >= 'a' && c <= 'z')
            {
                b = (byte)((int)c - (int)'a' + 26);
            }
            else if (c >= '0' && c <= '9')
            {
                b = (byte)((int)c - (int)'0' + 52);
            }
            else if (c == s_CharPlusSign)
            {
                b = (byte)62;
            }
            else
            {
                b = (byte)63;
            }
            return b;
        }

        static private char s_CharPlusSign = '+';
        /// <summary>
        /// Gets or sets the plus sign character.
        /// Default is '+'.
        /// </summary>
        static public char CharPlusSign
        {
            get
            {
                return s_CharPlusSign;
            }
            set
            {
                s_CharPlusSign = value;
            }
        }
        static private char s_CharSlash = '/';
        /// <summary>
        /// Gets or sets the slash character.
        /// Default is '/'.
        /// </summary>
        static public char CharSlash
        {
            get
            {
                return s_CharSlash;
            }
            set
            {
                s_CharSlash = value;
            }
        }
    }
}

Console Test Program

Here is a console test program that compares the custom encoder with the .NET Convert methods:

using System;

namespace Base64
{
    class Program
    {
        static void Main( string[] args )
        {
            Random r = new Random();
            int count = 21;
            byte[][] data = new byte[count][];
            string[] strings = new string[count];

            for (int i = 0; i < count; i++)
            {
                data[i] = new byte[i];
                for (int j = 0; j < i; j++)
                {
                    data[i][j] = (byte)r.Next( 255 );
                }
                string s1 = Convert.ToBase64String( data[i] );
                string s2 = Base64Encoder.ToBase64( data[i] );
                strings[i] = s1;
                string equal = String.Equals( s1, s2 ) ? "CORRECT" : "INCORRECT";
                Console.WriteLine( "{0}. {1}", i, equal );
                Console.WriteLine( "Convert.ToBase64String: {0}", s1 );
                Console.WriteLine( "Base64Encoder.ToBase64: {0}n", s2 );
            }

            for (int i = 0; i < count; i++)
            {
                byte[] d1 = Convert.FromBase64String( strings[i] );
                byte[] d2 = Base64Encoder.FromBase64( strings[i] );
                bool isEqual = false;
                int length = d1.Length;
                if (length == d2.Length)
                {
                    isEqual = true;
                    for (int j = 0; j < length; j++)
                    {
                        if (d1[j] != d2[j])
                        {
                            isEqual = false;
                            break;
                        }
                    }
                }
                string equal = isEqual ? "CORRECT" : "INCORRECT";
                Console.WriteLine( "{0}. {1}", i, equal );
                Console.Write( "Convert.FromBase64String: " );
                if (length > 0)
                {
                    for (int j = 0; j < length; j++)
                    {
                        Console.Write( "{0:x2} ", d1[j] );
                    }
                }
                Console.WriteLine();
                Console.Write( "Base64Encoder.FromBase64: " );
                length = d2.Length;
                if (length > 0)
                {
                    for (int j = 0; j < length; j++)
                    {
                        Console.Write( "{0:x2} ", d2[j] );
                    }
                }
                Console.WriteLine( "nn" );
            }

            int loops = 100000;
            DateTime t1 = DateTime.Now;
            for (int i = 0; i < loops; i++)
            {
                for (int j = 0; j < count; j++)
                {
                    string s1 = Convert.ToBase64String( data[j] );
                }
            }
            DateTime t2 = DateTime.Now;
            for (int i = 0; i < loops; i++)
            {
                for (int j = 0; j < count; j++)
                {
                    string s2 = Base64Encoder.ToBase64( data[j] );
                }
            }
            DateTime t3 = DateTime.Now;

            Console.WriteLine( "Convert.ToBase64String={0}nBase64Encoder.ToBase64={1}n",
                t2.Subtract( t1 ), t3.Subtract( t2 ) );

            t1 = DateTime.Now;
            for (int i = 0; i < loops; i++)
            {
                for (int j = 0; j < count; j++)
                {
                    byte[] d1 = Convert.FromBase64String( strings[j] );
                }
            }
            t2 = DateTime.Now;
            for (int i = 0; i < loops; i++)
            {
                for (int j = 0; j < count; j++)
                {
                    byte[] d2 = Base64Encoder.FromBase64( strings[j] );
                }
            }
            t3 = DateTime.Now;

            Console.WriteLine( "Convert.FromBase64String={0}nBase64Encoder.FromBase64={1}",
                t2.Subtract( t1 ), t3.Subtract( t2 ) );

            Console.ReadLine();
        }
    }
}

Test Program Output

Here is the test program console output, which of course will vary with each run. As an aside, check out the Base64 string generated for 2 random bytes of data: “Bug=”. These are the actual results, no kidding! (play eerie music here)

0. CORRECT
Convert.ToBase64String:
Base64Encoder.ToBase64:

1. CORRECT
Convert.ToBase64String: PQ==
Base64Encoder.ToBase64: PQ==

2. CORRECT
Convert.ToBase64String: Bug=
Base64Encoder.ToBase64: Bug=

3. CORRECT
Convert.ToBase64String: oFS0
Base64Encoder.ToBase64: oFS0

4. CORRECT
Convert.ToBase64String: 6pc1bA==
Base64Encoder.ToBase64: 6pc1bA==

5. CORRECT
Convert.ToBase64String: l36Di4A=
Base64Encoder.ToBase64: l36Di4A=

6. CORRECT
Convert.ToBase64String: o3YcyxrM
Base64Encoder.ToBase64: o3YcyxrM

7. CORRECT
Convert.ToBase64String: +4hLRYCoMA==
Base64Encoder.ToBase64: +4hLRYCoMA==

8. CORRECT
Convert.ToBase64String: NXFNjkxf20I=
Base64Encoder.ToBase64: NXFNjkxf20I=

9. CORRECT
Convert.ToBase64String: j16JCNJ48gH0
Base64Encoder.ToBase64: j16JCNJ48gH0

10. CORRECT
Convert.ToBase64String: +ZtRerYsZzeUtg==
Base64Encoder.ToBase64: +ZtRerYsZzeUtg==

11. CORRECT
Convert.ToBase64String: QVVZW9MMumLDHwg=
Base64Encoder.ToBase64: QVVZW9MMumLDHwg=

12. CORRECT
Convert.ToBase64String: MiSvPhMXkcNGVAiG
Base64Encoder.ToBase64: MiSvPhMXkcNGVAiG

13. CORRECT
Convert.ToBase64String: VUvjV7R9ROVWtqiZ6w==
Base64Encoder.ToBase64: VUvjV7R9ROVWtqiZ6w==

14. CORRECT
Convert.ToBase64String: NgO1+xgVL+HrxnegO6I=
Base64Encoder.ToBase64: NgO1+xgVL+HrxnegO6I=

15. CORRECT
Convert.ToBase64String: FNRzTmE5zQMQ7rMF4344
Base64Encoder.ToBase64: FNRzTmE5zQMQ7rMF4344

16. CORRECT
Convert.ToBase64String: sHp7FVHcFNvGLT7YpGiDbA==
Base64Encoder.ToBase64: sHp7FVHcFNvGLT7YpGiDbA==

17. CORRECT
Convert.ToBase64String: tXnZLxHiaEZgHTLxxUgTMa8=
Base64Encoder.ToBase64: tXnZLxHiaEZgHTLxxUgTMa8=

18. CORRECT
Convert.ToBase64String: qTtM+4voxQD3rCAjXymajIL8
Base64Encoder.ToBase64: qTtM+4voxQD3rCAjXymajIL8

19. CORRECT
Convert.ToBase64String: iglPJ80RNLM0qiEVfRkNKfotNg==
Base64Encoder.ToBase64: iglPJ80RNLM0qiEVfRkNKfotNg==

20. CORRECT
Convert.ToBase64String: cCqPFC8YNkgjAfOXZUJLhydggW0=
Base64Encoder.ToBase64: cCqPFC8YNkgjAfOXZUJLhydggW0=

0. CORRECT
Convert.FromBase64String:
Base64Encoder.FromBase64:

1. CORRECT
Convert.FromBase64String: 3d
Base64Encoder.FromBase64: 3d

2. CORRECT
Convert.FromBase64String: de a4
Base64Encoder.FromBase64: de a4

3. CORRECT
Convert.FromBase64String: a0 54 b4
Base64Encoder.FromBase64: a0 54 b4

4. CORRECT
Convert.FromBase64String: ea 97 35 6c
Base64Encoder.FromBase64: ea 97 35 6c

5. CORRECT
Convert.FromBase64String: 97 7e 83 8b 80
Base64Encoder.FromBase64: 97 7e 83 8b 80

6. CORRECT
Convert.FromBase64String: a3 76 1c cb 1a cc
Base64Encoder.FromBase64: a3 76 1c cb 1a cc

7. CORRECT
Convert.FromBase64String: fb 88 4b 45 80 a8 30
Base64Encoder.FromBase64: fb 88 4b 45 80 a8 30

8. CORRECT
Convert.FromBase64String: 35 71 4d 8e 4c 5f db 42
Base64Encoder.FromBase64: 35 71 4d 8e 4c 5f db 42

9. CORRECT
Convert.FromBase64String: 8f 5e 89 08 d2 78 f2 01 f4
Base64Encoder.FromBase64: 8f 5e 89 08 d2 78 f2 01 f4

10. CORRECT
Convert.FromBase64String: f9 9b 51 7a b6 2c 67 37 94 b6
Base64Encoder.FromBase64: f9 9b 51 7a b6 2c 67 37 94 b6

11. CORRECT
Convert.FromBase64String: 41 55 59 5b d3 0c ba 62 c3 1f 08
Base64Encoder.FromBase64: 41 55 59 5b d3 0c ba 62 c3 1f 08

12. CORRECT
Convert.FromBase64String: 32 24 af 3e 13 17 91 c3 46 54 08 86
Base64Encoder.FromBase64: 32 24 af 3e 13 17 91 c3 46 54 08 86

13. CORRECT
Convert.FromBase64String: 55 4b e3 57 b4 7d 44 e5 56 b6 a8 99 eb
Base64Encoder.FromBase64: 55 4b e3 57 b4 7d 44 e5 56 b6 a8 99 eb

14. CORRECT
Convert.FromBase64String: 36 03 b5 fb 18 15 2f e1 eb c6 77 a0 3b a2
Base64Encoder.FromBase64: 36 03 b5 fb 18 15 2f e1 eb c6 77 a0 3b a2

15. CORRECT
Convert.FromBase64String: 14 d4 73 4e 61 39 cd 03 10 ee b3 05 e3 7e 38
Base64Encoder.FromBase64: 14 d4 73 4e 61 39 cd 03 10 ee b3 05 e3 7e 38

16. CORRECT
Convert.FromBase64String: b0 7a 7b 15 51 dc 14 db c6 2d 3e d8 a4 68 83 6c
Base64Encoder.FromBase64: b0 7a 7b 15 51 dc 14 db c6 2d 3e d8 a4 68 83 6c

17. CORRECT
Convert.FromBase64String: b5 79 d9 2f 11 e2 68 46 60 1d 32 f1 c5 48 13 31 af
Base64Encoder.FromBase64: b5 79 d9 2f 11 e2 68 46 60 1d 32 f1 c5 48 13 31 af

18. CORRECT
Convert.FromBase64String: a9 3b 4c fb 8b e8 c5 00 f7 ac 20 23 5f 29 9a 8c 82 fc
Base64Encoder.FromBase64: a9 3b 4c fb 8b e8 c5 00 f7 ac 20 23 5f 29 9a 8c 82 fc

19. CORRECT
Convert.FromBase64String: 8a 09 4f 27 cd 11 34 b3 34 aa 21 15 7d 19 0d 29 fa 2d 36
Base64Encoder.FromBase64: 8a 09 4f 27 cd 11 34 b3 34 aa 21 15 7d 19 0d 29 fa 2d 36

20. CORRECT
Convert.FromBase64String: 70 2a 8f 14 2f 18 36 48 23 01 f3 97 65 42 4b 87 27 60 81 6d
Base64Encoder.FromBase64: 70 2a 8f 14 2f 18 36 48 23 01 f3 97 65 42 4b 87 27 60 81 6d

Convert.ToBase64String=00:00:00.6406250
Base64Encoder.ToBase64=00:00:01.6250000

Convert.FromBase64String=00:00:01.5625000
Base64Encoder.FromBase64=00:00:01.5625000