Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Base32, base32url, base64url, and z-base-32 Encoding and Decoding in .NET

4.86/5 (10 votes)
9 Mar 2019CPOL2 min read 2  
Base32, base32url, base64url, and z-base-32 Encoding and Decoding in .NET

Update

Moved to and maintained on github (https://github.com/ericlau-solid/Base3264-UrlEncoder), published on nuget (https://www.nuget.org/packages/Base3264-UrlEncoder/).

Introduction

Standards based implementations of various Base32 and Base64 encoding/decoding methods. These are designed to encode binary data to plain text, and decode the resulting text back to the original binary. This is useful when you need to transfer binary data through technologies that only support text (such as including binary security tokens in URLs).

Base32Url encodes with only the characters A to Z and 2 to 7. No hyphens, underscores, pluses, slashes or equals are used, making it usable as a URL token in almost all circumstances. Base32Url also supports custom alphabets. A custom case sensitive alphabet with only consonant (non vowel) characters can be used to ensure your tokens do not contain accidental profanities. The following is an example that avoids vowels, the letter L and has no numeric characters: BCDFGHKMNPQRSTVWXYZbcdfghkmnpqrs.

Base64Url is more compact than Base32Url and it is almost always usable as a URL token or file-name. The only non alpha-numeric characters Base64Url contains are the hyphen (-) and underscore (_) characters, neither of these need further encoding for use in URLs or file-names.

Code Maintained on GitHub and Published at NuGet

Thanks @ericlau-solid on github.

Base32Url (Encoder / Decoder)

  • The default mode for the Base32 encoder/decoder is Base32Url. This uses the standard Base32 alphabet encoding but omits padding characters and is case insensitive.
  • Supports standard Base32 with padding characters (=) per Base32 from RFC 4648.
  • Supports the Base32 extension / alternate alphabet z-base-32.

Base64Url (Encoder / Decoder)

  • Based on the standard .NET Base64 encoder
  • Uses the URL-Safe alternative Base64 alphabet from RFC 4648
  • This is not the same as Microsoft’s HttpServerUtility.UrlTokenEncode.

Further Information and Usage

There are other implementations of base32 encoding out there but I feel the code of this base32 implementation is much simpler (far less code involved in the bit shifting calculations).

The base64 implementation I have here is a little hackish, but a far better option than the one you get from Microsoft.

The result you get from HttpServerUtility.UrlTokenEncode is essentially base64url, but instead of truncating the padding, they append a digit (of 0, 1 or 2) indicating the number of padding characters removed.

Usage:
 
Base32Url.ToBase32String(Encoding.ASCII.GetBytes("Hello World!"));
 
JBSWY3DPEBLW64TMMQQQ
 
var b32 = new Base32Url(true); // Base32Url(bool usePadding)
b32.Encode(Encoding.ASCII.GetBytes("Hello World!"));
 
JBSWY3DPEBLW64TMMQQQ====

For more information about the standards involved, please see RFC 4648 http://tools.ietf.org/html/rfc4648[^].

Wikipedia also has good information. Please see http://en.wikipedia.org/wiki/Base32[^] and or http://en.wikipedia.org/wiki/Base64[^].

Written by Mhano Harkness - http://mhano.com[^]

Base32Url

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace MhanoHarkness
{
	/// <summary>
	/// Base32Url is a standard base 32 encoder/decoder except that padding turned
	/// off and it is not case sensitive (by default).
	/// 
	/// If you turn padding and case sensitivity on it becomes a standard base32
	/// encoder/decoder giving you 8 character chunks right padded with equals symbols.
	/// 
	/// If you leave padding off and use Base32Url.ZBase32Alphabet you
	/// get a z-base-32 compatible encoder/decoder.
	/// 
	/// See http://tools.ietf.org/html/rfc4648
	/// For more information see http://en.wikipedia.org/wiki/Base32
	/// </summary>
	public class Base32Url
	{
		public const char StandardPaddingChar = '=';
		public const string Base32StandardAlphabet  = 
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
		public const string ZBase32Alphabet = 
		"ybndrfg8ejkmcpqxot1uwisza345h769";
		public char PaddingChar;
		public bool UsePadding;
		public bool IsCaseSensitive;
		public bool IgnoreWhiteSpaceWhenDecoding;
		private readonly string _alphabet;
		private Dictionary<string, uint> _index;
		// alphabets may be used with varying case sensitivity, 
		// thus index must not ignore case
		private static Dictionary<string, Dictionary<string, 
		uint>> _indexes = new Dictionary<string, Dictionary<string, 
		uint>>(2, StringComparer.InvariantCulture);
		/// <summary>
		/// Create case insensitive encoder/decoder 
		/// using the standard base32 alphabet without padding.
		/// White space is not permitted when decoding (not ignored).
		/// </summary>
		public Base32Url() : this(false, false, false, Base32StandardAlphabet) { }
		/// <summary>
		/// Create case insensitive encoder/decoder using the standard base32 alphabet.
		/// White space is not permitted when decoding (not ignored).
		/// </summary>
		/// <param name="padding">
		/// Require/use padding characters?</param>
		public Base32Url(bool padding) : 
		this(padding, false, false, Base32StandardAlphabet) { }
		/// <summary>
		/// Create encoder/decoder using the standard base32 alphabet.
		/// White space is not permitted when decoding (not ignored).
		/// </summary>
		/// <param name="padding">
		/// Require/use padding characters?</param>
		/// <param name="caseSensitive">
		/// Be case sensitive when decoding?</param>
		public Base32Url(bool padding, bool caseSensitive) : 
		this(padding, caseSensitive, false, Base32StandardAlphabet) { }
		/// <summary>
		/// Create encoder/decoder using the standard base32 alphabet.
		/// </summary>
		/// <param name="padding">
		/// Require/use padding characters?</param>
		/// <param name="caseSensitive">
		/// Be case sensitive when decoding?</param>
		/// <param name="ignoreWhiteSpaceWhenDecoding">
		/// Ignore / allow white space when decoding?</param>
		public Base32Url(bool padding, bool caseSensitive, 
		bool ignoreWhiteSpaceWhenDecoding) : this(padding, caseSensitive, 
		ignoreWhiteSpaceWhenDecoding, Base32StandardAlphabet) { }
		/// <summary>
		/// Create case insensitive encoder/decoder with alternative alphabet and no padding.
		/// White space is not permitted when decoding (not ignored).
		/// </summary>
		/// <param name="alternateAlphabet">
		/// Alphabet to use (such as Base32Url.ZBase32Alphabet)</param>
		public Base32Url(string alternateAlphabet) : 
		this(false, false, false, alternateAlphabet) { }
		/// <summary>
		/// Create the encoder/decoder specifying all options manually.
		/// </summary>
		/// <param name="padding">Require/use padding characters?</param>
		/// <param name="caseSensitive">
		/// Be case sensitive when decoding?</param>
		/// <param name="ignoreWhiteSpaceWhenDecoding">
		/// Ignore / allow white space when decoding?</param>
		/// <param name="alternateAlphabet">Alphabet to use 
		/// (such as Base32Url.ZBase32Alphabet, Base32Url.Base32StandardAlphabet 
		/// or your own custom 32 character alphabet string)</param>
		public Base32Url(bool padding, bool caseSensitive, 
		bool ignoreWhiteSpaceWhenDecoding, string alternateAlphabet)
		{
			if (alternateAlphabet.Length != 32)
			{
				throw new ArgumentException("Alphabet must be 
				exactly 32 characters long for base 32 encoding.");
			}
			PaddingChar = StandardPaddingChar;
			UsePadding = padding;
			IsCaseSensitive = caseSensitive;
			IgnoreWhiteSpaceWhenDecoding = ignoreWhiteSpaceWhenDecoding;
			_alphabet = alternateAlphabet;
		}
		/// <summary>
		/// Decode a base32 string to a byte[] using the default options
		/// (case insensitive without padding using the standard base32 alphabet from rfc4648).
		/// White space is not permitted (not ignored).
		/// Use alternative constructors for more options.
		/// </summary>
		public static byte[] FromBase32String(string input)
		{
			return new Base32Url().Decode(input);
		}
		/// <summary>
		/// Encode a base32 string from a byte[] using the default options
		/// (case insensitive without padding 
		/// using the standard base32 alphabet from rfc4648).
		/// Use alternative constructors for more options.
		/// </summary>
		public static string ToBase32String(byte[] data)
		{
			return new Base32Url().Encode(data);
		}
		public string Encode(byte[] data)
		{
			StringBuilder result = new StringBuilder
			(Math.Max((int)Math.Ceiling(data.Length * 8 / 5.0), 1));
			byte[] emptyBuff = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
			byte[] buff = new byte[8];
			// take input five bytes at a time to chunk it up for encoding
			for (int i = 0; i < data.Length; i += 5)
			{
				int bytes = Math.Min(data.Length - i, 5);
				// parse five bytes at a time using an 8 byte ulong
				Array.Copy(emptyBuff, buff, emptyBuff.Length);
				Array.Copy(data, i, buff, buff.Length - (bytes+1), bytes);
				Array.Reverse(buff);
				ulong val = BitConverter.ToUInt64(buff, 0);
				for (int bitOffset = ((bytes+1) * 8) - 5; bitOffset > 3; bitOffset -= 5)
				{
					result.Append(_alphabet[(int)((val >> bitOffset) & 0x1f)]);
				}
			}
			if (UsePadding)
			{
				result.Append(string.Empty.PadRight((result.Length % 8) == 0 ? 
				0 : (8 - (result.Length % 8)), PaddingChar));
			}
			return result.ToString();
		}
		public byte[] Decode(string input)
		{
			if (IgnoreWhiteSpaceWhenDecoding)
			{
				input = Regex.Replace(input, "\\s+", "");
			}
			if (UsePadding)
			{
				if (input.Length % 8 != 0)
				{
					throw new ArgumentException
					("Invalid length for a base32 string with padding.");
				}
				input = input.TrimEnd(PaddingChar);
			}
			// index the alphabet for decoding only when needed
			EnsureAlphabetIndexed();
			MemoryStream ms = new MemoryStream
			(Math.Max((int)Math.Ceiling(input.Length * 5 / 8.0), 1));
			// take input eight bytes at a time to chunk it up for encoding
			for (int i = 0; i < input.Length; i += 8)
			{
				int chars = Math.Min(input.Length - i, 8);
				ulong val = 0;
				int bytes = (int)Math.Floor(chars * (5 / 8.0));
				for (int charOffset = 0; charOffset < chars; charOffset++)
				{
					uint cbyte;
					if (!_index.TryGetValue(input.Substring(i + charOffset, 1), out cbyte))
					{
						throw new ArgumentException
						("Invalid character '" + 
						input.Substring(i + charOffset, 1) + 
						"' in base32 string, valid characters are: " + _alphabet);
					}
					val |= (((ulong)cbyte) << 
					((((bytes + 1) * 8) - (charOffset * 5)) - 5));
				}
				byte[] buff = BitConverter.GetBytes(val);
				Array.Reverse(buff);
				ms.Write(buff, buff.Length - (bytes + 1), bytes);
			}
			return ms.ToArray();
		}
		private void EnsureAlphabetIndexed()
		{
			if (_index == null)
			{
				Dictionary<string, uint> cidx;
				string indexKey = (IsCaseSensitive ? "S" : "I") + _alphabet;
				if (!_indexes.TryGetValue(indexKey, out cidx))
				{
					lock (_indexes)
					{
						if (!_indexes.TryGetValue(indexKey, out cidx))
						{
							cidx = new Dictionary<string, uint>
							(_alphabet.Length, IsCaseSensitive ? 
							StringComparer.InvariantCulture : 
                                        StringComparer.InvariantCultureIgnoreCase);
							for (int i = 0; i < _alphabet.Length; i++)
							{
								cidx[_alphabet.Substring(i, 1)] = (uint) i;
							}
							_indexes.Add(indexKey, cidx);
						}
					}
				}
				_index = cidx;
			}
		}
	}
}

Base64Url

using System;
using System.Text;
namespace MhanoHarkness
{
	/// <summary>
	/// Modified Base64 for URL applications ('base64url' encoding)
	/// 
	/// See http://tools.ietf.org/html/rfc4648
	/// For more information see http://en.wikipedia.org/wiki/Base64
	/// </summary>
	public class Base64Url
	{
		/// <summary>
		/// Modified Base64 for URL applications ('base64url' encoding)
		/// 
		/// See http://tools.ietf.org/html/rfc4648
		/// For more information see http://en.wikipedia.org/wiki/Base64
		/// </summary>
		/// <param name="input"></param>
		/// <returns>Input byte array converted to a base64ForUrl encoded string</returns>
		public static string ToBase64ForUrlString(byte[] input)
		{
			StringBuilder result = new StringBuilder(Convert.ToBase64String(input).TrimEnd('='));
			result.Replace('+', '-');
			result.Replace('/', '_');
			return result.ToString();
		}
		/// <summary>
		/// Modified Base64 for URL applications ('base64url' encoding)
		/// 
		/// See http://tools.ietf.org/html/rfc4648
		/// For more information see http://en.wikipedia.org/wiki/Base64
		/// </summary>
		/// <param name="base64ForUrlInput"></param>
		/// <returns>Input base64ForUrl encoded string as the original byte array</returns>
		public static byte[] FromBase64ForUrlString(string base64ForUrlInput)
		{
			int padChars = (base64ForUrlInput.Length%4) == 0 ? 0 : (4 - (base64ForUrlInput.Length%4));
			StringBuilder result = new StringBuilder(base64ForUrlInput, 
                                   base64ForUrlInput.Length + padChars);
			result.Append(String.Empty.PadRight(padChars, '='));
			result.Replace('-', '+');
			result.Replace('_', '/');
			return Convert.FromBase64String(result.ToString());
		}
	}
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)