自己写的伪 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。

  照例用代码+注释的形式做说明:

private const string BASE32 = "abcdefghijklmnopqrstuvwxyz456789";
//挂表,通过 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 编解码方法一样。

posted @ 2008-08-13 12:19  田嵩  阅读(1909)  评论(1编辑  收藏  举报