Base64编解码及其C++实现


title: "Base64编解码及其C++实现"
date: 2021-02-08T19:54:53+08:00
draft: true
tags: ['base64','c++']
author: "dadigang"
author_cn: "大地缸"
personal: "http://www.real007.cn"


关于作者

http://www.real007.cn/about

这两天在为公司的框架添加一个Base64加解密的模块,于是就想分享一下Base64的原理及自己的C++实现,借鉴了poco库。博文中的代码都是这两天写的代码的简洁版,可以完成Base64的编解码,方便易用。

不推荐造轮子,但是轮子在别的车上,你得自己拆下来,然后根据自己车的尺寸DIY你的轮子,安在自己的车上,当然,你还需要了解这个轮子的原理,万一哪天轮子坏了要你来修呢。

Base64简介

Base64是一种字节编码方式,一个字节可表示256个值,那么ASCII中0x20 ~ 0x7E是可打印字符,也就是说只有这么些范围的字符打印出来是可见的。Base64编码就是把字节转化成ASCII码中可打印的字符(Base64编码是从二进制到可见字符的过程)。它是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。

Base64应用

  • 由于二进制的一些字符在网络协议中属于控制字符,不能直接传送,因此需要用Base64编码之后传输,编码之后传输的是一些很普通的ASCII字符。
  • Base64常用于邮件编码,当邮件中有二进制数据时,就要编码转换。
  • 图片的编码
  • Url中有二进制数据,这个时候需要Base64编码(Web安全的Base64)
  • 可以进行简单的加密,Base64的编解码规则是透明的,因此用Base64加密时要加盐。

Base64原理

用一句话来说明Base64编码的原理:“ 把3个字节变成4个字节”。

这么说吧,3个字节一共24个bit,把这24个bit依次分成4个组,每个组6个bit,再把这6个bit塞到一个字节中去(最高位补两个0就变成8个bit),就会变成4个字节。没了。

因为6个bit最多能表示26=64

2

6

=

64

,也就是说Base64编码出来的字符种类只有64个,这也是Base64名字的由来。

那我们就要从ASCII中0x20 ~ 0x7E是可打印字符选出64个普通的ASCII字符

下面就是映射表(来自维基百科):

这里写图片描述

解密的过程就是一个逆的过程,加解密下面有一张我自己绘制的图,看了你就明白了。

这里写图片描述

看了这张图片,你会发现貌似只有字节数是3的倍数才能处理啊,那么显示情况中,不是3的倍数的情况多的是,怎么办?补零加=号,看下图:

这里写图片描述

因此Base64编码后有时候可以看到=或者==这都是正常的。

那么解码的密文就有一定的要求的,从前面的分析中得出来, 加密之后形成的密文长度一定是4的倍数,且字符串中的字符一定要在映射表中,或者字符为=,还有,只可能有一个=或一个==

C++编码实现Base64

首先需要做出两张表,一张是编码映射表,一张是解码映射表。

编码表:

const unsigned char Base64EncodeMap[64] =
{
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '+', '/'
};

打编码表可以使用下面的代码:

for (int i = 0; i < 26; Base64EncodeMap[0 + i] = 'A' + i, ++i) {}
for (int i = 0; i < 26; Base64EncodeMap[26 + i] = 'a' + i, ++i) {}
for (int i = 0; i < 10; Base64EncodeMap[52 + i] = '0' + i, ++i) {}
Base64EncodeMap[62] = '+';
Base64EncodeMap[62] = '/';

解码表:

