Openssl数据安全传输平台011:base64的使用 - 代码测试 + 本项目中代码的实现

0 代码仓库

https://github.com/Chufeng-Jiang/OpenSSL_Secure_Data_Transmission_Platform/tree/main/Preparation

https://github.com/Chufeng-Jiang/OpenSSL_Secure_Data_Transmission_Platform/tree/main/ClientSecKey/ClientSecKey

1 base64

1.1 概念

Base64是一种基于64个可打印字符表示二进制数据的表示方法。在Base64中的可打印字符包括字母A-Za-z数字0-9,这样共有62个字符,加上**+/**, 共64个字符.

  • 普通的文本数据也可以使用base64进行编解码
  • Base64编解码的过程是可逆的
  • Base64不能当做加密算法来使用

1.2 应用场景

在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。 而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。

  • base64 应用

    • 它可用来作为电子邮件的传输编码。
      • 附件: 图片/音频/视频-> 二进制
      • 邮件传输协议只支持 ASCII字符传递,因此如果要传输二进制文件:图片、视频是无法实现的。
    • Http协议
      • HTTP协议要求请求行和请求头都必须是ASCII编码
    • 数据库数据读写 - blob
      • blob格式: - 文件、音频、视频、图片
      • blob格式的数据中有一些特殊的数据:
        • 中文
          • 写之前进行编码
          • 读出来之后解码
        • 二进制数据

2 base64 算法 (重要)

  • 把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24)
    • 假设有一个字符串, 需要对这个字符串分组, 每3个字节为一组, 分成N组
    • 将每一组的3个字节拆分, 拆成4个字节, 每个字节有6bit
  • 在6位的前面补两个0,形成8位一个字节的形式
    • 每个组就从3个字节变成了4个字节
    • 结论: base64编码之后的字符串变大了,
  • 如果剩下的字符不足3个字节,则用0填充,输出字符使用’=‘,因此编码后输出的文本末尾可能会出现1或2个’=', 表示补了多少字节,解码的时候,会自动去掉。
  • 数据通过base64编码, 编码之后的数据边长了还是变短了?
    - 变长了
    - 每三个字节一组, 编码之后每组增加一个字节

  • 编码之后的数据末尾有可能有=, 这个=是什么意思?
    - =代表0, 0是给最后一组补位用的, 最后一组缺几个字节就补几个0
    - 在解码的时候根据=的个数去掉对应的字节数

在这里插入图片描述Base64 将 8 位为一个单元的字节数据,拆分为 6 位为一个单元的二进制片段。每一个 6 位单元对应 Base64 索引表中的一个字符。简单举个例子,下图中 M 的 ASCII 码是 77 , 而转换为二进制后前六位二进制对应值为 19,为 Base64 字典中的 T。
在这里插入图片描述

3 openssl 中base64的使用

3.1 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,如果该BIO没有实现本函数则返回-2。
int BIO_read(BIO *b, void *buf, int len);
    - buf: 存储数据的缓冲区地址
    - len: buf的最大容量

// 往BIO中写入长度为len的数据。
// 成功返回真正写入的数据的长度,失败返回0或-1,如果该BIO没有实现本函数则返回-2。
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;              /* current number of bytes */
    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,那么如果执行操作,那么就会形成md1-md2-b64-f的BIO链,大家可以看到,在构造完一个BIO后,头一个BIO就代表了整个BIO链,这根链表的概念几乎是一样的

这时候,任何写往md1的数据都会经过md1,md2的摘要(或说hush运算),然后经过base64编码,最后写入文件f。可以看到,构造一条好的BIO链后,操作是非常方便的,你不用再关心具体的事情了,整个BIO链会自动将数据进行指定操作的系列处理。

需要注意的是,如果是读操作,那么数据会从相反的方向传递和处理,对于上面的BIO链,数据会从f文件读出,然后经过base64解码,然后经过md1,md2编码,最后读出。

BIO* md1 = BIO_new();    // md1 hash计算
BIO* md2 = BIO_new();    // md2 hash计算
BIO* b64 = BIO_new();    // base64 编解码
BIO* f = BIO_new();        // 操作磁盘文件

// 组织bio链
// b64->f
BIO_push(b64, f);
// 那么就会形成一个b64-f的链。然后再执行下面的操作:
// md2->b64->f
BIO_push(md2, b64);
// md1->md2->b64->f
BIO_push(md1, md2);

// bIO链
md1->md2->b64->f
// 写数据操作
int BIO_write(md1, "hello, world", 11);
// 数据的处理过程
// 进行md1 的哈希计算 -> md2的哈希计算 -> base64编码 -> 数据被写到磁盘

在这里插入图片描述

3.2 base64 编码 -> bio链的写操作

char* data = "hello, world";

// 创建base64编码的bio对象
BIO* b64 = BIO_new(BIO_f_base64());

// 最终在内存中得到一个base64编码之后的字符串
BIO* mem = BIO_new(BIO_s_mem());

// 将两个bio对象串联, 结构: b64->mem
BIO_push(b64, mem);

// 将要编码的数据写入到bio对象中
BIO_write(b64, data, strlen(data)+1);

// 将数据从bio对象对应的内存中取出 -> char*
BUF_MEM* ptr;

// 数据通过ptr指针传出
long BIO_get_mem_ptr(b64, &ptr);
char* buf = new char[ptr->length];
memcpy(buf, ptr->data, ptr->length);
printf("编码之后的数据: %s\n", buf);

