Base64 编解码

0.Base64的由来

目前Base64已经成为网络上常见的传输8Bit字节代码的编码方式之一。在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后再进行签名或加密,之后再进行(或再次Base64)传输。那么,Base64到底起到什么作用呢?
在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64的出现就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。
电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此Base64就登场了。随之,Base64在URL、Cookie、网页传输少量二进制文件中也有相应的使用。

1. Base64的编码原理

Base64的原理比较简单,每当我们使用Base64时都会先定义一个类似这样的数组:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
上面就是Base64的索引表,字符选用了"A-Z、a-z、0-9、+、/" 64个(所以叫base64)可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到“=”或“==”号出现在Base64的编码结果中,“=”在此是作为填充字符出现,后面会讲到。

Base64就是用6位(2的6次幂就是64)表示字符,因此成为Base64。同理,Base32就是用5位,Base16就是用4位。

1.1 索引表

 1.2 编码步骤

  • 第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
  • 第二步,将上面的24个二进制位每6个一组,共分为4组。
  • 第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
  • 第四步,根据Base64编码对照表(见上图)获得对应的值。

注意:

  • Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文大约三分之一。
  • 为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。

1.3 一般示例

  •  第一步:“M”、“a”、"n"对应的ASCII码值分别为77,97,110,对应的二进制值是01001101、01100001、01101110。如图第二三行所示,由此组成一个24位的二进制字符串。
  • 第二步:如图红色框,将24位每6位二进制位一组分成四组。
  • 第三步:在上面每一组前面补两个0,扩展成32个二进制位,此时变为四个字节:00010011、00010110、00000101、00101110。分别对应的值(Base64编码索引)为:19、22、5、46。
  • 第四步:用上面的值在Base64编码表中进行查找,分别对应:T、W、F、u。因此“Man”Base64编码之后就变为:TWFu。

1.4 位数不足的情况

如果文本的字节数不是3的倍数,那如何处理呢?

  • 两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;

  • 一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”;

 2. java代码验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sun.misc.BASE64Encoder;
 
/**
 *
 */
public class Base64Utils {
 
    public static void main(String[] args) {
        String man = "Man";
        String a = "A";
        String bc = "BC";
 
        BASE64Encoder encoder = new BASE64Encoder();
        System.out.println("Man base64结果为:" + encoder.encode(man.getBytes()));
        System.out.println("BC base64结果为:" + encoder.encode(bc.getBytes()));
        System.out.println("A base64结果为:" + encoder.encode(a.getBytes()));
    }
}

 

结果:和上面的示例一样。

1
2
3
Man base64结果为:TWFu
BC base64结果为:QkM=
A base64结果为:QQ==

3. C语言版本源码

3.1 解码原理

逆向推导,由每4个字节(每个字节包含6位有效比特位)合并成3个8位二进制数。

3.1.2 编制解码表

为了获取字符在编码索引表中的位置,每次都要在表中查找字符的位置;为了提高效率,可以编制一个128字节的解码索引表,例如上面“TWFu”的’T’,对应10进制为84,在编码索引表的位置为19,那么我们可以在解码索引表的下标84的位置放19;同理,‘W’对应的10进制为87,在编码索引表的位置为22,那么我们可以在解码索引表的下标87的位置放 22。64个参与编码的字符对应的下标位置,分别放进编码索引表的索引值。 我们给解码索引表起名base64DecodeChars,那么在这个表中,用C语言表示,就有下面的对应关系:

1
2
3
4
5
6
Base64编码字符作为下标       编码字符对应的十进制数         C数组的值
--------------------------------------------------------------------
base64DecodeChars['T']    --- base64DecodeChars[84]  ---    19
base64DecodeChars['W']    --- base64DecodeChars[87]  ---    22
base64DecodeChars['F']    --- base64DecodeChars[70]  ---    5
base64DecodeChars['u']    --- base64DecodeChars[117] ---    46

3.2  自己编写

主要参考多篇博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
static const char base64_table[] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T',
    'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g',
    'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '+', '/'
    };
 
static const unsigned char base64_suffix_map[256] = {
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255,
    255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
    52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255,
    255, 254, 255, 255, 255,   0,   1,   2,   3,   4,   5,   6,
    7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,
    19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
    255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,
    37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
    49,  50,  51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255
    };
 
unsigned char move_bits(unsigned char src,int mv_left,int mv_right)
{
    src <<= mv_left;
    src >>= mv_right;
    return src;
}
 