unsigned char Base64DecodeMap[256] =
{
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F,
    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
    0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF,
    0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
    0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
    0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

打解码表可以使用下面的代码:

for (int i = 0; i < (1 << 8); Base64DecodeMap[i++] = 0xFF) {}
for (int i = 0; i < (1 << 8); Base64DecodeMap[Base64EncodeMap[i]] = i, ++i) {}
Base64DecodeMap['='] = 0;

编解码的步骤就是上面两张图,用位运算就可以搞定了。下面是核心代码。

/*Encode*/

// 0 index byte
index = _group[0] >> 2;
_buf.push_back(_encodeMap[index]);
// 1 index byte
index = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
_buf.push_back(_encodeMap[index]);
// 2 index byte
index = ((_group[1] & 0x0F) << 2) | (_group[2] >> 6);
_buf.push_back(_encodeMap[index]);
// 3 index byte
index = _group[2] & 0x3F;
_buf.push_back(_encodeMap[index]);
/*Decode*/

buff[0] = (_decodeMap[_group[0]] << 2) | (_decodeMap[_group[1]] >> 4);
if (_group[2] != '=')
{
    buff[1] = ((_decodeMap[_group[1]] & 0x0F) << 4) | (_decodeMap[_group[2]] >> 2);
    top = 2;
}
if (_group[3] != '=')
{
    buff[2] = (_decodeMap[_group[2]] << 6) | _decodeMap[_group[3]];
    top = 3;
}

for (unsigned int i = 0; i < top; ++i)
{
    _buf.push_back(buff[i]);
}

然后把编码做成一个类Base64Encrypt,解码也做成一个类Base64Decrypt

你要考虑到的一点是,编解码有时候不会一次性完成,为什么呢,实际情况中,要编解码的字符串可能很长,你不可能把字符串一次性读到内存中,因此就要分批编解码,但是我们编码是以3个字节为一组进行编码,因此,在类中需要设置一个缓存,长度为3,当缓存满了,就直接把让3个字节去编码。poco库就是这么实现的。

编码出来的结果用vector去存储,但是vector存储又会发生内存的频繁复制(考虑下vector的实现)。怎么办呢?由于Base64的编解码规则是透明的,给你一段字符串,你马上可以计算出加密出来的字符串有多长。这样子就可以使用std::vector::reserve成员函数来为vector预先分配空间。

Base64Encrypt

class Base64Encrypt
{
public:
    Base64Encrypt() : _groupLength(0) {}
    Base64Encrypt(const void *input, size_t length) : Base64Encrypt()
    {
        Update(input, length);
    }

    void Update(const void *input, size_t length)
    {
        static const size_t LEN = 3;
        _buf.reserve(_buf.size() + (length - (LEN - _groupLength) + LEN - 1) / LEN * 4 + 1);
        const unsigned char *buff = reinterpret_cast<const unsigned char *>(input);
        unsigned int i;

        for (i = 0; i < length; ++i)
        {
            _group[_groupLength++] = buff[i];
            if (_groupLength == LEN)
            {
                Encode();
            }
        }
    }
    const unsigned char *CipherText()
    {
        Final();
        return _buf.data();
    }
    std::string GetString()
    {
        const char *pstr = (const char *)CipherText();
        size_t length = GetSize();
        return std::string(pstr, length);
    }
    void Reset()
    {
        _buf.clear();
        _groupLength = 0;
        for (unsigned int i = 0; i < sizeof(_group) / sizeof(_group[0]); ++i)
        {
            _group[i] = 0;
        }
    }
    size_t GetSize()
    {
        CipherText();
        return _buf.size();
    }

private:
    Base64Encrypt(const Base64Encrypt &) = delete;
    Base64Encrypt & operator = (const Base64Encrypt &) = delete;

    void Encode()
    {
        unsigned char index;

        // 0 index byte
        index = _group[0] >> 2;
        _buf.push_back(Base64EncodeMap[index]);
        // 1 index byte
        index = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
        _buf.push_back(Base64EncodeMap[index]);
        // 2 index byte
        index = ((_group[1] & 0x0F) << 2) | (_group[2] >> 6);
        _buf.push_back(Base64EncodeMap[index]);
        // 3 index byte
        index = _group[2] & 0x3F;
        _buf.push_back(Base64EncodeMap[index]);

        _groupLength = 0;
    }
    void Final()
    {
        unsigned char index;

        if (_groupLength == 1)
        {
            _group[1] = 0;
            // 0 index byte
            index = _group[0] >> 2;
            _buf.push_back(Base64EncodeMap[index]);
            // 1 index byte
            index = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
            _buf.push_back(Base64EncodeMap[index]);
            // 2 index byte
            _buf.push_back('=');
            // 3 index byte
            _buf.push_back('=');
        }
        else if (_groupLength == 2)
        {
            _group[2] = 0;
            // 0 index byte
            index = _group[0] >> 2;
            _buf.push_back(Base64EncodeMap[index]);
            // 1 index byte
            index = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
            _buf.push_back(Base64EncodeMap[index]);
            // 2 index byte
            index = ((_group[1] & 0x0F) << 2) | (_group[2] >> 6);
            _buf.push_back(Base64EncodeMap[index]);
            // 3 index byte
            _buf.push_back('=');
        }

        _groupLength = 0;
    }

private:
    std::vector<unsigned char> _buf;
    unsigned char _group[3];
    int _groupLength;

    static const unsigned char Base64EncodeMap[64];
};

注意_buf.reserve(_buf.size() + (length - (LEN - _groupLength) + LEN - 1) / LEN * 4 + 1);这行代码,好好体会下。

Base64Decrypt

class Base64Decrypt
{
public:
    Base64Decrypt() : _groupLength(0) {}
    Base64Decrypt(const void *input, size_t length) : Base64Decrypt()
    {
        Update(input, length);
    }

    void Update(const void *input, size_t length)
    {
        static const size_t LEN = 4;
        _buf.reserve(_buf.size() + (length + (LEN - _groupLength) + LEN - 1) / LEN * 3 + 1);
        const unsigned char *buff = reinterpret_cast<const unsigned char *>(input);
        unsigned int i;

        for (i = 0; i < length; ++i)
        {
            if (Base64DecodeMap[buff[i]] == 0xFF)
            {
                throw std::invalid_argument("ciphertext is illegal");
            }

            _group[_groupLength++] = buff[i];
            if (_groupLength == LEN)
            {
                Decode();
            }
        }
    }

    const unsigned char *PlainText()
    {
        if (_groupLength)
        {
            throw std::invalid_argument("ciphertext's length must be a multiple of 4");
        }
        return _buf.data();
    }
    void Reset()
    {
        _buf.clear();
        _groupLength = 0;
        for (unsigned int i = 0; i < sizeof(_group) / sizeof(_group[0]); ++i)
        {
            _group[i] = 0;
        }
    }
    size_t GetSize()
    {
        PlainText();
        return _buf.size();
    }

private:
    Base64Decrypt(const Base64Decrypt &) = delete;
    Base64Decrypt & operator = (const Base64Decrypt &) = delete;

    void Decode()
    {
        unsigned char buff[3];
        unsigned int top = 1;
        if (_group[0] == '=' || _group[1] == '=')
        {
            throw std::invalid_argument("ciphertext is illegal");
        }

        buff[0] = (Base64DecodeMap[_group[0]] << 2) | (Base64DecodeMap[_group[1]] >> 4);
        if (_group[2] != '=')
        {
            buff[1] = ((Base64DecodeMap[_group[1]] & 0x0F) << 4) | (Base64DecodeMap[_group[2]] >> 2);
            top = 2;
        }
        if (_group[3] != '=')
        {
            buff[2] = (Base64DecodeMap[_group[2]] << 6) | Base64DecodeMap[_group[3]];
            top = 3;
        }

        for (unsigned int i = 0; i < top; ++i)
        {
            _buf.push_back(buff[i]);
        }

        _groupLength = 0;
    }

private:
    std::vector<unsigned char> _buf;
    unsigned char _group[4];
    int _groupLength;

    static unsigned char Base64DecodeMap[256];
};

使用Base64Decrypt需要用try-catch代码块包裹起来,因为对密文进行解码,密文可能不合法,这个时候Base64Decrypt类只能通过抛出std::invalid_argument异常来告诉用户。

Web安全版Base64

Web安全版Base64其实和标准版一样,只不过映射表中的+对应-, /对应_,所有代码只要根据这两个地方具体改动,就可以完成Web安全版的Base64,Web安全版Base64也叫SafeUrlBase64。

测试

怎么知道你写的Base64是正确的,这需要测试,我们需要知道一段字符串通过Base64加密出来正确的密文,这个可以通过在线的Base64编解码网站实现,不过,更推荐Python中的base64模块,其中的base64.b64encode方法编码,base64.urlsafe_b64encode可以进行Web安全的Base64编码,有了正确的密文,进行比较;之后可以把这些正确的密文解码回来,看看是不是等于之前的字符串。


参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容