/* * 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) { } } }
Tuesday, 20 September 2016
Base32 Encoding and Decoding in C#
Gist available at https://gist.github.com/BravoTango86/2a085185c3b9bd8383a1f956600e515f
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment