65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 中的 Base32、base32url、base64url 和 z-base-32 编码和解码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (10投票s)

2010 年 4 月 28 日

CPOL

2分钟阅读

viewsIcon

82618

.NET 中的 Base32、base32url、base64url 和 z-base-32 编码和解码

更新

已迁移至 Github 维护 (https://github.com/ericlau-solid/Base3264-UrlEncoder),并发布到 nuget (https://nuget.net.cn/packages/Base3264-UrlEncoder/)。

引言

基于标准的各种 Base32Base64 编码/解码方法实现。 这些旨在将二进制数据编码为纯文本,并将生成的文本解码回原始二进制数据。 当您需要通过仅支持文本的技术(例如在 URL 中包含二进制安全令牌)传输二进制数据时,这非常有用。

Base32Url 仅使用字母 A 到 Z 和数字 2 到 7 进行编码。 不使用连字符、下划线、加号、斜杠或等号,使其几乎在所有情况下都可以用作 URL 令牌。 Base32Url 还支持自定义字母表。 可以使用仅包含辅音(非元音)字符的自定义区分大小写的字母表,以确保您的令牌不包含意外的脏话。 以下是一个避免元音、字母 L 且不包含数字字符的示例:BCDFGHKMNPQRSTVWXYZbcdfghkmnpqrs

Base64UrlBase32Url 更紧凑,并且几乎总是可以用作 URL 令牌或文件名。 Base64Url 包含的唯一非字母数字字符是连字符 (-) 和下划线 (_) 字符,这些字符都不需要在 URL 或文件名中使用时进行进一步编码。

代码在 GitHub 上维护并发布在 NuGet 上

感谢 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());
		}
	}
}
© . All rights reserved.