1: Base64
1: 概念
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。
在Base64中的可打印字符包括字母
A-Z
、a-z
、数字0-9
,这样共有62个字符,加上**+和/**, 共64个字符.
- 普通的文本数据也可以使用base64进行编解码
- Base64编解码的过程是可逆的
- Base64不能当做加密算法来使用
2: 应用场景
在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。 而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。
1: Base64 的应用
- http 通信的时候, 请求行中不允许出现特殊字符串
- 邮件收发的时候 -> 发送附件
- 发送的时候: 对附件进行base64编码
- 接收数据: 对附件进行base64解码
- 数据库的二进制数据存储 -> BLOB(这种格式对应的都是二进制数据)
- 存储的时候使用base64编码
- 读出之后使用base64解码
- 数据库中有中文时候
- 如果出现了乱码, 考虑使用base64对中文进行编解码
2: Base64 的算法
- 把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24)
- 一个字节8位
- base64编码的时候要分组
3个字节一组
- 将3个字节变成4个字节
- 这四个字节是有残缺的, 没个字节6位, 缺2位
- 在6位的前面补两个0,形成8位一个字节的形式
- 残缺的字节最前边补两个0 -> 得到了完整的字节
3: 字节的填充
- 通过填充 3个字节 -> 4个字节
- 如果剩下的字符不足3个字节,则用0填充,输出字符使用’=’,因此编码后输出的文本末尾可能会出现1或2个’=’, 表示补了多少字节,解码的时候,会自动去掉。
- 最后一个分组如果不够三字节, 缺一个补1个0, 缺两个字节补两个0
- 填充的0使用=标记
4: Base64 常见的问题
编码之后字符串长度变化?
- 变长了, 原始数据每3个字节就会多一字节
base64尾部的 = 的由来?
- base64会将原始数据分组, 每组3字节, =是对最后一个分组的填充
- 如果缺1或者2个会填充一个或2个=
2: Qt 中的Base64编码
// QByteArray // base64编码 QByteArray QByteArray::toBase64() const; // base64解码 [static] QByteArray QByteArray::fromBase64(const QByteArray &base64);
3: OpenSSL 中 Base64 的使用
来自于:OpenSSL中文手册之BIO库详解: https://blog.csdn.net/liao20081228/article/details/77193729
openssl中进行base64的编解码需要使用openssl中的BIO
- BIO可以对文件操作和内存等操作进行封装
- BIO链
- 将多个BIO对象进行串联, 类似于链表
- 如果将数据给到第一个节点, 数据会向下流动直到最后一个节点结束
- 对数据进行写操作
- 如果将数据给到最后一个节点, 数据就会从后往前流动, 直到第一个节点结束
- 进行了读操作
BIO 操作
// 创建BIO对象 BIO *BIO_new(const BIO_METHOD *type); // 封装了base64编码方法的BIO,写的时候进行编码,读的时候解码 BIO_METHOD* BIO_f_base64(); // 封装了内存操作的BIO接口,包括了对内存的读写操作 BIO_METHOD* BIO_s_mem(); // 创建一个内存型的BIO对象 // BIO * bio = BIO_new(BIO_s_mem()); BIO *BIO_new_mem_buf(void *buf, int len); // 创建一个磁盘文件操作的BIO对象 BIO* BIO_new_file(const char* filename, const char* mode); // 从BIO接口中读出len字节的数据到buf中。 // 成功就返回真正读出的数据的长度,失败返回0或-1。 int BIO_read(BIO *b, void *buf, int len); - buf: 存储数据的缓冲区地址 - len: buf的最大容量 // 往BIO中写入长度为len的数据。 // 成功返回真正写入的数据的长度,失败返回0或-1。 int BIO_write(BIO *b, const void *buf, int len); - buf: 要写入的数据, 写入到b对应的设备(内存/磁盘文件)中 - len: 要写入的数据的长度 // 将BIO内部缓冲区的数据都写出去, 成功: 1, 失败: 0或-1 int BIO_flush(BIO *b); - 在使用BIO_write()进行写操作的时候, 数据有时候在openssl提供的缓存中 - 将openssl提供的缓存中的数据刷到设备(内存/磁盘文件)中 // 把参数中名为append的BIO附加到名为b的BIO上,并返回b // 连接两个bio对象到链表中 // 在链表中的关系: b->append BIO* BIO_push(BIO *b, BIO *append); - b: 要插入到链表中的头结点 - append: 头结点的后继 // 把名为b的BIO从一个BIO链中移除并返回下一个BIO,如果没有下一个BIO,那么就返回NULL。 BIO* BIO_pop(BIO *b); typedef struct buf_mem_st BUF_MEM; struct buf_mem_st { size_t length; /* 内存中存储的数据的长度 */ char *data; // 指向存储数据的内存的地址 size_t max; /* size of buffer */ unsigned long flags; }; // 该函数也是一个宏定义函数,它将b底层的BUF_MEM结构放在指针pp中。 BUF_MEM* ptr; long BIO_get_mem_ptr(BIO *b, BUF_MEM **pp); // 释放整个bio链 void BIO_free_all(BIO *a);
我们举几个简单的例子说明BIO_push和BIO_pop的作用,假设md1、md2是digest类型的BIO,b64是Base64类型的BIO,而f是file类型的BIO,那么如果执行操作:
// 伪代码 // 创建不同的bio对象 BIO* md1 = bio_new(); // md1计算 BIO* md2 = bio_new(); // md2计算 BIO* bs64 = bio_new(); // base64的编码 BIO* file = bio_new(); // 写文件 // 数据首先进行md1计算, 然后进行md2计算, 然后进行base64编码, 最后将编码之后的数据写入到磁盘中 // 将这四个对象进行串联 -> 得到一个bio链 BIO_push(md1, md2); BIO_push(md2, bs64); BIO_push(bs64, file); // 得到了bio链 md1->md2->bs64->file // 将数据写入到bio链中 BIO_write(md1, buf, bufLen); // 释放bio链 BIO_free_all(md1);
那么就会形成md1-md2-b64-f的BIO链,大家可以看到,在构造完一个BIO后,头一个BIO就代表了整个BIO链,这根链表的概念几乎是一样的。
这时候,任何写往md1的数据都会经过md1,md2的摘要(或说hash运算),然后经过base64编码,最后写入文件f。可以看到,构造一条好的BIO链后,操作是非常方便的,你不用再关心具体的事情了,整个BIO链会自动将数据进行指定操作的系列处理。
需要注意的是,如果是读操作,那么数据会从相反的方向传递和处理,对于上面的BIO链,数据会从f文件读出,然后经过base64解码,然后经过md1,md2编码,最后读出。
// Base64 格式的对象 BIO *BIO_new(const BIO_METHOD *type); // 封装了base64编码方法的BIO,写的时候进行编码,读的时候解码 BIO_METHOD* BIO_f_base64(); // 如果得到这种类型的对象? BIO* bs64 = BIO_new(BIO_f_base64()); // Base64 格式的对象如何编码或解码 如果对当前的BIO进行 写操作 -> 数据编码 int BIO_write(BIO *b, const void *buf, int len); 如果对当前BIO进行 读操作 -> 数据解码 int BIO_read(BIO *b, void *buf, int len);
数据进行base64编码的过程
// 数据要进程base64编码-> 得到了新的字符串, 需要存到内存中(需要一个内存类型的BIO对象) // bs64 -> memory // Base64 格式的对象只能对象数据编码或解码 // 创建BIO对象 BIO *BIO_new(const BIO_METHOD *type); // 封装了base64编码方法的BIO,写的时候进行编码,读的时候解码 BIO_METHOD* BIO_f_base64(); // 封装了内存操作的BIO接口,包括了对内存的读写操作 BIO_METHOD* BIO_s_mem(); /====================================/ BIO* bs64 = BIO_new(BIO_f_base64()); BIO* mem = BIO_new(BIO_s_mem()); // 组织bio链 BIO_push(bs64, mem); // 往bio链中写数据 BIO_write(bs64, buf. strlen(buf)); // 刷新数据 BIO_flush(bs64); // 最终编码的数据在mem中 // 读bio链中最终存储到内存中的数据 // 从头结点开始搜索mem类型的节点并取出其中的数据 BUF_MEM* ptr; long BIO_get_mem_ptr(bs64, &ptr); string str(ptr->data, ptr->length);
数据进行base64解码的过程
// 需要将解码的base64字符串写到内存, 从内存中进行base64解码 // 需要两个bio, 一个是内存类型, 一个是base64类型 // base64->mem BIO* bs64 = BIO_new(BIO_f_base64()); BIO* mem = BIO_new(BIO_s_mem()); // 组织bio链 BIO_push(bs64, mem); // 将要解码的数据放到mem对象中 BIO_write(mem, buf, bufLen); // 读整个的bio链 // buf参数存储最终从bio链中读出的数据, len是参数buf数组的容量 int BIO_read(bs64, void *buf, int len);
4: Json, protobuf, base64
三种方式的应用场景不同
Json, protobuf对
数据块
进行序列化(编码)// 什么叫数据块 - 整形数 - 字符串 - 字符 - 结构体/类
- json处理轻量级数据
- protobuf可以处理重量级数据
- 大小通吃
base64
- 对二进制数据进行编码解码
- 处理的是一个字符串得到的还是一个字符串
- 因为涉及到数据传输数据的保存可以将避免直接对这些二进制数据进行操作
在数据进行 String 类型输出的时候
- 在二进制中转换为 字符输出时, 可能遇到:0000 0000 , 转换为 “/0”导致输出的数据直接结束。
- 我们可以使用十六进制, 将0000 0000 转换为了 数字 零, 避免了数据的输出直接中断。
- 同样也可以使用 Base64 解决同杨的问题。