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: oFS04. CORRECT
Convert.ToBase64String: 6pc1bA==
Base64Encoder.ToBase64: 6pc1bA==5. CORRECT
Convert.ToBase64String: l36Di4A=
Base64Encoder.ToBase64: l36Di4A=6. CORRECT
Convert.ToBase64String: o3YcyxrM
Base64Encoder.ToBase64: o3YcyxrM7. CORRECT
Convert.ToBase64String: +4hLRYCoMA==
Base64Encoder.ToBase64: +4hLRYCoMA==8. CORRECT
Convert.ToBase64String: NXFNjkxf20I=
Base64Encoder.ToBase64: NXFNjkxf20I=9. CORRECT
Convert.ToBase64String: j16JCNJ48gH0
Base64Encoder.ToBase64: j16JCNJ48gH010. CORRECT
Convert.ToBase64String: +ZtRerYsZzeUtg==
Base64Encoder.ToBase64: +ZtRerYsZzeUtg==11. CORRECT
Convert.ToBase64String: QVVZW9MMumLDHwg=
Base64Encoder.ToBase64: QVVZW9MMumLDHwg=12. CORRECT
Convert.ToBase64String: MiSvPhMXkcNGVAiG
Base64Encoder.ToBase64: MiSvPhMXkcNGVAiG13. 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: FNRzTmE5zQMQ7rMF434416. CORRECT
Convert.ToBase64String: sHp7FVHcFNvGLT7YpGiDbA==
Base64Encoder.ToBase64: sHp7FVHcFNvGLT7YpGiDbA==17. CORRECT
Convert.ToBase64String: tXnZLxHiaEZgHTLxxUgTMa8=
Base64Encoder.ToBase64: tXnZLxHiaEZgHTLxxUgTMa8=18. CORRECT
Convert.ToBase64String: qTtM+4voxQD3rCAjXymajIL8
Base64Encoder.ToBase64: qTtM+4voxQD3rCAjXymajIL819. 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: 3d2. CORRECT
Convert.FromBase64String: de a4
Base64Encoder.FromBase64: de a43. CORRECT
Convert.FromBase64String: a0 54 b4
Base64Encoder.FromBase64: a0 54 b44. CORRECT
Convert.FromBase64String: ea 97 35 6c
Base64Encoder.FromBase64: ea 97 35 6c5. CORRECT
Convert.FromBase64String: 97 7e 83 8b 80
Base64Encoder.FromBase64: 97 7e 83 8b 806. CORRECT
Convert.FromBase64String: a3 76 1c cb 1a cc
Base64Encoder.FromBase64: a3 76 1c cb 1a cc7. CORRECT
Convert.FromBase64String: fb 88 4b 45 80 a8 30
Base64Encoder.FromBase64: fb 88 4b 45 80 a8 308. CORRECT
Convert.FromBase64String: 35 71 4d 8e 4c 5f db 42
Base64Encoder.FromBase64: 35 71 4d 8e 4c 5f db 429. CORRECT
Convert.FromBase64String: 8f 5e 89 08 d2 78 f2 01 f4
Base64Encoder.FromBase64: 8f 5e 89 08 d2 78 f2 01 f410. CORRECT
Convert.FromBase64String: f9 9b 51 7a b6 2c 67 37 94 b6
Base64Encoder.FromBase64: f9 9b 51 7a b6 2c 67 37 94 b611. 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 0812. 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 8613. 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 eb14. 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 a215. 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 3816. 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 6c17. 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 af18. 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 fc19. 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 3620. 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 6dConvert.ToBase64String=00:00:00.6406250
Base64Encoder.ToBase64=00:00:01.6250000Convert.FromBase64String=00:00:01.5625000
Base64Encoder.FromBase64=00:00:01.5625000
Hi – This is a gret post that I have found extremely helpful.
I am using Base 64 to create shorter URLs as described, and I’m not too familiar with all the maths of bytes and bases.
Public Shared Function encUC(ByVal uniqueCode As String) As String
Dim val As Int64 = Convert.ToInt64(uniqueCode)
Dim retVal As String
Dim tinyByte() = System.BitConverter.GetBytes(val)
‘NB THIS WILL WORK FOR A MAX OF 12 DIGIT CODES
‘ReDim Preserve tinyByte(4)
Dim tinyCode = Base64.Base64Encoder.ToBase64(tinyByte)
retVal = Replace(tinyCode, “=”, “_”)
retVal = Replace(retVal, “/”, “-“)
retVal = Replace(retVal, “+”, “|”)
Return retVal
End Function
When I encode it seems to not give me the least possible characters in the base64 string
eg
1000001728 encodes as wNCaOwAAAAA_
So i have shortened the byte array :
ReDim Preserve tinyByte(4)
Which is fine as my codes are a max of 12 digits, but not very elegant.
Can you suggest anything?
ALSO
When I decode a 32 bit integer (8 digit code)
Public Shared Function decUC(ByVal encCode As String) As String
Dim repCode As String = encCode
repCode = Replace(repCode, “_”, “=”)
repCode = Replace(repCode, “-“, “/”)
repCode = Replace(repCode, “|”, “+”)
Dim origByte(7) As Byte
origByte = Base64.Base64Encoder.FromBase64(repCode)
ReDim Preserve origByte(7)
Return System.BitConverter.ToInt64(origByte, 0)
End Function
if I don’t use ReDim Preserve origByte(7) it gives me an exception on the
Return System.BitConverter.ToInt64(origByte, 0) (array to short)
As I won’t always know if i’m getting an 8 or 12 digital code – I want my functions to cater for both. Any ideas?
My thanks again for a really great post that ended hours of midnight hunting 🙂
I think you could replace the method
“static private char SixBitToChar( byte b )”
for an array.
It should be faster. It has no method call overhead, no ifs, no calculations, it is readonly and it only implies an access operation [].
string Dict = “ABCDE….abc…etc”
So instead of SixBitToChar(b)
you should do Dict[b]
I almost forgot, It could also fit into the system cache!
John’s method will be faster and let someone define arbitrary mappings. Unfortunately, you can’t beat .NET’s implementation since its written in C++ and is unsafe code. You can use .NET Reflector to look at the Convert.ToBase64String method yourself. Microsoft can get away with that because .NET assemblies are fully trusted. When you think about it, C++ has some great and totally unsafe ways of doing this conversion.
On the whole though, unless you are converting huge amounts of these things (maybe to mime encode a big binary) you don’t need a lot of speed.
Thanks for this code, it gives me a head start on my own implementation.
I wanted to make you aware of a optimized version that I made and that is available on github.
https://github.com/ramonsmits/Base64Encoder
why byte temp1 = (byte)((b1 & 0xFC) >> 2);
why this addres 0xFC