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
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace MhanoHarkness
{
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;
private static Dictionary<string, Dictionary<string,
uint>> _indexes = new Dictionary<string, Dictionary<string,
uint>>(2, StringComparer.InvariantCulture);
public Base32Url() : this(false, false, false, Base32StandardAlphabet) { }
public Base32Url(bool padding) :
this(padding, false, false, Base32StandardAlphabet) { }
public Base32Url(bool padding, bool caseSensitive) :
this(padding, caseSensitive, false, Base32StandardAlphabet) { }
public Base32Url(bool padding, bool caseSensitive,
bool ignoreWhiteSpaceWhenDecoding) : this(padding, caseSensitive,
ignoreWhiteSpaceWhenDecoding, Base32StandardAlphabet) { }
public Base32Url(string alternateAlphabet) :
this(false, false, false, alternateAlphabet) { }
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;
}
public static byte[] FromBase32String(string input)
{
return new Base32Url().Decode(input);
}
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];
for (int i = 0; i < data.Length; i += 5)
{
int bytes = Math.Min(data.Length - i, 5);
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);
}
EnsureAlphabetIndexed();
MemoryStream ms = new MemoryStream
(Math.Max((int)Math.Ceiling(input.Length * 5 / 8.0), 1));
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
{
public class Base64Url
{
public static string ToBase64ForUrlString(byte[] input)
{
StringBuilder result = new StringBuilder(Convert.ToBase64String(input).TrimEnd('='));
result.Replace('+', '-');
result.Replace('/', '_');
return result.ToString();
}
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());
}
}
}