2021.12.1更新:使用位断进行base64编码操作,见文末
Base64编码原理
最近学某个东西的时候提到了Base64编码,可以用来数据加密,出于好奇,探究一下Base64到底是怎么一回事,便有了本文记录,如有问题,还请师傅们指出!
首先,Base64编码有什么用呢?
- 传输:用于将二进制数据编码成文本数据进行传输,比如图片的传输等...
- 加密:通过修改索引表进行编码,没有索引表就很难解码
- 还有啥用我就不知道了
其次,Base64编码是怎么一回事?
Base64一句话概括:就是把二进制数据按位切割形成索引,通过索引映射到索引表中的可见字符,用这个可见字符来代替二进制数据传输
因为一个字节的二进制数据有8位,数据范围是0~255,可见字符比这少多了,不能直接用这8位数据来索引可见字符
所以需要进行切割,拿出一部分位来进行当索引,根据ASCII码表,可见字符数量有26个大写字母+26个小写字母+10个数字+27个符号 = 89个:
直接用二进制位来当索引的话,需要取2的倍数个字符来当索引表,最大能取到2的6次方,也就是64个字符来作为索引表,索引需要6个二进制位(所以编码叫Base64,同理也有Base32,Base16等),通常默认使用的Base64索引表是这样的:
于是需要把二进制数据以6位一划分,每6位获得一个索引号,根据索引号来转换可见字符,如下图所示,这里的二进制数据是字符串,每个字符都6位一分,得到索引号,使用索引号对应索引表得到一个可见字符,组成Base64编码
每3个字节,就能获得4个索引号,当不满3个字节时,有两种情况如下图所示,当索引号6位没有完整填充时,则会在索引号6位的空位填充0,如果索引位6位全是空的,则直接使用=
字符(填充字符)代替索引获取字符
图来自网络
原理部分介绍到此结束,接下来就是代码实现环节了
Base64编码代码实现
代码比较简单,所以也没啥注释,要注意一下,这里的关键点在于位运算(对位的切割与拼接)
测试环境:
- Windows 10 21H1
- Visual Studio 2019
Base64.h
#pragma once
#include <iostream>
#include <string>
class Base64calc
{
public:
Base64calc();
Base64calc(char * newIndexTable,char newEntrychar = '=');
~Base64calc();
public:
void printIndexTable();
char getIndex(char encodeChar);
std::string encodeBase64(char* unencodeStr);
std::string decodeBase64(char* encodeStr);
private:
char indexTable[64]; // 索引表,需要64位,支持通过字符数组设定
char entryChar; // 空白填充符
};
Base64.cpp
#include "Base64.h"
Base64calc::Base64calc()
{
// 生成默认索引表
for (int i = 0; i < 64;i++ ) {
if (i <= 25) {
indexTable[i] = 'A' + i;
}
else if (i > 25 && i <= 51) {
indexTable[i] = 'a' + i - 26;
}
else if(i>51 && i<=61){
indexTable[i] = '0' + i - 52;
}
else {
if (i == 62) {
indexTable[i] = '+';
}
if (i == 63) {
indexTable[i] = '/';
}
}
}
entryChar = '=';
}
Base64calc::Base64calc(char* newIndexTable, char newEntrychar)
{
memcpy(indexTable, newIndexTable, 64);
entryChar = newEntrychar;
}
Base64calc::~Base64calc()
{
}
void Base64calc::printIndexTable()
{
if (indexTable != NULL) {
printf("IndexTable: ");
for (int i = 0; i < 64; i++)
{
std::cout << indexTable[i];
}
std::cout << std::endl;
}
}
char Base64calc::getIndex(char encodeChar)
{
for (char i = 0; i < 64; i++)
{
if (encodeChar == indexTable[i])return i;
}
return -1;
}
std::string Base64calc::encodeBase64(char* unencodeStr)
{
std::string retStr;
int strLength = strlen(unencodeStr);
// 计算能完整转换的字节部分
for (int i = 0; i < strLength - strLength % 3; i += 3) {
char byte1 = unencodeStr[i];
char byte2 = unencodeStr[i+1];
char byte3 = unencodeStr[i+2];
retStr += indexTable[byte1 >> 2];
retStr += indexTable[byte2 >> 4 | (byte1 & 0x03) << 4];
retStr += indexTable[byte3 >> 6 | (byte2 & 0x0F) << 2];
retStr += indexTable[byte3 & 0x3F];
}
// 缺2字节时
if (strLength % 3 == 1) {
char byte = unencodeStr[strLength - 1];
retStr += indexTable[byte >> 2];
retStr += indexTable[(byte & 0x03) << 4];
retStr += entryChar;
retStr += entryChar;
}
// 缺1字节时
if (strLength % 3 == 2) {
char byte1 = unencodeStr[strLength - 2];
char byte2 = unencodeStr[strLength - 1];
retStr += indexTable[byte1 >> 2];
retStr += indexTable[byte2 >> 4 | (byte1 & 0x03) << 4];
retStr += indexTable[(byte2 & 0x0F) << 2];
retStr += entryChar;
}
return retStr;
}
std::string Base64calc::decodeBase64(char* encodeStr)
{
std::string retStr;
int strLength = strlen(encodeStr);
// 计算能完整转换的字节部分
for (int i = 0; i < strLength; i += 4) {
char byte1 = encodeStr[i];
char byte2 = encodeStr[i + 1];
char byte3 = encodeStr[i + 2];
char byte4 = encodeStr[i + 3];
retStr += (char)(getIndex(byte1) << 2) | (getIndex(byte2) >> 4);
if (byte3 != '=')
retStr += (getIndex(byte2) << 4) | (getIndex(byte3) >> 2);
if (byte4 != '=')
retStr += (getIndex(byte3) << 6) | (getIndex(byte4));
}
return retStr;
}
entry.cpp 测试代码
#include "Base64.h"
#include <iostream>
#include <string>
int main() {
std::string str = "Hello selph";
std::cout << "oriStr:" << str << std::endl;
std::cout << std::endl;
// 标准Base64索引表编码解码
Base64calc test;
test.printIndexTable();
std::string encode = test.encodeBase64((char*)str.data());
std::string decode = test.decodeBase64((char*)encode.data());
std::cout << "encode:" << encode << std::endl;
std::cout << "decode:" << decode << std::endl;
std::cout << std::endl;
// 自定义Base64索引表编码解码
char* myIndexTable =(char*) "abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ<>";
Base64calc testCostom(myIndexTable, 'a');
testCostom.printIndexTable();
std::string encode1 = testCostom.encodeBase64((char*)str.data());
std::string decode1 = testCostom.decodeBase64((char*)encode1.data());
std::cout << "encode:" << encode1 << std::endl;
std::cout << "decode:" << decode1 << std::endl;
return 0;
}
测试截图
*****2021.12.1更新****
编码操作的简化
使用位断可简化base64编码操作,对于解码操作,已经够简单了不需要再简化了
Base64.h 新增内容
struct base64unit
{
unsigned char b1 : 2;
unsigned char a : 6;
unsigned char c1 : 4;
unsigned char b2 : 4;
unsigned char d : 6;
unsigned char c2 : 2;
char index1;
char index2;
char index3;
char index4;
base64unit(const void* addr, int n) {
memset(this, 0, sizeof(base64unit));
memcpy((void*)this, addr, n);
index1 = a;
index2 = b1 << 4 | b2;
index3 = c1 << 2 | c2;
index4 = d;
}
};
Base64.cpp新增内容
std::string Base64calc::encodeBase64(char* unencodeStr, int test)
{
std::string retStrEx;
int strLength = strlen(unencodeStr);
// 计算能完整转换的字节部分
for (int i = 0; strLength; i += 3, strLength -= 3) {
base64unit tmp((const void*)&unencodeStr[i], strLength >= 3 ? 3 : strLength);
retStrEx += indexTable[tmp.index1];
retStrEx += indexTable[tmp.index2];
if (strLength >= 3) {
retStrEx += indexTable[tmp.index3];
retStrEx += indexTable[tmp.index4];
}
else {
retStrEx += strLength == 1 ? entryChar : indexTable[tmp.index3];
retStrEx += entryChar;
break;
}
}
return retStrEx;
}
由于计算结果是一样的,就不截图了