Base64编码原理&代码实现【附源码】
本帖最后由 kn0sky 于 2023-8-30 17:18 编辑2021.12.1更新:使用位断进行base64编码操作,见文末
---
## Base64编码原理
最近学某个东西的时候提到了Base64编码,可以用来数据加密,出于好奇,探究一下Base64到底是怎么一回事,便有了本文记录,如有问题,还请师傅们指出!
**首先,Base64编码有什么用呢?**
1. 传输:用于将二进制数据编码成文本数据进行传输,比如图片的传输等...
2. 加密:通过修改索引表进行编码,没有索引表就很难解码
3. 还有啥用我就不知道了
**其次,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
```cpp
#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位,支持通过字符数组设定
char entryChar; // 空白填充符
};
```
### Base64.cpp
```cpp
#include "Base64.h"
Base64calc::Base64calc()
{
// 生成默认索引表
for (int i = 0; i < 64;i++ ) {
if (i <= 25) {
indexTable = 'A' + i;
}
else if (i > 25 && i <= 51) {
indexTable = 'a' + i - 26;
}
else if(i>51 && i<=61){
indexTable = '0' + i - 52;
}
else {
if (i == 62) {
indexTable = '+';
}
if (i == 63) {
indexTable = '/';
}
}
}
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;
}
std::cout << std::endl;
}
}
char Base64calc::getIndex(char encodeChar)
{
for (char i = 0; i < 64; i++)
{
if (encodeChar == indexTable)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;
char byte2 = unencodeStr;
char byte3 = unencodeStr;
retStr += indexTable;
retStr += indexTable;
retStr += indexTable;
retStr += indexTable;
}
// 缺2字节时
if (strLength % 3 == 1) {
char byte = unencodeStr;
retStr += indexTable;
retStr += indexTable[(byte & 0x03) << 4];
retStr += entryChar;
retStr += entryChar;
}
// 缺1字节时
if (strLength % 3 == 2) {
char byte1 = unencodeStr;
char byte2 = unencodeStr;
retStr += indexTable;
retStr += indexTable;
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;
char byte2 = encodeStr;
char byte3 = encodeStr;
char byte4 = encodeStr;
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 测试代码
```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 新增内容
```cpp
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新增内容
```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, strLength >= 3 ? 3 : strLength);
retStrEx += indexTable;
retStrEx += indexTable;
if (strLength >= 3) {
retStrEx += indexTable;
retStrEx += indexTable;
}
else {
retStrEx += strLength == 1 ? entryChar : indexTable;
retStrEx += entryChar;
break;
}
}
return retStrEx;
}
```
由于计算结果是一样的,就不截图了
还可以出一个base64转图片的 支持一下{:1_927:} 谢谢分享学习了 学习一下 谢谢 厉害了,之前只用,从来没去了解它的原理 retStr += indexTable;//原先是8bit,现在只要取前6bit作为索引。
retStr += indexTable;// 这里到底是什么意思?byte1 & 00000011 ?
retStr += indexTable;// 这里到底是什么意思?byte2 & 00000111 ?
retStr += indexTable;
上面这一段我实在看不懂到底是什么意思?
为什么byte1要和00000011 进行位与运算?不是应该直接左移4位就可以拿到需要的4位的值了吗?,然后直接和byte2的前2为进行按位或运算就好了?
我苦思而万万不能解?
求解释,谢谢~~!! Ishin 发表于 2021-11-1 00:21
retStr += indexTable;//原先是8bit,现在只要取前6bit作为索引。
retStr + ...
首先要知道:
第一个索引值是byte1的前6位
第二个索引值是byte1的最后2位,和byte2的前4位
因为只需要用到byte1的最后两位,所以与运算一下0x11,这样就能将其他位置零,单独取出2位来
页:
[1]