using System.Collections.Generic;
using System.Text.RegularExpressions;
public static void Main()
Console.WriteLine(GetReference(long.MinValue,"HUGHES"));
Console.WriteLine(GetReference(long.MaxValue,"HUGHES"));
Console.WriteLine(GetReference(long.MinValue,"HUGHES",rand.Next().ToString()));
Console.WriteLine(GetReference(long.MaxValue,"HUGHES",rand.Next().ToString()));
public static string GetReference(long id, params string[] prefixes)
var b32 = new Base32Url();
var reference = new StringBuilder();
foreach(var prefix in prefixes){
reference.Append(prefix.Substring(0,1));
reference.Append(b32.Encode(BitConverter.GetBytes(id)));
return reference.ToString();
public const char StandardPaddingChar = '=';
public const string Base32StandardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
public const string ZBase32Alphabet = "ybndrfg8ejkmcpqxot1uwisza345h769";
private static readonly Dictionary<string, Dictionary<string, uint>> _indexes = new Dictionary<string, Dictionary<string, uint>>(2, StringComparer.InvariantCulture);
private readonly string _alphabet;
public char PaddingChar { get; set; }
public bool UsePadding { get; set; }
public bool IsCaseSensitive { get; set; }
public bool IgnoreWhiteSpaceWhenDecoding { get; set; }
private Dictionary<string, uint> _index;
: 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;
IsCaseSensitive = caseSensitive;
IgnoreWhiteSpaceWhenDecoding = ignoreWhiteSpaceWhenDecoding;
_alphabet = alternateAlphabet;
public string Encode(byte[] data)
var result = new StringBuilder(Math.Max((int)Math.Ceiling(data.Length * 8 / 5.0), 1));
var emptyBuff = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
for (var i = 0; i < data.Length; i += 5)
var bytes = Math.Min(data.Length - i, 5);
Array.Copy(emptyBuff, buff, emptyBuff.Length);
Array.Copy(data, i, buff, buff.Length - (bytes + 1), bytes);
var val = BitConverter.ToUInt64(buff, 0);
for (int bitOffset = (bytes + 1) * 8 - 5; bitOffset > 3; bitOffset -= 5)
result.Append(_alphabet[(int)((val >> bitOffset) & 0x1f)]);
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 (input.Length % 8 != 0)
throw new ArgumentException("Invalid length for a base32 string with padding.");
input = input.TrimEnd(PaddingChar);
using (var ms = new MemoryStream(Math.Max((int)Math.Ceiling(input.Length * 5 / 8.0), 1)))
for (var i = 0; i < input.Length; i += 8)
var chars = Math.Min(input.Length - i, 8);
var bytes = (int)Math.Floor(chars * (5 / 8.0));
for (var charOffset = 0; charOffset < chars; charOffset++)
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);
var buff = BitConverter.GetBytes(val);
ms.Write(buff, buff.Length - (bytes + 1), bytes);
private void EnsureAlphabetIndexed()
Dictionary<string, uint> cidx;
var indexKey = (IsCaseSensitive ? "S" : "I") + _alphabet;
if (!_indexes.TryGetValue(indexKey, out cidx))
if (!_indexes.TryGetValue(indexKey, out cidx))
cidx = new Dictionary<string, uint>(_alphabet.Length, IsCaseSensitive ? StringComparer.InvariantCulture : StringComparer.InvariantCultureIgnoreCase);
for (var i = 0; i < _alphabet.Length; i++)
cidx[_alphabet.Substring(i, 1)] = (uint)i;
_indexes.Add(indexKey, cidx);