自己写的伪 Base32 编解码函数
大家对 Base64 编码应该不陌生吧,它将一组二进制代码按照每6位为一组编码,从 0x0 到 0x3f 分别用 0-9A-Za-z+/ 这 64 个字符代替,另外根据二进制代码的长度还会在后面补 = 代表剩余长度不够6位的情况。在 C# 中通常把 byte[] 类型数据通过 System.Convert.ToBase64String(byte[]) 编码然后通过 System.Convert.FromBase64String(string) 解码,这种编码主要是解决了 byte[] 类型无法用于只能使用 ASCII 的情况,同时也解决了将数据转换成十六进制字符串(即 0x3f 转换为 "3f")时过于冗长(通常长度为 16 个 byte[] 转换为 Base64 后长度为 24,转换成字符串为 32)的问题。在 ASP.NET 页面中查看源代码就可以看到 Base64 编码的表单隐藏字段 __VIEWSTATE。
这样看来就比较完美了,不过有些情况要求不能区分大小写,不能有特殊符号(“+”“/”“=”),不能有容易混淆的字符(字母 o、i、z 的大写形式和数字 0、1、2),这时 Base64 就不行了,此时我们可以用十六进制字符串,但是长度过长的问题又出现了。
根据 Base64 的思想,可以用 a-z4-9 这 32 个字符写成 Base32 编码,上网搜了一下,发现网上的实现效率比较低,于是决定自行编写编解码函数。后来发现和标准的 Base32 编码不兼容,于是改叫伪 Base32 编码。和 Base64 编码根据剩余二进制位数追加 = 号不同,伪 Base32 编码并不需要这样做,例如最后剩余二进制代码为 001,则扩展为 00001 即 0x1,解码时根据剩余位数自动将 00001 移位为 00100,然后根据 byte 剩余长度截取到 001 即结束,忽略末尾的 00。
照例用代码+注释的形式做说明:
//挂表,通过 BASE32[i] 和 BASE32.IndexOf(c) 获取某数字代表的字符和某字符代表的数字
public static string ToBase32(byte[] value)
{
//编码
int length = (value.Length * 8 + 4) / 5;
//计算字符串的长度,每个 byte 为8位二进制数,1到5位二进制数需要1个字符,6到10位为2个,依此类推
StringBuilder sb = new StringBuilder(length);
//通过 StringBuilder 预先分配空间,提高效率
int cur_i = 0;
//当前 byte[] 索引
byte cur_b = 0;
//当前 byte
int index;
//5位二进制数结果
int k;
//当前二进制位索引
for (int i = 0; i < length; i++)
{
//对每个 Base32 字符循环
index = 0;
//初始置 0
for (int j = 0; j < 5; j++)
{
//对每个 Base32 字符代表的二进制位循环
k = i * 5 + j;
//计算当前二进制位索引
if (k == value.Length * 8)
break;
//二进制位扫描结束
if (k % 8 == 0)
cur_b = value[cur_i++];
//转移到下一个 byte,初始化时转移到第 0 个 byte
index <<= 1;
//左移一位以便继续设置最低位
index |= (cur_b & 128) == 0 ? 0 : 1;
//将 byte 的最高位送入 index 的最低位
cur_b <<= 1;
//将 byte 左移一位以将次高位变为最高位
}
sb.Append(BASE32[index]);
//追加字符
}
return sb.ToString();
}
public static byte[] ToBytes(string value)
{
//解码
value = value.ToLower();
//转换为小写
int length = value.Length * 5 / 8;
//计算长度,因为末位可能会多表示 0 到 4 个二进制位,因此无需修正余数。
byte[] r = new byte[length];
//分配空间
int cur_i = 0;
//当前 Base32 字符索引
int cur_v = 0;
//当前 Base32 字符代表的数字
int k;
//当前二进制位索引
for (int i = 0; i < length; i++)
{
//对每个 byte 循环
for (int j = 0; j < 8; j++)
{
//对每个 byte 对应的二进制位循环
k = i * 8 + j;
//计算当前二进制位索引
if (k == value.Length * 5)
break;
//二进制位扫描结束,通常 Base32 字符代表的二进制位会大于等于实际长度
//因此此处主要用于检测不规范 Base32 编码
if (k % 5 == 0)
{
cur_v = BASE32.IndexOf(value[cur_i++]);
//转移到下一个 Base32 字符,初始化时转移到第 0 个字符
if (cur_i == value.Length && value.Length % 8 != 0)
cur_v <<= value.Length * 5 % 8;
//根据 Base32 字符串代表的长度和实际 byte[] 长度
//修正最末尾 Base32 字符代表的数字。
}
r[i] <<= 1;
r[i] |= (byte)((cur_v & 16) == 0 ? 0 : 1);
cur_v <<= 1;
//编码过程的逆过程,同样是移位、送位
}
}
return r;
}
这样就 OK 了,使用方法和 .NET 自带的 Base64 编解码方法一样。