int base64_encode(char *indata,int inlen,char *outdata,int *outlen)
{
    int ret=0;
    int i=0;
 
    if(indata==NULL || inlen<0){
        printf("indata is null,please input a correct data");
        return -1;
    }
 
    int in_new_len=0;
    int pad_len=0;
 
    if(inlen%3 != 0){
        pad_len = 3 - inlen%3;
    }
 
    in_new_len=inlen+pad_len;
 
    char *p=outdata;
/*
    int j=0;
    for(j=0;j<64;j++){
        printf("base64_table[%d] is:%c\n",j,base64_table[j]);
    }
*/
    for(i=0;i<in_new_len;i+=3){
         
        *p = base64_table[move_bits(*indata,0,2)];
 
        if(i==in_new_len-3 && pad_len != 0){
            if(pad_len==1){
                *(p+1) = base64_table[move_bits(*indata,6,2) + move_bits(*(indata+1),0,4)];
                *(p+2) = base64_table[move_bits(*(indata+1),4,2)];
                *(p+3) = '=';
            }else if(pad_len==2){
                *(p+1) = base64_table[move_bits(*indata,6,2)];
                *(p+2) = '=';
                *(p+3) = '=';
            }
        }else{
            *(p+1) = base64_table[move_bits(*indata,6,2) + move_bits(*(indata+1),0,4)];
            *(p+2) = base64_table[move_bits(*(indata+1),4,2)+move_bits(*(indata+2),0,6)];
            *(p+3) = base64_table[move_bits(*(indata+2),2,2)];
        }
        p=p+4;
        indata=indata+3;
        ret=i;
    }
 
    *outlen=(in_new_len/3)*4;
 
    return ret;
}
 
int base64_decode(const char* indata,int inlen,char *outdata,int *outlen){
    int ret = 0;
 
    if(indata == NULL || inlen <=0){
        return ret=-1;
    }
 
    if(inlen%4 != 0){
        return ret=-2;
    }
 
    int t=0,x=0,y=0,i=0;
    unsigned char c=0;
    int g=3;
 
    while(x<inlen){
        c = base64_suffix_map[indata[x++]];
        switch(c)
        {
            case 255:
                return -1;
                break;
            case 253:
                continue;
                break;
            case 254:
                c=0;
                g--;
                break;
            default:
                break;
        }
        t = (t<<6)|c;
        if(++y==4){
            outdata[i++] =(unsigned char)((t>>16)&0xff);
            if(g>1) outdata[i++] = (unsigned char)((t>>8)&0xff);
            if(g>2) outdata[i++] = (unsigned char)(t&0xff);
            y=t=0;
        }
    }
    if(outlen != NULL){
        *outlen = i;
    }
 
    return ret;
}
 
int main(int argc,char *argv[])
{
 
    if(argc < 2){
        printf("use: %s xxx(a str)\n",argv[0]);
        return 0;
    }
 
    int ret;
    char *buf=NULL;
    buf=argv[1];
         
    int len_base64=0;
        printf("len of buf is: %ld\n",strlen(buf));
     
    int len=(strlen(buf)+2)/3*4;
 
    printf("len is: %d\n",len);
    char *buf_base64 = (char*)malloc(len);
    memset(buf_base64,'\0',len);
     
    ret=base64_encode(buf,strlen(buf),buf_base64,&len_base64);
    printf("buf is:\n");
    printf("%s\n",buf);
 
    printf("\nbase64_encode is:\n");
    printf("%s\n",buf_base64);
    printf("ret is: %d,len_base64 is: %d\n\n",ret,len_base64);
 
    char *buf_base64_de = (char*)malloc(strlen(buf));
    memset(buf_base64_de,'\0',strlen(buf));
    int len_base64_de=0;
 
    ret=base64_decode(buf_base64,len,buf_base64_de,&len_base64_de);
 
    printf("\nbase64_decode is:\n");
    printf("%s\n",buf_base64_de);
    printf("ret is: %d,len_base64_de is: %d\n\n",ret,len_base64_de);
 
    return 0;
}

 

3.3 其他版本

https://blog.csdn.net/wooin/article/details/127429256

 

参考链接:

https://blog.csdn.net/wo541075754/article/details/81734770

https://blog.csdn.net/hwd00001/article/details/125803710

 

posted @   皓然123  阅读(159)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示