我有一个 base32 字符串,需要将其转换为字节数组。而且我在 .NET 框架中找不到转换方法。我可以找到 base64 的方法,但找不到 base32 的方法。
Convert.FromBase64String
– 像这样的 base32 将是完美的。
框架中有这样的方法还是我必须自己动手?
我需要一个 base32 编码器/解码器,所以今天下午我花了几个小时把它放在一起。我相信它符合此处列出的标准:https ://www.rfc-editor.org/rfc/rfc4648#section-6 。
public class Base32Encoding
{
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException("input");
}
input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
byte[] returnArray = new byte[byteCount];
byte curByte = 0, bitsRemaining = 8;
int mask = 0, arrayIndex = 0;
foreach (char c in input)
{
int cValue = CharToValue(c);
if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << (3 + bitsRemaining));
bitsRemaining += 3;
}
}
//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
public static string ToString(byte[] input)
{
if (input == null || input.Length == 0)
{
throw new ArgumentNullException("input");
}
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
char[] returnArray = new char[charCount];
byte nextChar = 0, bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining < 4)
{
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}
bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
}
return new string(returnArray);
}
private static int CharToValue(char c)
{
int value = (int)c;
//65-90 == uppercase letters
if (value < 91 && value > 64)
{
return value - 65;
}
//50-55 == numbers 2-7
if (value < 56 && value > 49)
{
return value - 24;
}
//97-122 == lowercase letters
if (value < 123 && value > 96)
{
return value - 97;
}
throw new ArgumentException("Character is not a Base32 character.", "c");
}
private static char ValueToChar(byte b)
{
if (b < 26)
{
return (char)(b + 65);
}
if (b < 32)
{
return (char)(b + 24);
}
throw new ArgumentException("Byte is not a value Base32 value.", "b");
}
}
检查这里的FromBase32String
.NET 实现。
编辑:以上链接已失效;您可以在archive.org找到存档副本
实际代码如下:
using System;
using System.Text;
public sealed class Base32 {
// the valid chars for the encoding
private static string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP";
/// <summary>
/// Converts an array of bytes to a Base32-k string.
/// </summary>
public static string ToBase32String(byte[] bytes) {
StringBuilder sb = new StringBuilder(); // holds the base32 chars
byte index;
int hi = 5;
int currentByte = 0;
while (currentByte < bytes.Length) {
// do we need to use the next byte?
if (hi > 8) {
// get the last piece from the current byte, shift it to the right
// and increment the byte counter
index = (byte)(bytes[currentByte++] >> (hi - 5));
if (currentByte != bytes.Length) {
// if we are not at the end, get the first piece from
// the next byte, clear it and shift it to the left
index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
}
hi -= 3;
} else if(hi == 8) {
index = (byte)(bytes[currentByte++] >> 3);
hi -= 3;
} else {
// simply get the stuff from the current byte
index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
hi += 5;
}
sb.Append(ValidChars[index]);
}
return sb.ToString();
}
/// <summary>
/// Converts a Base32-k string into an array of bytes.
/// </summary>
/// <exception cref="System.ArgumentException">
/// Input string <paramref name="s">s</paramref> contains invalid Base32-k characters.
/// </exception>
public static byte[] FromBase32String(string str) {
int numBytes = str.Length * 5 / 8;
byte[] bytes = new Byte[numBytes];
// all UPPERCASE chars
str = str.ToUpper();
int bit_buffer;
int currentCharIndex;
int bits_in_buffer;
if (str.Length < 3) {
bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
return bytes;
}
bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
bits_in_buffer = 10;
currentCharIndex = 2;
for (int i = 0; i < bytes.Length; i++) {
bytes[i] = (byte)bit_buffer;
bit_buffer >>= 8;
bits_in_buffer -= 8;
while (bits_in_buffer < 8 && currentCharIndex < str.Length) {
bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
bits_in_buffer += 5;
}
}
return bytes;
}
}
这是我的编码和解码功能。我觉得它们比其他建议更短更简洁。所以如果你需要一个小的,试试这些。
public static string BytesToBase32(byte[] bytes) {
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
string output = "";
for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5) {
int dualbyte = bytes[bitIndex / 8] << 8;
if (bitIndex / 8 + 1 < bytes.Length)
dualbyte |= bytes[bitIndex / 8 + 1];
dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
output += alphabet[dualbyte];
}
return output;
}
public static byte[] Base32ToBytes(string base32) {
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
List<byte> output = new List<byte>();
char[] bytes = base32.ToCharArray();
for (int bitIndex = 0; bitIndex < base32.Length * 5; bitIndex += 8) {
int dualbyte = alphabet.IndexOf(bytes[bitIndex / 5]) << 10;
if (bitIndex / 5 + 1 < bytes.Length)
dualbyte |= alphabet.IndexOf(bytes[bitIndex / 5 + 1]) << 5;
if (bitIndex / 5 + 2 < bytes.Length)
dualbyte |= alphabet.IndexOf(bytes[bitIndex / 5 + 2]);
dualbyte = 0xff & (dualbyte >> (15 - bitIndex % 5 - 8));
output.Add((byte)(dualbyte));
}
return output.ToArray();
}
这是一个非常古老的问题,但我碰巧偶然发现它想要 OTP 令牌同样的东西。事实证明,NuGet 上的 OTP.NET 包中内置了 base 32 功能:
Base32Encoding.ToBytes("(your base 32 string here)")
反过来也是可能的:
Base32Encoding.ToString(new byte[] { /* your bytes here */ })
我已经编写了一些基于灵活标准的各种 Base32 和 Base64 编码/解码方法的实现。值得注意的是:base64url(根据 rfc4648)及其 base32 等效项。
默认情况下,Base32Url 类仅使用字符 A 到 Z 和 2 到 7 进行编码。不使用连字符、下划线、加号、斜线或等号,使其在几乎所有情况下都可用作 URL 标记。Base32Url 还支持自定义字母、区分大小写/不区分大小写、填充/无填充等。
这是我快速记下的解决方案。它仅适用于 8 个 base32 字符的倍数的字符串。不过确实有效。
public class Base32
{
/// <summary>
/// Decode a Base32 string
/// This will only work on a multiple of 40 bits (5 bytes)
/// http://www.garykessler.net/library/base64.html
/// </summary>
public static byte[] Decode(string Base32String)
{
// Ignore hyphens
string str = Base32String.Replace("-", "");
// Convert it to bits
List<byte> bits = new List<byte>();
foreach (char c in str)
{
int i = CharToValue(c);
bits.Add((byte)((i & 16) > 0 ? 1 : 0));
bits.Add((byte)((i & 8) > 0 ? 1 : 0));
bits.Add((byte)((i & 4) > 0 ? 1 : 0));
bits.Add((byte)((i & 2) > 0 ? 1 : 0));
bits.Add((byte)((i & 1) > 0 ? 1 : 0));
}
// Convert bits into bytes
List<byte> bytes = new List<byte>();
for (int i = 0; i < bits.Count; i += 8)
{
bytes.Add((byte)(
(bits[i + 0] << 7) +
(bits[i + 1] << 6) +
(bits[i + 2] << 5) +
(bits[i + 3] << 4) +
(bits[i + 4] << 3) +
(bits[i + 5] << 2) +
(bits[i + 6] << 1) +
(bits[i + 7] << 0)));
}
return bytes.ToArray();
}
static int CharToValue(char c)
{
char cl = char.ToLower(c);
if (cl == 'a') return 0;
if (cl == 'b') return 1;
if (cl == 'c') return 2;
if (cl == 'd') return 3;
if (cl == 'e') return 4;
if (cl == 'f') return 5;
if (cl == 'g') return 6;
if (cl == 'h') return 7;
if (cl == 'i') return 8;
if (cl == 'j') return 9;
if (cl == 'k') return 10;
if (cl == 'l') return 11;
if (cl == 'm') return 12;
if (cl == 'n') return 13;
if (cl == 'o') return 14;
if (cl == 'p') return 15;
if (cl == 'q') return 16;
if (cl == 'r') return 17;
if (cl == 's') return 18;
if (cl == 't') return 19;
if (cl == 'u') return 20;
if (cl == 'v') return 21;
if (cl == 'w') return 22;
if (cl == 'x') return 23;
if (cl == 'y') return 24;
if (cl == 'z') return 25;
if (cl == '2') return 26;
if (cl == '3') return 27;
if (cl == '4') return 28;
if (cl == '5') return 29;
if (cl == '6') return 30;
if (cl == '7') return 31;
throw new Exception("Not a base32 string");
}
}
我已经为 VB.NET 提出了我自己的通用 Base32 编码器/解码器实现。我已经通过独立网站验证了结果,因此它看起来确实非常准确。
任何关于如何改进代码的评论都将受到欢迎。
Option Compare Text
Imports System.ComponentModel
Public Structure Base32(Of T)
Private Const csValidStandardBase32Chars As String = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
<EditorBrowsable(EditorBrowsableState.Never)> _
Class Base32Nibble
<EditorBrowsable(EditorBrowsableState.Never)> _
Friend mStore As New BitArray(5, False)
Public ReadOnly Property Value As Byte
Get
Dim result As Byte = 0
For index As Byte = 0 To mStore.Count - 1
If mStore(index) Then
result += (2 ^ index)
End If
Next
Return result
End Get
End Property
Public Overrides Function ToString() As String
Dim nibbleString As String = Nothing
For Each bit As Boolean In mStore
nibbleString = Math.Abs(CInt(bit)).ToString() & nibbleString
Next
Return nibbleString
End Function
End Class
Private Shared mNibbles As List(Of Base32Nibble)
Public ReadOnly Property Count As Long
Get
Return mNibbles.Count
End Get
End Property
Default Public ReadOnly Property Item(ByVal index As Integer) As Base32Nibble
Get
Return DirectCast(mNibbles(index), Base32Nibble)
End Get
End Property
Public Sub New(ByVal Value As T)
Dim temp As Object = CType(Value, T)
getNibbles(BitConverter.GetBytes(temp))
End Sub
Public Sub New(ByVal Value As Byte())
getNibbles(Value)
End Sub
Public Shared Widening Operator CType(ByVal Value As T) As Base32(Of T)
Return New Base32(Of T)(Value)
End Operator
Public Shared Widening Operator CType(ByVal Value As Byte()) As Base32(Of T)
Return New Base32(Of T)(Value)
End Operator
Public ReadOnly Property Value As String
Get
Dim result As String = Nothing
For Each Nib As Base32(Of T).Base32Nibble In mNibbles
result = csValidStandardBase32Chars(Nib.Value) & result
Next
Return result.TrimStart("0")
End Get
End Property
Public Function ToNumeric(ByVal Base32String As String) As T
Dim result As T = CType(CType(0, Object), T)
Try
If Base32String.Trim.Length > 0 Then
Dim pos As Integer = 0
Do
Dim temp As Object = getBase32Value(Base32String, pos)
result = result + temp
pos += 1
Loop While (pos < Base32String.Length)
End If
Catch ex As Exception
' Catch overflow errors if the generic type T doesn't have enough
' room to store the result
System.Diagnostics.Debug.Print(ex.Message)
End Try
Return result
End Function
Private Shared Sub getNibbles(ByVal Value As Byte())
Dim valueBytes As New BitArray(Value)
Dim nib As Base32Nibble = Nothing
mNibbles = New List(Of Base32Nibble)
Dim padding As Byte = (1 - (valueBytes.Length / 5 - (valueBytes.Length \ 5))) * 5
valueBytes.Length = valueBytes.Length + padding
For element As Short = 0 To valueBytes.Count - 1
If (element Mod 5 = 0) Then
nib = New Base32Nibble()
mNibbles.Add(nib)
End If
nib.mStore.Item(element Mod 5) = valueBytes.Item(element)
Next
End Sub
Private Function getBase32Char(ByVal InputString As String, ByVal InputPosition As Integer) As String
Return csValidStandardBase32Chars.IndexOf(Mid(InputString, InputPosition + 1, 1))
End Function
Private Function getBase32Value(ByVal InputString As String, ByVal InputPosition As Integer) As T
Return CType(CType((getBase32Char(InputString, InputPosition) * (32 ^ (InputString.Length - 1 - InputPosition))), Object), T)
End Function
End Structure
这是将 123456789 转换为 Base32 的示例
Dim value As Base32(Of Int64) = 123456789
Console.WriteLine( "123456789 in Base32 = " & value.Value)