using System.Text; namespace zero.Identity; // This is a re-implementation of ASP.NET Core Base32, as they have marked it as internal // by using this we can create user security stamps on the fly and don't need // see: https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Identity/Extensions.Core/src/Base32.cs public static class Base32 { private static readonly string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; public static string ToBase32(byte[] input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } StringBuilder sb = new StringBuilder(); for (int offset = 0; offset < input.Length;) { byte a, b, c, d, e, f, g, h; int numCharsToOutput = GetNextGroup(input, ref offset, out a, out b, out c, out d, out e, out f, out g, out h); sb.Append((numCharsToOutput >= 1) ? _base32Chars[a] : '='); sb.Append((numCharsToOutput >= 2) ? _base32Chars[b] : '='); sb.Append((numCharsToOutput >= 3) ? _base32Chars[c] : '='); sb.Append((numCharsToOutput >= 4) ? _base32Chars[d] : '='); sb.Append((numCharsToOutput >= 5) ? _base32Chars[e] : '='); sb.Append((numCharsToOutput >= 6) ? _base32Chars[f] : '='); sb.Append((numCharsToOutput >= 7) ? _base32Chars[g] : '='); sb.Append((numCharsToOutput >= 8) ? _base32Chars[h] : '='); } return sb.ToString(); } public static byte[] FromBase32(string input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } input = input.TrimEnd('=').ToUpperInvariant(); if (input.Length == 0) { return new byte[0]; } var output = new byte[input.Length * 5 / 8]; var bitIndex = 0; var inputIndex = 0; var outputBits = 0; var outputIndex = 0; while (outputIndex < output.Length) { var byteIndex = _base32Chars.IndexOf(input[inputIndex]); if (byteIndex < 0) { throw new FormatException(); } var bits = Math.Min(5 - bitIndex, 8 - outputBits); output[outputIndex] <<= bits; output[outputIndex] |= (byte)(byteIndex >> (5 - (bitIndex + bits))); bitIndex += bits; if (bitIndex >= 5) { inputIndex++; bitIndex = 0; } outputBits += bits; if (outputBits >= 8) { outputIndex++; outputBits = 0; } } return output; } // returns the number of bytes that were output private static int GetNextGroup(byte[] input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h) { uint b1, b2, b3, b4, b5; int retVal; switch (input.Length - offset) { case 1: retVal = 2; break; case 2: retVal = 4; break; case 3: retVal = 5; break; case 4: retVal = 7; break; default: retVal = 8; break; } b1 = (offset < input.Length) ? input[offset++] : 0U; b2 = (offset < input.Length) ? input[offset++] : 0U; b3 = (offset < input.Length) ? input[offset++] : 0U; b4 = (offset < input.Length) ? input[offset++] : 0U; b5 = (offset < input.Length) ? input[offset++] : 0U; a = (byte)(b1 >> 3); b = (byte)(((b1 & 0x07) << 2) | (b2 >> 6)); c = (byte)((b2 >> 1) & 0x1f); d = (byte)(((b2 & 0x01) << 4) | (b3 >> 4)); e = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7)); f = (byte)((b4 >> 2) & 0x1f); g = (byte)(((b4 & 0x3) << 3) | (b5 >> 5)); h = (byte)(b5 & 0x1f); return retVal; } }