base64網上實現很多,但是如果是對中文進行編碼,有的無法編碼,有的編碼結果不一致
經過研究,發現base64算法都沒有問題,問題出現在漢字的編碼上,下面的base64編碼稍微做了一些改進,增加了編碼判斷
所有漢字一律轉換成UTF8后再進行base64編碼,與網絡上通用的base64解碼接軌。
以下base64算法使用了開源庫uchardet,需要下載uchardet源碼編譯生成動態庫調用
uchardet源碼網址:官網地址
/** * base64編碼原理 * 1. 源數據都是8位位寬的數據; * 2. 相當於分組碼,將源數據分為3個一組,每一組共24bits,采用每6位對應一個編碼碼字,那么3*8bits = 4*6its,將3個數據映射成4個數據, 由於編碼的碼字都是6位長度,換位10進制就是0-63,總共有64中可能性,這也是base64名字的來歷; * 3. 6bits對應10進制數對應的碼字如表;[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/] * 轉解碼過程 * 3 * 8 = 4 * 6; 3字節占24位, 4*6=24 * 先將要編碼的轉成對應的ASCII值 * 如編碼: s 1 3 * 對應ASCII值為: 115 49 51 * 對應二進制為: 01110011 00110001 00110011 * 將其6個分組分4組: 011100 110011 000100 110011 * 而計算機是以8bit存儲, 所以在每組的高位補兩個0如下: * 00011100 00110011 00000100 00110011對應:28 51 4 51 * 查找base64 轉換表 對應 c z E z * * 解碼 * c z E z * 對應ASCII值為 99 122 69 122 * 對應表base64_suffix_map的值為 28 51 4 51 * 對應二進制值為 00011100 00110011 00000100 00110011 * 依次去除每組的前兩位, 再拼接成3字節 * 即: 01110011 00110001 00110011 * 對應的就是s 1 3 */
#ifndef __BASE64_H_ #define __BASE64_H_ /******************************************************** Func Name: base64_encode Date Created: 2018-8-3 Description: base64編碼 Input: plaintext_in:源文件 length_in:源文件長度 Output: code_out:生成編碼文件 length_out:生成編碼文件的長度 Return: Caution: code_out內存由調用函數釋放 *********************************************************/ int base64_encode(const char *plaintext_in, int length_in, char **code_out, int *length_out); /******************************************************** Func Name: base64_decode Date Created: 2018-8-3 Description: base64解碼 Input: code_in;編碼后的文件 length_in:編碼后的文件長度 Output: plaintext_out:源文件 outlen:源文件長度 Return: Caution: plaintext_out內存由調用函數釋放 *********************************************************/ int base64_decode(char *code_in, int length_in, char **plaintext_out, int *outlen); /******************************************************** Func Name: base64_encode_calculate Date Created: 2018-8-2 Description: 編碼算法 Input: plaintext_in:源文件 length_in:源文件長度 Output: code_out:生成編碼文件 length_out:生成編碼文件的長度 Return: Caution: code_out內存由調用函數釋放 *********************************************************/ int base64_encode_calculate(const char *plaintext_in, int length_in, char **code_out, int *length_out); /******************************************************** Func Name: base64_decode_calculate Date Created: 2018-8-3 Description: 解碼算法 Input: code_in;編碼后的文件 length_in:編碼后的文件長度 Output: plaintext_out:源文件 outlen:源文件長度 Return: Caution: plaintext_out內存由調用函數釋放 *********************************************************/ int base64_decode_calculate(char *code_in, int length_in, char **plaintext_out, int *outlen); #endif
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "base64.h" #include "ucharcode.h" #define SRC_CHAR_SIZE 3 //源碼3個字節 #define BASE_CHAR_SIZE 4 //編碼后4個字節 #define CHAR_SIZE 8 //一個字節有8bits #define BASE_DATA_SIZE 6 //base編碼中6個bits是實際數據 #define DEFAULT_CODE "UTF-8" /******************************************************** Func Name: base64_encode_value Date Created: 2018-8-2 Description: 獲取對應編碼的值 Input: value_in:需要編碼的字符 Output: Return: Caution: *********************************************************/ char base64_encode_value(char value_in) { static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if (value_in > 63) return '='; return encoding[(int)value_in]; } /******************************************************** Func Name: base64_decode_value Date Created: 2018-8-2 Description: 獲取對應解碼的值 Input: value_in:需要解碼的字符 Output: Return: Caution: *********************************************************/ int base64_decode_value(char value_in) { static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56 ,57,58,59,60,61,-1,-1,-1,-2,-1 ,-1,-1,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,-1,-1 ,-1,-1,-1,-1,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}; static const char decoding_size = sizeof(decoding); //+ 的ascll值是43 value_in -= 43; if (value_in < 0 || value_in >= decoding_size) return -1; return decoding[(int)value_in]; } /******************************************************** Func Name: base64_encode Date Created: 2018-8-3 Description: base64編碼 Input: plaintext_in:源文件 length_in:源文件長度 Output: code_out:生成編碼文件 length_out:生成編碼文件的長度 Return: Caution: code_out內存由調用函數釋放 *********************************************************/ int base64_encode(const char *plaintext_in, int length_in, char **code_out, int *length_out) { int iRet = 0; char * pcCode = NULL; char *pcOut = NULL; //因為utf8一個漢字用3個字節表示,gdk用兩個字節表示,所以2倍的長度應該是夠用的 int iOutLen = length_in * 2; int iLeftNum = iOutLen; //參數校驗 if (NULL == plaintext_in || 0 == length_in || NULL == code_out || NULL == length_out) { return -1; } //編碼格式判斷 iRet = getStringCode(plaintext_in, length_in, &pcCode); if(0 != iRet) { return -2; } if (0 == strcmp(pcCode,DEFAULT_CODE)) { iRet = base64_encode_calculate(plaintext_in, length_in, code_out, length_out); return iRet; } //如果不是UTF-8編碼 需要轉碼 pcOut = (char *)malloc(iOutLen); if (NULL == pcOut) { return -3; } memset(pcOut, 0, iOutLen); iRet = transcodeToUTF8((char *)plaintext_in, length_in, pcOut, &iLeftNum, pcCode); if (0 != iRet) { //釋放資源 if (pcOut) { free(pcOut); pcOut = NULL; } return -4; } //釋放編碼資源 if (pcCode) { free(pcCode); pcCode = NULL; } iRet = base64_encode_calculate(pcOut, iOutLen - iLeftNum, code_out, length_out); //釋放資源 if (pcOut) { free(pcOut); pcOut = NULL; } return iRet; } /******************************************************** Func Name: base64_encode_calculate Date Created: 2018-8-2 Description: 編碼算法 Input: plaintext_in:源文件 length_in:源文件長度 Output: code_out:生成編碼文件 length_out:生成編碼文件的長度 Return: Caution: code_out內存由調用函數釋放 *********************************************************/ int base64_encode_calculate(const char *plaintext_in, int length_in, char **code_out, int *length_out) { int iPadLen = 0; //需要補齊的字節數 int iBaseLen = 0; //base64編碼后的字節數 int i = 0; char *pcOut = NULL; char gPadChar[BASE_CHAR_SIZE] = {0}; char * pcOutIndex = NULL; if (NULL == plaintext_in || 0 == length_in || NULL == code_out || NULL == length_out) { return -1; } if (0 != length_in % SRC_CHAR_SIZE) { //3 - length_in%3 源碼需要添加的字節數 iPadLen = SRC_CHAR_SIZE - length_in % SRC_CHAR_SIZE; } //計算base編碼后實際長度 +1 最后一個字符是'/0' iBaseLen = (length_in + iPadLen)* CHAR_SIZE / BASE_DATA_SIZE + 1; pcOut = (char *)malloc(sizeof(char) * iBaseLen); if (NULL == pcOut) { return -2; } memset(pcOut, 0, sizeof(char) * iBaseLen); pcOutIndex = pcOut; for (i = 0; i < length_in; i += SRC_CHAR_SIZE) { if (i == (length_in + iPadLen -3) && 0 != iPadLen) { if (1 == iPadLen) { //末尾實際上有兩個字節的數據,將這兩個字節的數據進行編碼 //第一個字節處理 gPadChar[0] = base64_encode_value(*(plaintext_in+i) >> 2 & 0x3f); //第二個字節處理 gPadChar[1] = base64_encode_value((*(plaintext_in+i) << 6 >> 2 & 0x30) + (*(plaintext_in+i+1) >> 4 & 0xf)); //第二個字節最后4bits處理,不足的補0 gPadChar[2] = base64_encode_value((*(plaintext_in+i+1) << 4 >> 2 & 0x3c)); //沒有字節的以'='代替 因為base編碼只有6bit真實數據,所以不會和源數據中的"="重復 gPadChar[3] = '='; }else if (2 == iPadLen) { //末尾實際上有一個字節的數據,將這一個字節的數據進行編碼 //第一個字節處理 gPadChar[0] = base64_encode_value(*(plaintext_in+i) >> 2 & 0x3f); //第一個字節最后2bits處理 gPadChar[1] = base64_encode_value((*(plaintext_in+i) << 6 >> 2 & 0x30)); gPadChar[2] = '='; gPadChar[3] = '='; } }else { //第一個字節處理 0x3f前兩位清零 //取第一個字節的前6bits gPadChar[0] =base64_encode_value(*(plaintext_in+i) >> 2 & 0x3f); //第二個字節處理 //*(pcIndex+i) << 6 >> 2 & 0x30 取第一個字節的后2bits 無效數據清零 //*(pcIndex+i+1) >> 4 & 0xf 取第二個字節的前4bits 無效數據清零 gPadChar[1] = base64_encode_value((*(plaintext_in+i) << 6 >> 2 & 0x30) + (*(plaintext_in+i+1) >> 4 & 0xf)); //第二個字節和第三個字節處理 //*(pcIndex+i+1) << 4 >> 2 & 0x3c 取第二個字節的后4bits數據 無效數據清零 //(*(pcIndex+i+2) >> 6 & 0x3 取第三個字節的前2bits數據 無效數據清零 gPadChar[2] = base64_encode_value((*(plaintext_in+i+1) << 4 >> 2 & 0x3c) + (*(plaintext_in+i+2) >> 6 & 0x3)); //第三個字節處理 //*(pcIndex+i+2) & 0x3f 清除第三個字節的前2bits數據 gPadChar[3] = base64_encode_value(*(plaintext_in+i+2) & 0x3f); } //並非字符串操作,不能使用strcat memcpy(pcOutIndex, gPadChar, BASE_CHAR_SIZE); pcOutIndex += BASE_CHAR_SIZE; memset(gPadChar, 0, BASE_CHAR_SIZE); } pcOut[iBaseLen-1] = 0; *length_out = iBaseLen; *code_out = pcOut; return 0; } /******************************************************** Func Name: base64_decode Date Created: 2018-8-3 Description: base64解碼 Input: code_in;編碼后的文件 length_in:編碼后的文件長度 Output: plaintext_out:源文件 outlen:源文件長度 Return: Caution: plaintext_out內存由調用函數釋放 *********************************************************/ int base64_decode(char *code_in, int length_in, char **plaintext_out, int *outlen) { int iRet = base64_decode_calculate(code_in, length_in, plaintext_out, outlen); return iRet; } /******************************************************** Func Name: base64_decode_calculate Date Created: 2018-8-3 Description: 解碼算法 Input: code_in;編碼后的文件 length_in:編碼后的文件長度 Output: plaintext_out:源文件 outlen:源文件長度 Return: Caution: plaintext_out內存由調用函數釋放 *********************************************************/ int base64_decode_calculate(char *code_in, int length_in, char **plaintext_out, int *outlen) { int i = 0, j = 0; int iPadNum = 0; char *pcSrc = code_in; char * pcIndex = NULL; int iSrcLen = 0; char *pcOut = NULL; if (NULL == code_in || NULL == plaintext_out || NULL == outlen) { return -1; } while(1) { pcIndex = strchr(pcSrc, '='); if (NULL == pcIndex) { break; } iPadNum++; pcIndex += 1; pcSrc = pcIndex; } //計算源文件的字符個數 iSrcLen = length_in/4*3 - iPadNum; //末尾增加\0 pcOut = (char *)malloc(sizeof(char)*iSrcLen + 1); if (NULL == pcOut) { return -2; } memset(pcOut, 0, sizeof(char)*iSrcLen + 1); for (i = 0, j = 0; i < length_in; i += 4) { if ((i == length_in-4) && iPadNum > 0) { if (1 == iPadNum) { //實際上有兩個字符 只能用3個base字符表示 //字符1 pcOut[j] = (base64_decode_value(code_in[i]) << 2) + (base64_decode_value(code_in[i+1]) << 2 >> 6 & 0x3); //字符2 pcOut[j+1] = (base64_decode_value(code_in[i+1]) << 4) + (base64_decode_value(code_in[i+2]) << 2 >> 4 & 0xf); j += 2; }else if (2 == iPadNum) { //實際上有1個字符數據 只能用2個base字符表示 pcOut[j] = (base64_decode_value(code_in[i])<<2) + (base64_decode_value(code_in[i+1]) << 2 >> 6 &0x3); j ++; } }else { //字符1 pcOut[j] = (base64_decode_value(code_in[i])<<2) + (base64_decode_value(code_in[i+1]) << 2 >> 6 &0x3); //字符2 pcOut[j+1] = (base64_decode_value(code_in[i+1]) << 4) + (base64_decode_value(code_in[i+2]) << 2 >> 4 & 0xf); //字符3 pcOut[j+2] = (base64_decode_value(code_in[i+2]) << 6) + (base64_decode_value(code_in[i+3]) & 0x3f); j += 3; } } pcOut[iSrcLen] = '\0'; *plaintext_out = pcOut; *outlen = iSrcLen; return 0; }
#ifndef __UNCHARCODE_H_ #define __UNCHARCODE_H_ /******************************************************** Func Name: getStringCode Date Created: 2018-8-3 Description: 獲取字符串編碼 Input: pcSrc:源編碼數據 iLenIn:源編碼長度 pcCode:結果存放內存地址 Output: Return: error code Caution: pcDest內存需要由調用函數釋放 *********************************************************/ int getStringCode(const char *pcSrc, int iLenIn, char **pcCode); /******************************************************** Func Name: transcodeToUTF8 Date Created: 2018-8-3 Description: 轉碼UTF8 Input: pcSrc:源編碼數據 lenIn:源編碼長度 pcDest:結果存放內存地址(INOUT) lenOut:剩余內存單位個數(INOUT) pcCodeType:源編碼類型 Output: Return: error code Caution: pcDest內存需要由調用函數分配 *********************************************************/ int transcodeToUTF8(char *pcSrc, int lenIn, char *pcDest, int *lenOut,const char *pcCodeType); /******************************************************** Func Name: transcodeToGBK Date Created: 2018-8-3 Description: 轉碼GBK Input: pcSrc:源編碼數據 lenIn:源編碼長度 pcDest:結果存放內存地址(INOUT) lenOut:剩余內存單位個數(INOUT) pcCodeType:源編碼類型 Output: Return: error code Caution: pcDest內存需要由調用函數分配 *********************************************************/ int transcodeToGBK(char *pcSrc, int lenIn, char *pcDest, int *lenOut,const char *pcCodeType); #endif
#include <stdlib.h> #include <string.h> #include <iconv.h> #include "ucharcode.h" #include "uchardet.h" /******************************************************** Func Name: transcodeToUTF8 Date Created: 2018-8-3 Description: 轉碼UTF8 Input: pcSrc:源編碼數據 lenIn:源編碼長度 pcDest:結果存放內存地址(INOUT) lenOut:剩余內存單位個數(INOUT) pcCodeType:源編碼類型 Output: Return: error code Caution: pcDest內存需要由調用函數分配 *********************************************************/ int fromcodeToCode(char *pcSrc, int lenIn, char *pcDest, int *lenOut, const char *pcFromCode, const char *pcToCode) { int iRet = 0; //由於iconv()函數會修改指針,所以要保存源指針 char *pcStart = pcSrc; char *pcOut = pcDest; iconv_t cd = (iconv_t)-1; size_t inbytesleft = (size_t)lenIn; if (NULL == pcSrc || 0 >= lenIn || NULL == pcDest || NULL == lenOut || NULL == pcFromCode || NULL == pcToCode) { return -1; } cd = iconv_open(pcToCode, pcFromCode); if ((iconv_t)-1 == cd) { return -2; } /* *@param cd iconv_open()產生的句柄 *@param pcStart 需要轉換的字符串 *@param inbytesleft 存放還有多少字符沒有轉換 *@param pcOut 存放轉換后的字符串 *@param outlen 存放轉換后,pcOut剩余的空間 */ iRet = iconv(cd, &pcStart, &inbytesleft, &pcOut, (size_t *)lenOut); if (iRet < 0) { return -3; } iconv_close(cd); return iRet; } /******************************************************** Func Name: transcodeToUTF8 Date Created: 2018-8-3 Description: 轉碼UTF8 Input: pcSrc:源編碼數據 lenIn:源編碼長度 pcDest:結果存放內存地址(INOUT) lenOut:剩余內存單位個數(INOUT) pcCodeType:源編碼類型 Output: Return: error code Caution: pcDest內存需要由調用函數分配 *********************************************************/ int transcodeToUTF8(char *pcSrc, int lenIn, char *pcDest, int *lenOut,const char *pcCodeType) { int iRet = 0; const char * targetCode = "UTF8"; if (NULL == pcSrc || 0 >= lenIn || NULL == pcDest || NULL == lenOut || NULL == pcCodeType) { return -1; } iRet = fromcodeToCode(pcSrc, lenIn, pcDest, lenOut, pcCodeType, targetCode); return iRet; } /******************************************************** Func Name: transcodeToGBK Date Created: 2018-8-3 Description: 轉碼GBK Input: pcSrc:源編碼數據 lenIn:源編碼長度 pcDest:結果存放內存地址(INOUT) lenOut:剩余內存單位個數(INOUT) pcCodeType:源編碼類型 Output: Return: error code Caution: pcDest內存需要由調用函數分配 *********************************************************/ int transcodeToGBK(char *pcSrc, int lenIn, char *pcDest, int *lenOut, char *pcCodeType) { int iRet = 0; const char * targetCode = "GBK"; if (NULL == pcSrc || 0 >= lenIn || NULL == pcDest || NULL == lenOut || NULL == pcCodeType) { return -1; } iRet = fromcodeToCode(pcSrc, lenIn, pcDest, lenOut, pcCodeType, targetCode); return iRet; } /******************************************************** Func Name: getStringCode Date Created: 2018-8-3 Description: 獲取字符串編碼 Input: pcSrc:源編碼數據 iLenIn:源編碼長度 pcCode:結果存放內存地址 Output: Return: error code Caution: pcDest內存需要由調用函數釋放 *********************************************************/ int getStringCode(const char *pcSrc, int iLenIn, char **pcCode) { uchardet_t ud; int iErrorCode = 0; char *pcBuf = NULL; const char *pcDest = NULL; if (NULL == pcSrc || 0 == iLenIn || NULL == pcCode) { return -1; } ud = uchardet_new(); //如果樣本字符不夠,那么有可能導致分析失敗 iErrorCode = uchardet_handle_data(ud, pcSrc, iLenIn); if (0 != iErrorCode) { return -2; } uchardet_data_end(ud); pcDest = uchardet_get_charset(ud); //+1 多留一個字符'\0' pcBuf = (char *)malloc(strlen(pcDest)+1); if (NULL == pcBuf) { return -3; } memset(pcBuf, 0, strlen(pcDest)+1); strcpy(pcBuf,pcDest); *pcCode = pcBuf; uchardet_delete(ud); return 0; }
#include <iostream> #include <cstring> #include "base64.h" using namespace std; /* 測試小程序 */ void test() { int len = 0; int iRet= 0; char *pcOut = NULL; const char* s = "我是中國人"; char * pcBuf = NULL; int lastLen = 0; //base64編碼 iRet = base64_encode((char *)s, strlen(s), &pcOut, &len); if (0 != iRet) { cout << "base64_encode() error ." << endl; } cout << "result = " << pcOut << endl; //base64解碼 iRet = base64_decode(pcOut, len, &pcBuf, &lastLen); if (0 != iRet) { cout << "base64_decode() error ." << endl; } cout << "end len = " << lastLen << " result = " << pcBuf << endl; if (pcOut) { free(pcOut); pcOut = NULL; } if (pcBuf) { free(pcBuf); pcBuf = NULL; } } int main() { test(); getchar(); return 0; }