Base64编码/解码[C/C++]
#include <windows.h>
#include <cstdint>
#include <cassert>
#include <iostream>
#include <clocale>
#define _TEST
const char base64Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
/*
\\ Base64编码
//
// 3字节为一组处理
// 3字节元数据 -> 编码后为4字节, 不足3字节的以0补足
//
// 比如:
// 待编码字符串: hello
// 转换为二进制: 01101000 01100101 01101100 01101100 01101111 00000000
// 以6位为单位转换为十进制: 26 6 21 44 27 6 60 0
// 以转换出来的数字索引编码表: a G V s b G 8 A
// 按比较常见的MIME BASE64编码规范的话, 最后填补的 'A' 一般用 '=' 代替
// 最终结果就是: aGVsbG8=
//
*/
void Base64Encode(
IN const char *pInData,
IN uint32_t uLen,
OUT char **pOut,
OUT uint32_t *uLenOut)
{
assert(pInData && uLen > 0);
#ifdef _TEST
for(int i = 0; i < uLen; ++i)
printf("%x ", pInData[i]);
putc('\n', stdout);
#endif
// 计算所需输出空间
uint32_t div = uLen / 3;
uint32_t mod = uLen % 3;
// 针对字符串数据+1
*uLenOut = (div + (mod == 0 ? 0 : 1)) * 4 + 1;
// 开辟新空间
*pOut = (char*)calloc(*uLenOut, 1);
char *pTmp = *pOut;
// 填充新空间, 每3个原字节转化为新的4个字节数据
int iTmp = 0;
for (int i = 0, j = 0; i < uLen; i += 3, j += 4)
{
iTmp = uLen - i;
if (iTmp >= 3)
{
pTmp[j] = base64Table[pInData[i] >> 2];
pTmp[j + 1] = base64Table[((pInData[i] & 0x3) << 4) | (pInData[i + 1] >> 4)];
pTmp[j + 2] = base64Table[((pInData[i + 1] & 0xF) << 2) | ((pInData[i + 2] & 0xC0) >> 6)];
pTmp[j + 3] = base64Table[pInData[i + 2] & 0x3F];
}
else
{
// 刚好是3的倍数则不进入处理
// 处理 总数 % 3 的字节( 1 <= x <= 2 )
pTmp[j] = base64Table[pInData[i] >> 2];
if (iTmp == 2)
{
pTmp[j + 1] = base64Table[((pInData[i] & 0x3) << 4) | (pInData[i + 1] >> 4)];
pTmp[j + 2] = base64Table[(pInData[i + 1] & 0xF) << 2];
}
else
{
pTmp[j + 1] = base64Table[(pInData[i] & 0x3) << 4];
pTmp[j + 2] = '=';
}
pTmp[j + 3] = '=';
}
}
pTmp[*uLenOut - 1] = '\0';
// 实际长度减掉1
*uLenOut -= 1;
}
/*
\Base64解码
*/
void Base64Decode(
IN const char *pEncode,
IN uint32_t uEncLen,
OUT char **pDecode,
OUT uint32_t *uDecLen)
{
assert(pEncode && uEncLen > 0);
// 先将编码数据转换为索引表中的索引
char *uIndexs = (char *)calloc(uEncLen, 1);
int i, j;
char cTmp;
for (i = 0; i < uEncLen; i++)
{
cTmp = pEncode[i];
/* = */
if (cTmp == '=')
uIndexs[i] = 0;
/* + / */
else if (cTmp < '0')
uIndexs[i] = (cTmp == '/' ? 63 : 62);
/* 0123456789 */
else if (cTmp <= '9')
uIndexs[i] = 52 + (cTmp - '0');
/* A-Z */
else if (cTmp <= 'Z')
uIndexs[i] = cTmp - 'A';
/* a-z */
else if (cTmp <= 'z')
uIndexs[i] = 26 + cTmp - 'a';
}
// 现在得到了原来编码时用的索引值列表
// 因为编码时是在原数据上按6bit的单位依次取得的索引
// 所以现在的每个索引应该去掉高位2bit然后再组装成原数据就行了
// 计算所需空间
*uDecLen = (uEncLen / 4) * 3 + 1;
*pDecode = (char *)calloc(*uDecLen, 1);
char *pTmp = *pDecode;
// 解码按4个索引对3个原字节来分组处理
for (i = 0, j = 0; i < uEncLen; i += 4, j += 3)
{
/* 每组第一个索引后6位 + 第二个索引去高两位后的次高2位 */
pTmp[j] = (uIndexs[i] << 2) | ((uIndexs[i + 1] >> 4) & 0x3);
/* 每组第二个索引余下的低4位 + 第三个索引的去高两位后的次高4位*/
pTmp[j + 1] = ((uIndexs[i + 1] & 0xf) << 4) | ((uIndexs[i + 2] >> 2) & 0xF);
/* 每组第三个索引的低2位 + 第四个索引的低6位 */
pTmp[j + 2] = ((uIndexs[i + 2] & 0x3) << 6) | (uIndexs[i + 3] & 0x3F);
}
free(uIndexs);
}
// MAIN
int wmain(int argc, wchar_t **argv)
{
assert(argc == 2);
char *lc = setlocale(LC_ALL, "chs");
uint32_t uLenIn = wcsnlen(argv[1], MAX_PATH);
uint32_t uLenOut = 0;
// 编码
char *pEncOut = NULL;
// 如果是字符串, 因为统一用了UNICODE字符集, 编码为U16_LE, 所以长度记得 *2
// 如果是一般二进制流, 就该是多长就多长了
Base64Encode((const char*)argv[1], uLenIn*2, &pEncOut, &uLenOut);
if (pEncOut)
{
std::cout << "Encrypt: " << pEncOut << std::endl;
// 解码
char *pDecOut = NULL;
Base64Decode(pEncOut, uLenOut, &pDecOut, &uLenOut);
if (pDecOut)
{
std::wcout << L"Decrypted: " << ((wchar_t*)pDecOut) << std::endl;
free(pDecOut);
}
else
{
std::wcout << L"Base64Decode failed\n";
}
free(pEncOut);
}
else
{
std::wcout << L"Base64Encode failed\n";
}
setlocale(LC_ALL, lc);
return 0;
}
编码原理图示(来源):