.NET 中的 Base32、base32url、base64url 和 z-base-32 编码和解码
.NET 中的 Base32、base32url、base64url 和 z-base-32 编码和解码
更新
已迁移至 Github 维护 (https://github.com/ericlau-solid/Base3264-UrlEncoder),并发布到 nuget (https://nuget.net.cn/packages/Base3264-UrlEncoder/)。
引言
基于标准的各种 Base32
和 Base64
编码/解码方法实现。 这些旨在将二进制数据编码为纯文本,并将生成的文本解码回原始二进制数据。 当您需要通过仅支持文本的技术(例如在 URL 中包含二进制安全令牌)传输二进制数据时,这非常有用。
Base32Url
仅使用字母 A 到 Z 和数字 2 到 7 进行编码。 不使用连字符、下划线、加号、斜杠或等号,使其几乎在所有情况下都可以用作 URL 令牌。 Base32Url
还支持自定义字母表。 可以使用仅包含辅音(非元音)字符的自定义区分大小写的字母表,以确保您的令牌不包含意外的脏话。 以下是一个避免元音、字母 L 且不包含数字字符的示例:BCDFGHKMNPQRSTVWXYZbcdfghkmnpqrs
。
Base64Url
比 Base32Url
更紧凑,并且几乎总是可以用作 URL 令牌或文件名。 Base64Url
包含的唯一非字母数字字符是连字符 (-
) 和下划线 (_
) 字符,这些字符都不需要在 URL 或文件名中使用时进行进一步编码。
代码在 GitHub 上维护并发布在 NuGet 上
- https://github.com/ericlau-solid/Base3264-UrlEncoder
- https://nuget.net.cn/packages/Base3264-UrlEncoder/
感谢 Github 上的 @ericlau-solid。
Base32Url(编码器/解码器)
Base32
编码器/解码器的默认模式是Base32Url
。 这使用标准的Base32
字母表编码,但省略填充字符且不区分大小写。- 支持带有填充字符 (=) 的标准
Base32
,符合 RFC 4648 中的Base32
。 - 支持
Base32
扩展/替代字母表 z-base-32。
Base64Url(编码器/解码器)
- 基于标准的 .NET
Base64
编码器 - 使用 RFC 4648 中 URL 安全的替代
Base64
字母表 - 这与 Microsoft 的
HttpServerUtility.UrlTokenEncode
不同。
更多信息和用法
市面上还有其他 base32
编码实现,但我认为此 base32
实现的代码更简单(涉及的位移计算代码更少)。
我在这里提供的 base64
实现有点hackish,但比 Microsoft 提供的要好得多。
从 HttpServerUtility.UrlTokenEncode
获得的结果本质上是 base64url
,但他们没有截断填充,而是附加一个数字(0、1 或 2),指示删除的填充字符数。
Usage:
Base32Url.ToBase32String(Encoding.ASCII.GetBytes("Hello World!"));
JBSWY3DPEBLW64TMMQQQ
var b32 = new Base32Url(true); // Base32Url(bool usePadding)
b32.Encode(Encoding.ASCII.GetBytes("Hello World!"));
JBSWY3DPEBLW64TMMQQQ====
有关所涉及标准的更多信息,请参阅 RFC 4648 http://tools.ietf.org/html/rfc4648[^]。
维基百科也提供了很好的信息。 请参阅 http://en.wikipedia.org/wiki/Base32[^] 或 http://en.wikipedia.org/wiki/Base64[^]。
作者:Mhano Harkness - http://mhano.com[^]
Base32Url
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace MhanoHarkness
{
/// <summary>
/// Base32Url is a standard base 32 encoder/decoder except that padding turned
/// off and it is not case sensitive (by default).
///
/// If you turn padding and case sensitivity on it becomes a standard base32
/// encoder/decoder giving you 8 character chunks right padded with equals symbols.
///
/// If you leave padding off and use Base32Url.ZBase32Alphabet you
/// get a z-base-32 compatible encoder/decoder.
///
/// See http://tools.ietf.org/html/rfc4648
/// For more information see http://en.wikipedia.org/wiki/Base32
/// </summary>
public class Base32Url
{
public const char StandardPaddingChar = '=';
public const string Base32StandardAlphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
public const string ZBase32Alphabet =
"ybndrfg8ejkmcpqxot1uwisza345h769";
public char PaddingChar;
public bool UsePadding;
public bool IsCaseSensitive;
public bool IgnoreWhiteSpaceWhenDecoding;
private readonly string _alphabet;
private Dictionary<string, uint> _index;
// alphabets may be used with varying case sensitivity,
// thus index must not ignore case
private static Dictionary<string, Dictionary<string,
uint>> _indexes = new Dictionary<string, Dictionary<string,
uint>>(2, StringComparer.InvariantCulture);
/// <summary>
/// Create case insensitive encoder/decoder
/// using the standard base32 alphabet without padding.
/// White space is not permitted when decoding (not ignored).
/// </summary>
public Base32Url() : this(false, false, false, Base32StandardAlphabet) { }
/// <summary>
/// Create case insensitive encoder/decoder using the standard base32 alphabet.
/// White space is not permitted when decoding (not ignored).
/// </summary>
/// <param name="padding">
/// Require/use padding characters?</param>
public Base32Url(bool padding) :
this(padding, false, false, Base32StandardAlphabet) { }
/// <summary>
/// Create encoder/decoder using the standard base32 alphabet.
/// White space is not permitted when decoding (not ignored).
/// </summary>
/// <param name="padding">
/// Require/use padding characters?</param>
/// <param name="caseSensitive">
/// Be case sensitive when decoding?</param>
public Base32Url(bool padding, bool caseSensitive) :
this(padding, caseSensitive, false, Base32StandardAlphabet) { }
/// <summary>
/// Create encoder/decoder using the standard base32 alphabet.
/// </summary>
/// <param name="padding">
/// Require/use padding characters?</param>
/// <param name="caseSensitive">
/// Be case sensitive when decoding?</param>
/// <param name="ignoreWhiteSpaceWhenDecoding">
/// Ignore / allow white space when decoding?</param>
public Base32Url(bool padding, bool caseSensitive,
bool ignoreWhiteSpaceWhenDecoding) : this(padding, caseSensitive,
ignoreWhiteSpaceWhenDecoding, Base32StandardAlphabet) { }
/// <summary>
/// Create case insensitive encoder/decoder with alternative alphabet and no padding.
/// White space is not permitted when decoding (not ignored).
/// </summary>
/// <param name="alternateAlphabet">
/// Alphabet to use (such as Base32Url.ZBase32Alphabet)</param>
public Base32Url(string alternateAlphabet) :
this(false, false, false, alternateAlphabet) { }
/// <summary>
/// Create the encoder/decoder specifying all options manually.
/// </summary>
/// <param name="padding">Require/use padding characters?</param>
/// <param name="caseSensitive">
/// Be case sensitive when decoding?</param>
/// <param name="ignoreWhiteSpaceWhenDecoding">
/// Ignore / allow white space when decoding?</param>
/// <param name="alternateAlphabet">Alphabet to use
/// (such as Base32Url.ZBase32Alphabet, Base32Url.Base32StandardAlphabet
/// or your own custom 32 character alphabet string)</param>
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;
UsePadding = padding;
IsCaseSensitive = caseSensitive;
IgnoreWhiteSpaceWhenDecoding = ignoreWhiteSpaceWhenDecoding;
_alphabet = alternateAlphabet;
}
/// <summary>
/// Decode a base32 string to a byte[] using the default options
/// (case insensitive without padding using the standard base32 alphabet from rfc4648).
/// White space is not permitted (not ignored).
/// Use alternative constructors for more options.
/// </summary>
public static byte[] FromBase32String(string input)
{
return new Base32Url().Decode(input);
}
/// <summary>
/// Encode a base32 string from a byte[] using the default options
/// (case insensitive without padding
/// using the standard base32 alphabet from rfc4648).
/// Use alternative constructors for more options.
/// </summary>
public static string ToBase32String(byte[] data)
{
return new Base32Url().Encode(data);
}
public string Encode(byte[] data)
{
StringBuilder result = new StringBuilder
(Math.Max((int)Math.Ceiling(data.Length * 8 / 5.0), 1));
byte[] emptyBuff = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] buff = new byte[8];
// take input five bytes at a time to chunk it up for encoding
for (int i = 0; i < data.Length; i += 5)
{
int bytes = Math.Min(data.Length - i, 5);
// parse five bytes at a time using an 8 byte ulong
Array.Copy(emptyBuff, buff, emptyBuff.Length);
Array.Copy(data, i, buff, buff.Length - (bytes+1), bytes);
Array.Reverse(buff);
ulong val = BitConverter.ToUInt64(buff, 0);
for (int bitOffset = ((bytes+1) * 8) - 5; bitOffset > 3; bitOffset -= 5)
{
result.Append(_alphabet[(int)((val >> bitOffset) & 0x1f)]);
}
}
if (UsePadding)
{
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 (UsePadding)
{
if (input.Length % 8 != 0)
{
throw new ArgumentException
("Invalid length for a base32 string with padding.");
}
input = input.TrimEnd(PaddingChar);
}
// index the alphabet for decoding only when needed
EnsureAlphabetIndexed();
MemoryStream ms = new MemoryStream
(Math.Max((int)Math.Ceiling(input.Length * 5 / 8.0), 1));
// take input eight bytes at a time to chunk it up for encoding
for (int i = 0; i < input.Length; i += 8)
{
int chars = Math.Min(input.Length - i, 8);
ulong val = 0;
int bytes = (int)Math.Floor(chars * (5 / 8.0));
for (int charOffset = 0; charOffset < chars; charOffset++)
{
uint cbyte;
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));
}
byte[] buff = BitConverter.GetBytes(val);
Array.Reverse(buff);
ms.Write(buff, buff.Length - (bytes + 1), bytes);
}
return ms.ToArray();
}
private void EnsureAlphabetIndexed()
{
if (_index == null)
{
Dictionary<string, uint> cidx;
string indexKey = (IsCaseSensitive ? "S" : "I") + _alphabet;
if (!_indexes.TryGetValue(indexKey, out cidx))
{
lock (_indexes)
{
if (!_indexes.TryGetValue(indexKey, out cidx))
{
cidx = new Dictionary<string, uint>
(_alphabet.Length, IsCaseSensitive ?
StringComparer.InvariantCulture :
StringComparer.InvariantCultureIgnoreCase);
for (int i = 0; i < _alphabet.Length; i++)
{
cidx[_alphabet.Substring(i, 1)] = (uint) i;
}
_indexes.Add(indexKey, cidx);
}
}
}
_index = cidx;
}
}
}
}
Base64Url
using System; using System.Text; namespace MhanoHarkness { /// <summary> /// Modified Base64 for URL applications ('base64url' encoding) /// /// See http://tools.ietf.org/html/rfc4648 /// For more information see http://en.wikipedia.org/wiki/Base64 /// </summary> public class Base64Url { /// <summary> /// Modified Base64 for URL applications ('base64url' encoding) /// /// See http://tools.ietf.org/html/rfc4648 /// For more information see http://en.wikipedia.org/wiki/Base64 /// </summary> /// <param name="input"></param> /// <returns>Input byte array converted to a base64ForUrl encoded string</returns> public static string ToBase64ForUrlString(byte[] input) { StringBuilder result = new StringBuilder(Convert.ToBase64String(input).TrimEnd('=')); result.Replace('+', '-'); result.Replace('/', '_'); return result.ToString(); } /// <summary> /// Modified Base64 for URL applications ('base64url' encoding) /// /// See http://tools.ietf.org/html/rfc4648 /// For more information see http://en.wikipedia.org/wiki/Base64 /// </summary> /// <param name="base64ForUrlInput"></param> /// <returns>Input base64ForUrl encoded string as the original byte array</returns> public static byte[] FromBase64ForUrlString(string base64ForUrlInput) { int padChars = (base64ForUrlInput.Length%4) == 0 ? 0 : (4 - (base64ForUrlInput.Length%4)); StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars); result.Append(String.Empty.PadRight(padChars, '=')); result.Replace('-', '+'); result.Replace('_', '/'); return Convert.FromBase64String(result.ToString()); } } }