前言
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
山上層層桃李花,雲間煙火是人家。
銀釧金釵來負水,長刀短笠去燒畲。
唐代·劉禹錫《竹枝詞九首·其九》