Base64原理和实现

Base64 概念

Base64已经成为网络上常见的传输8bit字节的编码方式之一。一般在做数据的传输时,系统之间的报文交互都需要使用Base64对明文进行编码,然后再进行加密,最后才传输。那么Base64的作用是什么?

在数据传输时经常遇到一类情况:使用全ASCII英文字母没问题,但是涉及中文就会乱码,或者网络传输的字符并不完全时可打印的字符,如二进制文件、图片等。Base64就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。

电子邮件刚问世时,只能传输英文,但后来随着用户增加,中文、日文等文字也有需求,但这些字符并不能被服务器或网关有效处理,这时候Base64就登场了。之后,Base在URL、Cookie、网页传输小二进制文件也由广泛使用。

Base64 编码原理

Base64的原理比较简单,Base64算法中通常都会定义一个类似这样的数组:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

Base64的字符索引表如下:

Base64字符索引表

以上字符集选用了A-Z、a-z、0-9、+、/ 64个可打印字符,这是标准的Base64协议规定。在平时使用中我们还会看到===号出现在Base64的编码结果中,=在此是作为填充字符出现,下面会提到。

具体转换步骤

  • 步骤1:将待转换的字符串每3个字节分为一组,每个字节占用8bit,总共占用24bit。

  • 步骤2:将总的24bit每6bit分为一组,共分为4组。

  • 步骤3:在每组前面添加两个0,每组由6bit变为8bit二进制位,共32bit,即4个字节。

  • 步骤4:根据Base64编码对照表获得对应的值。

从上面的步骤中可发现:

Base64字符表中的字符原本用6个bit就可以表示,现在前面添加了2个0,变为8bit,会造成一定浪费。Base64编码后的字符串,比原文大约大三分之一。

为什么使用3个字节一组呢?因为6和8的最小公倍数是24,三个字节正好24个二进制位,每6bit一组,恰好能够分为4组。

Base64 示例说明

从下图为例,具体分析Base64编码过程。

Base64编码1

第一步:M、a、n对应的ASCII码分别是77、97、110。对应的二进制分别是01001101、01100001、01101110。如图第二三行所示,由此组成一个24位的二进制字符串。

第二部:将24位每6位分成一组,共四组。

第三步:上面4组每一组前面补两个0,扩展成32个二进制位,此时变成4个字节:00010011、00010110、00000101、00101110。分别对应的值(如图Base64编码索引)为:19、22、5、46。

第四步:用上面的值在Base64编码表中进行查找,分别对应:T、W、F、u。因此字符串Man通过Base64编码之后就变为TWFu。

位数不足情况

上面是按照三个字节来举例说明的,如果字节数不足3个,如何处理?

Base64编码2

两个字节:两个字节共16位二进制,依旧按照规则进行分组。此时共16个bit位,每6个一组,则第三组缺少两位用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中字符串“BC”通过Base64编码后为“QKM=”。

一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个bit位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据都用“=”补上。因此,上图中“A”转换之后为“QQ==”。

注意事项

  • 大多数编码都是由字符串转化成二进制的过程,而Base64的编码则是从二进制转换为字符串。与常规恰恰相反,Base64编码主要用在传输、存储、表示二进制领域,不能算得上加密,只是无法直接看到明文。也可以通过打乱Base64编码来进行加密。
  • 中文有多种编码(例如utf-8、gb2312、gbk等),不同编码对应Base64编码结果不一样。

延伸

上面已经看到了Base64就是用6位(2的6次幂就是64)表示字符,因此成为Base64。同理,Base32就是用5位,Base16就是用4位。都可以按照上面的步骤进行演化。

Base64 详细实现

Base64.h

#pragma once
#include <string>

class CBase64
{
public:
public:
    CBase64();
    ~CBase64();

    /*编码
    DataByte
    [in]输入的数据长度,以字节为单位
    */
    std::string Encode(const char *Data, int DataByte);

    /*解码
    DataByte
    [in]输入的数据长度,以字节为单位
    OutByte
    [out]输出的数据长度,以字节为单位,请不要通过返回值计算
    输出数据的长度
    */
    std::string Decode(const char *Data, int DataByte, int &OutByte);
};

