kn0sky 发表于 2021-10-28 14:21

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;
}
```

由于计算结果是一样的,就不截图了

tlf 发表于 2021-10-28 14:36

潇洒超人 发表于 2021-10-28 14:55

还可以出一个base64转图片的

rapol0 发表于 2021-10-28 17:01

支持一下{:1_927:}

QingRemix 发表于 2021-10-28 17:14

谢谢分享学习了

liiyii00 发表于 2021-10-28 18:30

学习一下 谢谢

2513002960 发表于 2021-10-31 21:47

厉害了,之前只用,从来没去了解它的原理

Ishin 发表于 2021-11-1 00:21

retStr += indexTable;//原先是8bit,现在只要取前6bit作为索引。
      retStr += indexTable;// 这里到底是什么意思?byte1 & 00000011 ?
      retStr += indexTable;// 这里到底是什么意思?byte2 & 00000111 ?
      retStr += indexTable;
上面这一段我实在看不懂到底是什么意思?
为什么byte1要和00000011 进行位与运算?不是应该直接左移4位就可以拿到需要的4位的值了吗?,然后直接和byte2的前2为进行按位或运算就好了?
我苦思而万万不能解?
求解释,谢谢~~!!

kn0sky 发表于 2021-11-2 18:24

Ishin 发表于 2021-11-1 00:21
retStr += indexTable;//原先是8bit,现在只要取前6bit作为索引。
      retStr + ...

首先要知道:
第一个索引值是byte1的前6位
第二个索引值是byte1的最后2位,和byte2的前4位
因为只需要用到byte1的最后两位,所以与运算一下0x11,这样就能将其他位置零,单独取出2位来
页: [1]
查看完整版本: Base64编码原理&代码实现【附源码】