前言
Base64非常常见的用于传输8Bit字节码的编码方式之一,也就是采用64个可打印的自体来编码二进制数据进行传输的方法。在ASCII表中,0~31、127是控制字符32~126属于可打印字符,而Http协议下传输二进制数据,是需要把数据转为字符来传输的,所以实际上能用于传输的就是ASCII表中的32~126这95个字符,base64则是采用了其中的64个来编码。
标准base64中可打印的64个字符为26个字母的大小写、10个数字以及+、/,共64个字符,但是由于在Http传输中URL编码器会把标准base64中的"/“和”+“字符变为形如“%XX”的形式,而且由于”%“是ANSI SQL中规定的通配符,这个时候又会对”%"进行转换等操作,因此针对http协议的传输,可采用改进的URL安全的base64编码,当然,除了URL安全的base64编码方式,还有其他改进的方案(如正则表达式的base64等),但是好像标准base64和URL安全的base64比较常见。
标准base64编码
标准的base64编码表如下:
索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
根据上边的索引表,实际上,64字符只需要6位即可表示,但是ASCII表使用8位来表示的,所以在转换的时候是采用了4*6个bit来存储3*8bit数据,所以就会有如下转换方式:
1. 当数据刚好是3的倍数的时候:
ASCII | S | O | n | |
---|---|---|---|---|
10进制 | 83 | 111 | 110 | |
2进制 | 01010011 | 01101111 | 01101110 | |
每6个bit一组 | 010100 | 110110 | 111101 | 101110 |
高位补0 | 00010100 | 00110110 | 00111101 | 00101110 |
base64索引 | 20 | 54 | 61 | 46 |
base64字符 | U | 2 | 9 | u |
2. 当数据不满足3的倍数的时候:此时规定不足的一律补0
ASCII | S | |||
---|---|---|---|---|
10进制 | 83 | |||
2进制 | 01010011 | |||
每6个bit一组 | 010100 | 110000 | 000000 | 000000 |
高位补0 | 00010100 | 00110000 | 00000000 | 00000000 |
base64索引 | 20 | 48 | ||
base64字符 | U | w | = | = |
S的二进制前6位补零后为字符“U”,第二组末尾补4个0转换后为字符“w”。剩下的使用“=”替代。即字符“S”通过Base64编码后为“Uw==”。这就是Base64的编码过程。
C++实现标准base64编码
C++实现base64编码是基于std::string操作来实现的,代码主要来自github上开源的一个项目[1],其实现了基于std::string的base64编码和解码。
编码部分代码如下:
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len)
{
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--)
{
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
{
ret += base64_chars[char_array_4[i]];
}
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
{
char_array_3[j] = '\0';
}
char_array_4[0] = ( char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
for (j = 0; (j < i + 1); j++)
{
ret += base64_chars[char_array_4[j]];
}
while((i++ < 3))
{
ret += '=';
}
}
return ret;
}
解码部分代码如下:
std::string base64_decode(std::string const& encoded_string)
{
size_t in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
{
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4)
{
for (i = 0; i <4; i++)
{
char_array_4[i] = base64_chars.find(char_array_4[i]) & 0xff;
}
char_array_3[0] = ( char_array_4[0] << 2 ) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
{
ret += char_array_3[i];
}
i = 0;
}
}
if (i)
{
for (j = 0; j < i; j++)
{
char_array_4[j] = base64_chars.find(char_array_4[j]) & 0xff;
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
for (j = 0; (j < i - 1); j++)
{
ret += char_array_3[j];
}
}
return ret;
}
由于要支持URL安全传输,所以我在这两个函数之上又添加了URL安全的base64,主要添加的工作量很少,只是按照URL安全的base64的规则,对编码后的+和/进行替换而已,也就是“+”和“/”分别改成了“-”和“_”,所以实现的代码两不多,代码如下:
static inline char b64_to_safe(char c)
{
switch (c)
{
case '+':
return '-';
break;
case '/':
return '_';
break;
default:
return c;
}
}
static inline char safe_to_b64(char c)
{
switch (c) {
case '-':
return '+';
break;
case '_':
return '/';
break;
default:
return c;
}
}
std::string urlsafe_base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len)
{
std::string b64_encode = base64_encode(bytes_to_encode, in_len);
std::string::iterator it;
for(it = b64_encode.begin(); it != b64_encode.end(); it++)
{
*it = b64_to_safe(*it);
}
return b64_encode;
}
std::string urlsafe_base64_decode(std::string const& encoded_string)
{
std::string b64_decode = encoded_string;
std::string::iterator it;
for(it = b64_decode.begin(); it != b64_decode.end(); it++)
{
*it = safe_to_b64(*it);
}
b64_decode = base64_decode(b64_decode);
return b64_decode;
}
参考
[1] github https://github.com/drleq/CppBase64
[2] csdn https://blog.csdn.net/qq_20545367/article/details/79538530
山上层层桃李花,云间烟火是人家。
银钏金钗来负水,长刀短笠去烧畲。
唐代·刘禹锡《竹枝词九首·其九》