Base64.cpp

#include "Base64.h"

CBase64::CBase64(){}

CBase64::~CBase64(){}

std::string CBase64::Encode(const char *Data, int DataByte)
{
    //编码表
    const char EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    //返回值
    std::string strEncode;
    unsigned char Tmp[4] = {0};
    int LineLength = 0;
    for (int i = 0; i < (int)(DataByte / 3); i++)
    {
        Tmp[1] = *Data++;
        Tmp[2] = *Data++;
        Tmp[3] = *Data++;
        strEncode += EncodeTable[Tmp[1] >> 2];
        strEncode += EncodeTable[((Tmp[1] << 4) | (Tmp[2] >> 4)) & 0x3F];
        strEncode += EncodeTable[((Tmp[2] << 2) | (Tmp[3] >> 6)) & 0x3F];
        strEncode += EncodeTable[Tmp[3] & 0x3F];
        if (LineLength += 4, LineLength == 76)
        {
            strEncode += "\r\n";
            LineLength = 0;
        }
    }
    //对剩余数据进行编码
    int Mod = DataByte % 3;
    if (Mod == 1)
    {
        Tmp[1] = *Data++;
        strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];
        strEncode += EncodeTable[((Tmp[1] & 0x03) << 4)];
        strEncode += "==";
    }
    else if (Mod == 2)
    {
        Tmp[1] = *Data++;
        Tmp[2] = *Data++;
        strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];
        strEncode += EncodeTable[((Tmp[1] & 0x03) << 4) | ((Tmp[2] & 0xF0) >> 4)];
        strEncode += EncodeTable[((Tmp[2] & 0x0F) << 2)];
        strEncode += "=";
    }

    return strEncode;
}

std::string CBase64::Decode(const char *Data, int DataByte, int &OutByte)
{
    //解码表
    const char DecodeTable[] =
        {
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            62, // '+'
            0, 0, 0,
            63,                                     // '/'
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
            0, 0, 0, 0, 0, 0, 0,
            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, // 'A'-'Z'
            0, 0, 0, 0, 0, 0,
            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, // 'a'-'z'
        };
    //返回值
    std::string strDecode;
    int nValue;
    int i = 0;
    while (i < DataByte)
    {
        if (*Data != '\r' && *Data != '\n')
        {
            nValue = DecodeTable[*Data++] << 18;
            nValue += DecodeTable[*Data++] << 12;
            strDecode += (nValue & 0x00FF0000) >> 16;
            OutByte++;
            if (*Data != '=')
            {
                nValue += DecodeTable[*Data++] << 6;
                strDecode += (nValue & 0x0000FF00) >> 8;
                OutByte++;
                if (*Data != '=')
                {
                    nValue += DecodeTable[*Data++];
                    strDecode += nValue & 0x000000FF;
                    OutByte++;
                }
            }
            i += 4;
        }
        else // 回车换行,跳过
        {
            Data++;
            i++;
        }
    }
    return strDecode;
}

测试main.cpp,假设D盘下有一张图片为:my.png

int main()
{
    ifstream inf;
    inf.open("D:\\my.png", ios::in | ios::binary);
    inf.seekg(0, std::ios_base::end);
    std::streampos sp = inf.tellg();
    int readsize = sp;
    //得到长度后,重新定位指针到文件头
    inf.seekg(0, std::ios_base::beg);
    char *readBuf = new char[readsize];
    inf.read(readBuf, readsize);

    CBase64 base;

    std::string strData = "";
    //对缓冲区编码
    strData = base.Encode((const char *)readBuf, readsize);
    delete[] readBuf;
    cout << strData << endl;
    //对缓冲区解码
    int dataLen(0);
    string strDecode = base.Decode(strData.data(), strData.size(), dataLen);

    ofstream outf;
    outf.open("D:\\you.png", ios::out | ios::binary);
    outf.write(strDecode.data(), strDecode.size());

    inf.close();
    outf.close();

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_peak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值