一文搞懂Base64編碼原理
Base64是最常見(jiàn)的用于傳輸8Bit字節(jié)碼的編碼方式之一,它是一種基于64個(gè)可打印字符來(lái)表示二進(jìn)制數(shù)據(jù)的方法。
Base64是什么
Base64編碼,是由64個(gè)字符組成編碼集:26個(gè)大寫(xiě)字母A~Z,26個(gè)小寫(xiě)字母a~z,10個(gè)數(shù)字0~9,符號(hào)“+”與符號(hào)“/”。Base64編碼的基本思路是將原始數(shù)據(jù)的三個(gè)字節(jié)拆分轉(zhuǎn)化為四個(gè)字節(jié),然后根據(jù)Base64的對(duì)應(yīng)表,得到對(duì)應(yīng)的編碼數(shù)據(jù)。
當(dāng)原始數(shù)據(jù)湊不夠三個(gè)字節(jié)時(shí),編碼結(jié)果中會(huì)使用額外的符號(hào)“=”來(lái)表示這種情況。
Base64編碼表
| 碼值 | 字符 | 碼值 | 字符 | 碼值 | 字符 |
|---|---|---|---|---|---|
| 0 | A | 26 | a | 52 | 0 |
| 1 | B | 27 | b | 53 | 1 |
| 2 | C | 28 | c | 54 | 2 |
| 3 | D | 29 | d | 55 | 3 |
| 4 | E | 30 | e | 56 | 4 |
| 5 | F | 31 | f | 57 | 5 |
| 6 | G | 32 | g | 58 | 6 |
| 7 | H | 33 | h | 59 | 7 |
| 8 | I | 34 | i | 60 | 8 |
| 9 | J | 35 | j | 61 | 9 |
| 10 | K | 36 | k | 62 | + |
| 11 | L | 37 | l | 63 | / |
| 12 | M | 38 | m | ||
| 13 | N | 39 | n | ||
| 14 | O | 40 | o | ||
| 15 | P | 41 | p | ||
| 16 | Q | 42 | q | ||
| 17 | R | 43 | r | ||
| 18 | S | 44 | s | ||
| 19 | T | 45 | t | ||
| 20 | U | 46 | u | ||
| 21 | V | 47 | v | ||
| 22 | W | 48 | w | ||
| 23 | X | 49 | x | ||
| 24 | Y | 50 | y | ||
| 25 | Z | 51 | z |
Base64編碼步驟
-
將原始數(shù)據(jù)按照每三個(gè)字節(jié)作為一組進(jìn)行劃分,每組一共是24個(gè)二進(jìn)制位。 -
再將這24個(gè)二進(jìn)制位,每6個(gè)一劃分,分為四組(6×4=24個(gè)二進(jìn)制位)。 -
然后在每組前面補(bǔ)上00,擴(kuò)展成8×4=32個(gè)二進(jìn)制位,即四個(gè)字節(jié)(因?yàn)槊總€(gè)字節(jié)前面有2個(gè)0,所以每個(gè)字節(jié)的最大值是63)。 -
最后根據(jù)Base64編碼表,將這四個(gè)字節(jié)的碼值,轉(zhuǎn)換為對(duì)應(yīng)的Base64的字符即可。
Base64編碼過(guò)程舉例
情況1:正常的3個(gè)字節(jié)編碼
將單詞“PCB”轉(zhuǎn)換為Base64編碼:
-
"P"、"C"、"B"的ASCII值分別是80、67、66,對(duì)應(yīng)的二進(jìn)制值是0101 0000、0100 0011、0100 0010,將它們連成一個(gè)24位的二進(jìn)制字符串010100000100001101000010。
-
將這個(gè)24位的二進(jìn)制字符串,每6個(gè)一組分成4組:010100、000100、001101、000010。
-
在每組前面加兩個(gè)00,擴(kuò)展成32個(gè)二進(jìn)制位,即四個(gè)字節(jié):00010100、00000100、00001101、00000010。它們的十進(jìn)制值分別是19、22、5、46。(最前面加上兩個(gè)0只是為了湊成一個(gè)字節(jié),實(shí)際上其本身的數(shù)值是沒(méi)有變化的)
-
根據(jù)上表,得到每個(gè)值對(duì)應(yīng)Base64編碼,即U、E、N、C。
情況2:剩余2個(gè)字節(jié)編碼
對(duì)于2個(gè)字節(jié)(16個(gè)二進(jìn)制數(shù))的情況,比如將“PC”轉(zhuǎn)換為Base64編碼:
轉(zhuǎn)換方法同上,區(qū)別在于:
-
16個(gè)二進(jìn)制數(shù),每6個(gè)一組分割,最后剩余4個(gè),這時(shí)再在后面補(bǔ)兩個(gè)0湊成6個(gè)。 -
然后還按照基礎(chǔ)的方法轉(zhuǎn)換,最后補(bǔ)一個(gè)“=”即可
轉(zhuǎn)換過(guò)程如下表,最終將“PC”轉(zhuǎn)換為了“UEM=”
情況3:剩余1個(gè)字節(jié)編碼
對(duì)于12個(gè)字節(jié)(8個(gè)二進(jìn)制數(shù))的情況,比如將“P”轉(zhuǎn)換為Base64編碼:
轉(zhuǎn)換方法同上,區(qū)別在于:
-
16個(gè)二進(jìn)制數(shù),每6個(gè)一組分割,最后剩余2個(gè),后面要再補(bǔ)4個(gè)0 -
然后還按照基礎(chǔ)的方法轉(zhuǎn)換,最后補(bǔ)兩個(gè)“=”即可
轉(zhuǎn)換過(guò)程如下表,最終將“P”轉(zhuǎn)換為了“UA==”
Base64編解碼C程序
編碼程序
編碼的程序設(shè)計(jì)思路,就是按照上面講解的編碼過(guò)程,每3個(gè)原始字符為一組,進(jìn)行編碼,得到4個(gè)base64的字符。對(duì)于不夠3個(gè)字符的情況,編碼的base64的字符后面補(bǔ)上一到兩個(gè)=號(hào)。
#include <stdio.h>
#include <string.h>
/*base64符號(hào)表*/
const char *base64Arr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*base64增補(bǔ)符號(hào)*/
const char paddingChar = '=';
/** @func: base64_encode
* @brief: base64編碼
* @para: [srcData]:要進(jìn)行編碼的原始數(shù)據(jù)
* [resBase64]:base64編碼結(jié)果
* @return:none
*/
void base64_encode(const unsigned char * srcData, char * resBase64)
{
int i=0; /*原始數(shù)據(jù)索引*/
int j=0; /*base64結(jié)果索引*/
unsigned char transIdx=0; // 索引是8位,但是高兩位都為0
const int srcLen = strlen((const char*)srcData);
/*每3個(gè)一組,進(jìn)行編碼*/
for(i=0; i < srcLen; i+=3)
{
/*取出第1個(gè)字符的高6位*/
transIdx = ((srcData[i] >> 2) & 0x3f); /*0011 1111*/
/*查表*/
resBase64[j++] = base64Arr[(int)transIdx];
/*取出第1個(gè)字符的低2位*/
transIdx = ((srcData[i] << 4) & 0x30); /*0011 0000*/
/*第1個(gè)字符后面還有字符*/
if (i + 1 < srcLen)
{
/*取出第2個(gè)字符的高4位,并與第1個(gè)字符的低2位進(jìn)行組合*/
transIdx |= ((srcData[i + 1] >> 4) & 0x0f); /*0000 1111*/
/*查表*/
resBase64[j++] = base64Arr[(int)transIdx];
}
else /*第1個(gè)字符后面沒(méi)有字符了*/
{
/*直接使用第1個(gè)字符的低2位查表*/
resBase64[j++] = base64Arr[(int)transIdx];
/*然后補(bǔ)上兩個(gè)=號(hào)*/
resBase64[j++] = paddingChar;
resBase64[j++] = paddingChar;
break; /*沒(méi)有數(shù)據(jù)了,break結(jié)束*/
}
/*取出第2個(gè)字符的低4位*/
transIdx = ((srcData[i + 1] << 2) & 0x3c); /*0011 1100*/
/*第2個(gè)字符后面還有字符*/
if (i + 2 < srcLen)
{
/*取出第3個(gè)字符的高2位,并與第2個(gè)字符的低4位進(jìn)行組合*/
transIdx |= ((srcData[i + 2] >> 6) & 0x03); /*0000 0011*/
/*查表*/
resBase64[j++] = base64Arr[(int)transIdx];
/*取出第3個(gè)字符的低6位*/
transIdx = srcData[i + 2] & 0x3f; /*0011 1111*/
/*查表*/
resBase64[j++] = base64Arr[(int)transIdx];
}
else /*第2個(gè)字符后面沒(méi)有字符了*/
{
/*直接使用第2個(gè)字符的低4位查表*/
resBase64[j++] = base64Arr[(int)transIdx];
/*然后補(bǔ)上一個(gè)=號(hào)*/
resBase64[j++] = paddingChar;
break; /*沒(méi)有數(shù)據(jù)了,break結(jié)束*/
}
}
/*結(jié)束符*/
resBase64[j] = '\0';
}
解碼程序
解碼的程序設(shè)計(jì)思路,其實(shí)就是編碼的反過(guò)程,把要解碼的base64符號(hào),每4個(gè)為一組,譯碼成3個(gè)字符。對(duì)于最后出現(xiàn)的=的情況,就說(shuō)明是要結(jié)束了,直接使用剩余的base64符號(hào)進(jìn)行譯碼,然后就結(jié)束了。
/** @func: idx_in_base64Arr
* @brief: 在base64符號(hào)表中查找字符c對(duì)應(yīng)的索引值
* @para: [c]:要查找的字符
* @return:字符c在base64符號(hào)表中對(duì)應(yīng)的索引值(0~63)
*/
int idx_in_base64Arr(char c)
{
/*在base64表中搜索第一次出現(xiàn)字符c的位置*/
const char *pIdx = strchr(base64Arr, c);
if (NULL == pIdx)
{
/*找不到對(duì)應(yīng)的base64字符,說(shuō)明輸入的base64字符串有誤*/
return -1;
}
/*返回字符c在base64表中的位置*/
return (pIdx - base64Arr);
}
/** @func: base64_decode
* @brief: base64解碼
* @para: [srcBase64]:要進(jìn)行解碼的原始base64數(shù)據(jù)
* [resData]:解碼出的結(jié)果
* @return:none
*/
void base64_decode(const char *srcBase64, unsigned char *resData)
{
int i = 0; /*原始base64數(shù)據(jù)索引*/
int j = 0; /*解碼后的結(jié)果數(shù)據(jù)索引*/
int trans[4] = {0,0,0,0}; /*4個(gè)base64符號(hào)對(duì)應(yīng)的表中的位置(0~63的數(shù)字)轉(zhuǎn)換值*/
/*base64符號(hào)每4個(gè)一組,譯碼成3個(gè)字符*/
for (i=0; srcBase64[i]!='\0'; i+=4)
{
/*------譯碼第1個(gè)字符------*/
/*前2個(gè)base64符號(hào)在表中的位置(0~63的數(shù)字)*/
trans[0] = idx_in_base64Arr(srcBase64[i]);
trans[1] = idx_in_base64Arr(srcBase64[i+1]);
/*第1個(gè)符號(hào)的后6位,與第2個(gè)符號(hào)的6、5位,譯出第1個(gè)字符*/
resData[j++] = ((trans[0] << 2) & 0xfc) | ((trans[1]>>4) & 0x03); /*1111 1100 0000 0011 */
/*------譯碼第2個(gè)字符------*/
/*第3個(gè)base64符號(hào)是否是=號(hào)*/
if (srcBase64[i+2] != '=')
{
/*第3個(gè)base64符號(hào)在表中的位置(0~63的數(shù)字)*/
trans[2] = idx_in_base64Arr(srcBase64[i + 2]);
}
else
{
break;/*沒(méi)有數(shù)據(jù)了,break結(jié)束*/
}
/*第2個(gè)符號(hào)的后4位,與第3個(gè)符號(hào)的6、5、4、3位,譯出第2個(gè)字符*/
resData[j++] = ((trans[1] << 4) & 0xf0) | ((trans[2] >> 2) & 0x0f); /*1111 0000 0000 1111*/
/*------譯碼第3個(gè)字符------*/
/*第4個(gè)base64符號(hào)是否是=號(hào)*/
if (srcBase64[i + 3] != '=')
{
/*第4個(gè)base64符號(hào)在表中的位置(0~63的數(shù)字)*/
trans[3] = idx_in_base64Arr(srcBase64[i + 3]);
}
else
{
break;/*沒(méi)有數(shù)據(jù)了,break結(jié)束*/
}
/*第3個(gè)符號(hào)的后2位,與第4個(gè)符號(hào)的后6位,譯出第3個(gè)字符*/
resData[j++] = ((trans[2] << 6) & 0xc0) | (trans[3] & 0x3f); /*1100 0000 0011 1111*/
}
/*結(jié)束符*/
resData[j] = '\0';
}
測(cè)試程序
使用字符串“PCB”進(jìn)行base64編碼測(cè)試,然后再將編碼得到的結(jié)果,進(jìn)行解碼測(cè)試。
/*測(cè)試*/
int main()
{
/*定義要進(jìn)行base64編碼的字符串*/
const unsigned char *srcData = "PCB" ;
/*先測(cè)試編碼*/
char base64[128];
base64_encode(srcData, base64);
printf("base64編碼:%s\n",base64);
/*再測(cè)試解碼*/
char resData[128];
base64_decode(base64, (unsigned char*)resData);
printf("base64解碼:%s", resData);
return 0;
}
輸出結(jié)果如下,可以看出,“PCB”進(jìn)行base64編碼,得到了“UENC”,然后再反向解碼,又得到了“PCB”
base64編碼:UENC
base64解碼:PCB
--------------------------------
Process exited after 0.01123 seconds with return value 0
請(qǐng)按任意鍵繼續(xù). . .
完整程序可從我的gitee倉(cāng)庫(kù)下載(點(diǎn)擊閱讀原文,直達(dá)代碼倉(cāng)庫(kù)~)