3.3 base64 解码 -> bio链的读操作

// 要解码的数据
char* data = "xxxxxxxxxxxxxxxxxxxx";

// 创建base64解码的bio对象
BIO* b64 = BIO_new(BIO_f_base64());
#if 0
// 存储要解码的数据
BIO* mem = BIO_new(BIO_s_mem());
// 将数据写入到mem对应的内存中
BIO_write(mem, data, strlen(data));
#else
BIO *mem = BIO_new_mem_buf(data, strlen(data));
#endif

// 组织bio链
BIO_push(b64, mem);

// 读数据
char buf[1024];
int BIO_read(b64, buf, 1024);
printf("base64解码的数据: %s\n", buf);

上面代码有点问题,暂时还没找出来bug…

4 代码测试

4.1 配置VS

在这里插入图片描述在这里插入图片描述
附加依赖项输入:

libssl.lib;libcrypto.lib

在这里插入图片描述

4.2 测试代码

#include <iostream>
#include <string>
#include <stdlib.h>  
#include <openssl/rsa.h>
#include <openssl/pem.h>
using namespace std;


void WriteEncode()
{
	char  data[] = "hello, world";
	// 创建base64编码的bio对象
	BIO* b64 = BIO_new(BIO_f_base64());

	// 最终在内存中得到一个base64编码之后的字符串
	BIO* mem = BIO_new(BIO_s_mem());

	// 将两个bio对象串联, 结构: b64->mem
	BIO_push(b64, mem);

	// 将要编码的数据写入到bio对象中
	BIO_write(b64, &data, strlen(data) + 1);

	// 将数据从bio对象对应的内存中取出 -> char*
	BUF_MEM* ptr = NULL;

	// 数据通过ptr指针传出
	BIO_get_mem_ptr(b64, &ptr);
	char* buf = new char[ptr->length+1];
	memcpy(buf, ptr->data, ptr->length);

	printf("编码之后的数据: %s\n", buf);
	printf("==============================\n");

}

void ReadDecode()
{
	// 要解码的数据
	char  data[] = "Good evening, California";
	// 创建base64解码的bio对象
	BIO* b64 = BIO_new(BIO_f_base64());

#if 0
	// 存储要解码的数据
	BIO* mem = BIO_new(BIO_s_mem());
	// 将数据写到mem对应的内存中
	BIO_write(mem, data, strlen(data));
#else
	BIO* mem = BIO_new_mem_buf(&data, strlen(data) + 1);
#endif
	// 组织bio链
	BIO_push(b64, mem);
	// 读数据
	char buf[1024];
	BIO_read(b64, buf, 1024);
	printf("base64解码的数据:%s\n", buf);
}


// 编码
char* Base64Encode(char* input, int length)
{
	BIO* bmem = NULL;
	BIO* b64 = NULL;
	BUF_MEM* bptr = NULL;
	b64 = BIO_new(BIO_f_base64());

	bmem = BIO_new(BIO_s_mem());
	b64 = BIO_push(b64, bmem);
	BIO_write(b64, input, length);
	BIO_flush(b64);
	BIO_get_mem_ptr(b64, &bptr);
	char* buff = (char*)malloc(bptr->length + 1);
	memcpy(buff, bptr->data, bptr->length);
	buff[bptr->length] = 0;
	BIO_free_all(b64);
	return buff;
}

// 解码
char* Base64Decode(char* input, int length)
{
	BIO* b64 = NULL;
	BIO* bmem = NULL;
	char* buffer = (char*)malloc(length);
	memset(buffer, 0, length);
	b64 = BIO_new(BIO_f_base64());
	bmem = BIO_new_mem_buf(input, length);
	bmem = BIO_push(b64, bmem);
	BIO_read(bmem, buffer, length);
	BIO_free_all(bmem);
	return buffer;
}

int main()
{
	
	WriteEncode();
	ReadDecode();
	
	char data[] = "hello, world";
	char*  str = Base64Encode(data, strlen(data)+1);
	printf("\n带参数____编码后的字符串为:%s \n", str);
	return 0;
}

在这里插入图片描述

5 安全传输平台中base64的使用

在RsaCrypto.cpp中,对秘钥的加密、解密、签名、验证签名部分,均使用到了base64进行数据传输。
在这里插入图片描述

string RsaCrypto::toBase64(const char* str, int len)
{
	BIO* mem = BIO_new(BIO_s_mem());
	BIO* bs64 = BIO_new(BIO_f_base64());
	// mem添加到bs64中
	bs64 = BIO_push(bs64, mem);
	// 写数据
	BIO_write(bs64, str, len);
	BIO_flush(bs64);
	// 得到内存对象指针
	BUF_MEM *memPtr;
	BIO_get_mem_ptr(bs64, &memPtr);
	string retStr = string(memPtr->data, memPtr->length - 1);
	BIO_free_all(bs64);
	return retStr;
}

char* RsaCrypto::fromBase64(string str)
{
	int length = str.size();
	BIO* bs64 = BIO_new(BIO_f_base64());
	BIO* mem = BIO_new_mem_buf(str.data(), length);
	BIO_push(bs64, mem);
	char* buffer = new char[length];
	memset(buffer, 0, length);
	BIO_read(bs64, buffer, length);
	BIO_free_all(bs64);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大大枫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值