Skip to content

Instantly share code, notes, and snippets.

@BravoTango86
Created September 20, 2016 22:03
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save BravoTango86/2a085185c3b9bd8383a1f956600e515f to your computer and use it in GitHub Desktop.
Save BravoTango86/2a085185c3b9bd8383a1f956600e515f to your computer and use it in GitHub Desktop.
Base32 Encoding and Decoding in C#
/*
* Derived from https://github.com/google/google-authenticator-android/blob/master/AuthenticatorApp/src/main/java/com/google/android/apps/authenticator/Base32String.java
*
* Copyright (C) 2016 BravoTango86
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
public static class Base32 {
private static readonly char[] DIGITS;
private static readonly int MASK;
private static readonly int SHIFT;
private static Dictionary<char, int> CHAR_MAP = new Dictionary<char, int>();
private const string SEPARATOR = "-";
static Base32() {
DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray();
MASK = DIGITS.Length - 1;
SHIFT = numberOfTrailingZeros(DIGITS.Length);
for (int i = 0; i < DIGITS.Length; i++) CHAR_MAP[DIGITS[i]] = i;
}
private static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i << 16; if (y != 0) { n = n - 16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - (int)((uint)(i << 1) >> 31);
}
public static byte[] Decode(string encoded) {
// Remove whitespace and separators
encoded = encoded.Trim().Replace(SEPARATOR, "");
// Remove padding. Note: the padding is used as hint to determine how many
// bits to decode from the last incomplete chunk (which is commented out
// below, so this may have been wrong to start with).
encoded = Regex.Replace(encoded, "[=]*$", "");
// Canonicalize to all upper case
encoded = encoded.ToUpper();
if (encoded.Length == 0) {
return new byte[0];
}
int encodedLength = encoded.Length;
int outLength = encodedLength * SHIFT / 8;
byte[] result = new byte[outLength];
int buffer = 0;
int next = 0;
int bitsLeft = 0;
foreach (char c in encoded.ToCharArray()) {
if (!CHAR_MAP.ContainsKey(c)) {
throw new DecodingException("Illegal character: " + c);
}
buffer <<= SHIFT;
buffer |= CHAR_MAP[c] & MASK;
bitsLeft += SHIFT;
if (bitsLeft >= 8) {
result[next++] = (byte)(buffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
}
// We'll ignore leftover bits for now.
//
// if (next != outLength || bitsLeft >= SHIFT) {
// throw new DecodingException("Bits left: " + bitsLeft);
// }
return result;
}
public static string Encode(byte[] data, bool padOutput = false) {
if (data.Length == 0) {
return "";
}
// SHIFT is the number of bits per output character, so the length of the
// output is the length of the input multiplied by 8/SHIFT, rounded up.
if (data.Length >= (1 << 28)) {
// The computation below will fail, so don't do it.
throw new ArgumentOutOfRangeException("data");
}
int outputLength = (data.Length * 8 + SHIFT - 1) / SHIFT;
StringBuilder result = new StringBuilder(outputLength);
int buffer = data[0];
int next = 1;
int bitsLeft = 8;
while (bitsLeft > 0 || next < data.Length) {
if (bitsLeft < SHIFT) {
if (next < data.Length) {
buffer <<= 8;
buffer |= (data[next++] & 0xff);
bitsLeft += 8;
} else {
int pad = SHIFT - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int index = MASK & (buffer >> (bitsLeft - SHIFT));
bitsLeft -= SHIFT;
result.Append(DIGITS[index]);
}
if (padOutput) {
int padding = 8 - (result.Length % 8);
if (padding > 0) result.Append(new string('=', padding == 8 ? 0 : padding));
}
return result.ToString();
}
private class DecodingException : Exception {
public DecodingException(string message) : base(message) {
}
}
}
@mishrsud
Copy link

Thanks for sharing, this was exactly what I was after!

@ivanstevevegaherrera
Copy link

Thank you very much for sharing, you saved me hours and hours of work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment