【OpenSSL】OpenSSL实现Base64

news2024/12/27 12:35:29

Base 64概述和应用场景

概述

Base64就是将二进制数据转换为字符串的一种算法。

应用场景

  • 邮件编码
  • xml或则json存储二进制内容
  • 网页传递数据URL
  • 数据库中以文本形式存放二进制数据
  • 可打印的比特币钱包地址base58Check(hash校验)
  • 网页上可以将图片直接使用Base64表达
  • 公私密钥的文本文件

Base16(16进制)

Base164位, 一个Unicode字符编码需要8位 ,那就需要将一个字符分解成2部分。编码字节的值,对应Base64的值如下对照表:

字节值Base64编码
00
11
22
33
44
55
66
77
88
99
10A
11B
12C
13D
14E
15F

从零开始实现Base16编解码

代码如下:

#include <iostream>
using namespace std;

static const char BASE16_ENC_TAB[] = "0123456789ABCDEF";
// '0'~'9'  => 48~57, 'A'~'E' => 65~70

static const char BASE16_DEC_TAB[128] =
{
	-1, // 0
	-1, -1, -1, -1, -1,-1, -1, -1, -1, -1, // 1-10
	-1, -1, -1, -1, -1,-1, -1, -1, -1, -1, // 11-20
	-1, -1, -1, -1, -1,-1, -1, -1, -1, -1, // 21-30
	-1, -1, -1, -1, -1,-1, -1, -1, -1, -1, // 31-40
	-1, -1, -1, -1, -1,-1, -1, 0, 1, 2, // 41-50
	3, 4, 5, 6, 7, 8, 9, -1, -1, -1, // 51-60
	-1, -1, -1, -1, 10, 11, 12, 13, 14, 15, // 61-70 'A'~'F'
};

int Base16Encode(const unsigned char* in, int size, char* out)
{
	for (int i = 0; i < size; i++) 
	{
		//1 一个字节取出高4位和低4位
		char h = (in[i] >> 4) ;  // 以为
		char l = (in[i] & 0x0f); // 0000 1111 //去掉高4位
		// 0~15映射到对应的字符
		out[i * 2] = BASE16_ENC_TAB[h];
		out[i * 2 + 1] = BASE16_ENC_TAB[l];
		
	}
	// base 16转码后空间扩大一倍 4位转成一个字符 1字节转成两个字符
	return size * 2;
}

/**
* 将Base16字符转换成常规字符串
*/
int Base16Decode(const string &in, unsigned char* out) 
{
	// 将两个字符拼成一个字节
	for (int i = 0; i < in.size(); i+=2) 
	{
		unsigned char ch = in[i]; //高位转换的字符
		unsigned char lh = in[i + 1]; // 低位转换的字符
		// 上面拿到的还是个字符, 要转换成原始的数据
		unsigned char h = BASE16_DEC_TAB[ch];
		unsigned char l = BASE16_DEC_TAB[lh];

		//out[i/2] = (h <<4) + l;
		out[i / 2] = h << 4 | l;
	}
	return in.size() / 2;
}

int main()
{
	cout << "Test Base16" << endl;

	const unsigned char data[] = "测试Base16";
	int len = sizeof(data);
	char out1[1024] = { 0 };
	int res = Base16Encode(data, len, out1);
	cout << res << ":" << out1 << endl;

	string code(out1);
	unsigned char out2[1024] = { 0 };
	res = Base16Decode(code, out2);
	cout << res << ":" << out2 << endl;
	return 0;
}

Base64(64进制)

首先查看Base64的值码对应表

字节值Base64编码字节值Base64编码字节值Base64编码字节值Base64编码
0A16Q32g48w
1B17R33h49x
2C18S34i50y
3D19T35j51z
4E20U36k520
5F21V37l531
6G22W38m542
7H23X39n553
8I24Y40o564
9J25Z41p575
10K26a42q586
11L27b43t597
12M28c44s608
13N29d45t619
14O30e46u62+
15P31f47v63/

Base64编码要求把3个8位字节(3*8=24), 之后在6位的前面补两个0, 形成8位一个字节的形式。如果剩下的字符不足3个字节, 则用0填充,输出字符使用=,因此编码后输出的文本末尾可能会出现1或2个=

Open SSL BIO接口

  • BIO包含了多种接口,用于控制在BIO_METHOD中不同实现函数, 包括6种filter型和8种source/sink型应用场景。
  • BIO_new创建一个BIO对象.
  • 数据源:source/sink类型的BIO是数据源BIO_new(BIO_s_mem()),生存内存是数据源对象
  • 过滤:filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口 BIO_new(BIO_f_base64())
  • BIO链:一个BIO链通常包括一个source BIO和一个或多个filter BIO BIO_push(b64_bio, mem_bio);
  • 写编码, 读解码 BIO_write BIO_read_ex

Open SSL BIO实现Base64编解码

#include <iostream>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
using namespace std;

int Base64Encode(const unsigned char* in, int len, char* out_base64)
{
	if (!in || len < 0 || !out_base64) 
	{
		return 0;
	}

	//创建内存源
	auto mem_bio = BIO_new(BIO_s_mem());
	if (!mem_bio) return 0;
	// base64 filter
	auto b64_bio = BIO_new(BIO_f_base64());//这个接口在头文件 evp.h

	if (!b64_bio) 
	{
		BIO_free(mem_bio); //释放申请成功的空间
		return 0;
	}

	// 形成BIO链表
	//b64-mem
	// 形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果
	BIO_push(b64_bio, mem_bio);  // 2个链表(从链表头部,代表整个链表)
	//write 是编码 3字节 => 4字节 不足3字节补充0 和等于号
	int re = BIO_write(b64_bio, in, len); //将数据写入到链表头
	if (re < 0) 
	{
		// 写入失败, 清空整个链表节点
		BIO_free_all(b64_bio);
		return 0;
	}
	// 刷新缓存, 写入链表的mem
	BIO_flush(b64_bio);

	// 从链表源内存读取
	BUF_MEM* p_data = nullptr; // 需要引入
	BIO_get_mem_ptr(b64_bio, &p_data); //拿到编码数据了
	int out_size = 0;
	if (p_data) 
	{
		memcpy(out_base64, p_data->data, p_data->length);
		out_size = p_data->length;
	}
	//执行完后, 清理空间
	BIO_free_all(b64_bio);
	return out_size;
}

int Base64Decode(const char* in, int len, unsigned char* out_data) 
{
	if (!in || len <= 0 || !out_data)
	{
		return 0;
	}

	// 内存源
	auto mem_bio = BIO_new_mem_buf(in, len);
	if (!mem_bio) 
	{
		return 0;
	}
	// base64 过滤器
	auto b64_bio = BIO_new(BIO_f_base64());
	if (!b64_bio) 
	{
		BIO_free(mem_bio);
		return 0;
	}

	//形成BIO链条
	BIO_push(b64_bio, mem_bio);
	//读取 解码 4字节转3字节
	size_t size = 0;
	BIO_read_ex(b64_bio, out_data, len, &size);
	BIO_free_all(b64_bio);
	return size;
}

int main(int argc, char argv[])
{
	cout << "Test openssl BIO base64" << endl;
	unsigned char data[] = "测试Base64数据";
	int len = sizeof(data);
	char out[1024];
	int ret = Base64Encode(data, len, out);
	if (ret) 
	{
		out[ret] = '\0';
	}
	cout << "base64:" << out << endl;

	
	unsigned char out_data[1024] = { 0 };
	//ret = Base64Decode(out, sizeof(out), out_data);// 这里不能用sizeof()  , 用计算字符长度
	ret = Base64Decode(out, strlen(out), out_data);
	cout << "encode :" << out_data << endl;
}

Open SSLBase64编码换行

Open SSLBase64在做编码操作的时候,默认情况下遇到64字节(不同平台不确定,)的时候就会进行换行操作。
例如将上面的输入内容改得过长.

unsigned char data[] = "测试Base64数据形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果";

执行的编码结果就会出现换行操作, 如下图所示:
在这里插入图片描述
只需要在写入待编码码内容前进行参数设置,就可以使其不换行

	//超过长度不还行
	BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); 

编码方法的整体代码如下:


int Base64Encode(const unsigned char* in, int len, char* out_base64)
{
	if (!in || len < 0 || !out_base64) 
	{
		return 0;
	}

	//创建内存源
	auto mem_bio = BIO_new(BIO_s_mem());
	if (!mem_bio) return 0;
	// base64 filter
	auto b64_bio = BIO_new(BIO_f_base64());//这个接口在头文件 evp.h

	if (!b64_bio) 
	{
		BIO_free(mem_bio); //释放申请成功的空间
		return 0;
	}

	// 形成BIO链表
	//b64-mem
	// 形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果
	BIO_push(b64_bio, mem_bio);  // 2个链表(从链表头部,代表整个链表)

	//超过长度不还行
	BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); 

	//write 是编码 3字节 => 4字节 不足3字节补充0 和等于号
	//编码数据每64直接会加一个\n换行符号,并且结尾时也有换行符号

	int re = BIO_write(b64_bio, in, len); //将数据写入到链表头
	if (re < 0) 
	{
		// 写入失败, 清空整个链表节点
		BIO_free_all(b64_bio);
		return 0;
	}
	// 刷新缓存, 写入链表的mem
	BIO_flush(b64_bio);

	// 从链表源内存读取
	BUF_MEM* p_data = nullptr; // 需要引入
	BIO_get_mem_ptr(b64_bio, &p_data); //拿到编码数据了
	int out_size = 0;
	if (p_data) 
	{
		memcpy(out_base64, p_data->data, p_data->length);
		out_size = p_data->length;
	}
	//执行完后, 清理空间
	BIO_free_all(b64_bio);
	return out_size;
}

执行结果,如下,编码的内容已经不会再换行了。
在这里插入图片描述
从上可以看出, 如果没有了末尾的换行符号。解码的时候又出现无法解码的问题, 因为解码是按照末尾的\n来执行结尾的。因为如下,如果我再编码内容的后面加上\n即可正确的进行解码操作了.

int main(int argc, char argv[])
{
	cout << "Test openssl BIO base64" << endl;
	unsigned char data[] = "测试Base64数据形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果形成链表, 往base64内存写数据, 进行编码,结果会传递到链表的下一个节点, 到mem中读取结果";
	int len = sizeof(data);
	char out[1024];
	int ret = Base64Encode(data, len, out);
	if (ret) 
	{
		out[ret] = '\0';
	}
	cout << "base64:" << out << endl;
	// 手动加下行结尾的符号
	out[ret] = '\n';
	out[ret+1] = '\0';
	unsigned char out_data[1024] = { 0 };
	//ret = Base64Decode(out, sizeof(out), out_data);// 这里不能用sizeof()  , 用计算字符长度
	ret = Base64Decode(out, strlen(out), out_data);
	cout << "encode :" << out_data << endl;
}

另外一种解决办法就是直接也在解码代码中也加上对换行符号的忽略.


int Base64Decode(const char* in, int len, unsigned char* out_data) 
{
	if (!in || len <= 0 || !out_data)
	{
		return 0;
	}

	// 内存源
	auto mem_bio = BIO_new_mem_buf(in, len);
	if (!mem_bio) 
	{
		return 0;
	}
	// base64 过滤器
	auto b64_bio = BIO_new(BIO_f_base64());
	if (!b64_bio) 
	{
		BIO_free(mem_bio);
		return 0;
	}

	//形成BIO链条
	BIO_push(b64_bio, mem_bio);
	//取消默认读取换行符号做结束的操作
	BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL);
	//读取 解码 4字节转3字节
	size_t size = 0;
	BIO_read_ex(b64_bio, out_data, len, &size);
	BIO_free_all(b64_bio);
	return size;
}

执行的结果都可以OK了。
在这里插入图片描述
综上问题, 编解码必须要一致。

Base58

由于Base64的方式存在一些问题,比如+,/在一些传输内容时容易出现特殊字符问题,还有0/O/o, l(小写母L)I(大写字母i)在某些情况下肉眼不易区分。Base58去掉了0(数字0)O(大写字母o)l(小写字母L)I(大写字母i),以及两个特殊字符+,/.一共去掉了6个字符,就剩下了58个字符。

字节值Base58编码字节值Base58编码字节值Base58编码字节值Base58编码
0116H32Z48q
1217J33a49r
2318K34b50s
3419L35c51t
4520M36d52u
5621N37e53v
6722P38f54w
7823Q39g55x
8924R40h56y
9A25S41i57z
10B26T42j
11C27U43k
12D28V44m
13E29W45n
14F30X46o
15G31Y47p

辗转相除法

  • 两个数的最大公约数等于它们中较小的数和两数只差的最大公约数
  • 欧几里德算法,是求最大公约数的算法
  • 两个数的最大公约数是指同时整除它们的最大正整数。辗转相除法的基本原理是两个数的最大公约数等于它们中较小的数和两数只差的最大公约数。
  • 如果要将1234转换成58进制;
  1. 1234 除以 58 ,商21, 余数为16,查表得H
  2. 用21除以58, 商0, 余数为21, 查表得N
  3. 如果待转的数前面又0直接附加编码1来代表,有多少个就附加多少个。

Base56输出字节数

  • 在编码后字符串中, 是从58个字符中选择,需要表示的位数是 l o g 2 58 log_{2}58 log258, 每一个字母代表的信息是 l o g 2 58 log_{2}58 log258.
  • 输入的字节: (length *8)位。
  • 预留的字符数量就是 ( l e n g t h ∗ 8 ) / l o g 2 58 (length*8)/log_{2}58 length8)/log258

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1040924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 用列表实现模拟手机通讯录(简易版)

"""列表实现好友管理系统知识点&#xff1a;1、列表存储信息2、列表增删改查3、嵌套循环4、字符串分割和拼接&#xff08;重点&#xff09;5、列表索引"""# 暂存好友信息&#xff08;程序结束数据删除&#xff09; friend_info list()input_buf…

opencv形态学-膨胀

opencv形态学-膨胀 膨胀就是取每一个位置结构元领域内最大值作为该位置的输出灰度值&#xff1b; 膨胀是取邻域内最大值&#xff0c;那么显然膨胀后图像整体亮度会比原先要高&#xff0c;图像中亮的物体尺寸会变大&#xff0c;相反暗的尺寸会减小&#xff0c;甚至是消失 结构元…

流程图在线制作:5款专业流程图制作网站,无需下载,高效立现!

流程图&#xff0c;是特定的图形符号加上说明&#xff0c;表示算法的图&#xff0c;是一种可视化工具。近年来流程图逐渐在工作、教育、项目管理等诸多领域大放异彩&#xff0c;市面上也流行着许多流程图制作软件&#xff0c;考虑到许多软件下载流程繁琐、并且还有下载盗版的风…

pytorch学习笔记——BCE与CE

BCELoss的话只需要网络输出一个通道&#xff0c;CE Loss(Cross Entropy Loss)需要输出n_class个通道。 对于二分类任务可以使用CE Loss输出两个通道&#xff0c;也可以使用BCE Loss输出一个通道。 https://www.jianshu.com/p/5b01705368bb https://zhuanlan.zhihu.com/p/372628…

客户端负载均衡_负载均衡策略

以前的Ribbon有多种负载均衡策略 RandomRule - 随性而为 解释&#xff1a; 随机 RoundRobinRule - 按部就班 解释&#xff1a; 轮询 RetryRule - 卷土重来 解释&#xff1a; 先按照RoundRobinRule的策略获取服务&#xff0c;如果获取服务失败则在指定时间内会进行重试。 Weigh…

Learn Prompt- Midjourney案例:网页设计

快速开始​ 用 “ web design for...” 或 “ modern web design for..” 来快速开始你的提示。 web design for a generic SaaS startup --ar 3:2否定提示-no​ 使用--no告诉 Midjourney 你不想要什么。Midjourney 的默认风格倾向于现实和详细。但这可能不适用于所有品牌。…

HEC-RAS 1D/2D水动力与水环境模拟从小白到精通

专题一 水动力模型基础 1.水动力模型的本质 2.水动力模型的基本方程与适用范围 3.模型建模要点 4.注意事项与建模经验 专题二 恒定流模型(1D/2D) 1.恒定流及其适用范围 2.水面线分析及其数据要求 3.曼宁公式与恒定流&#xff0c;后处理 4.HEC-RA的水工建筑物&#xff…

ADC数模转化器

简介 • ADC &#xff08; Analog-Digital Converter &#xff09;模拟 - 数字转换器 • ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 • 12 位逐次逼近型 ADC &#xff0c; 1us 转换时间 &#xff08;12位:分辨率…

vue里使用elementui的级联选择器el-cascader进行懒加载的怎么实现数据回显?

需要实现的懒加载回显效果 比如&#xff1a;后端返回数据 广东省/广州市/天河区 &#xff1a;440000000000/440100000000/440106000000&#xff0c;需要我们自动展开到天河区的下一级&#xff0c;效果如下 代码实现 我的实现思路就是拿到 440000000000/440100000000/44010600…

YOLOv5如何训练自己的数据集(生活垃圾数据集为例)

文章目录 前言1、数据标注说明2、定义自己模型文件3、训练模型参考文献 前言 本文主要介绍如何利用YOLOv5训练自己的数据集 1、数据标注说明 以生活垃圾数据集为例子 生活垃圾数据集&#xff08;YOLO版&#xff09;点击这里直接下载本文生活垃圾数据集 生活垃圾数据集组成&…

软件测试/测试开发丨利用人工智能ChatGPT自动生成PPT

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…

VSCode 和 CLion

文章目录 一、VSCode1、文档2、插件3、智能编写4、VSCode 与 C&#xff08;1&#xff09;安装&#xff08;2&#xff09;调试&#xff08;a&#xff09;使用 CMake 进行跨平台编译与调试&#xff08;b&#xff09;launch.json&#xff08;c&#xff09;传参 &#xff08;3&…

mac有必要用清理软件吗

随着科技的不断进步&#xff0c;我们的计算机硬盘容量也在不断增长。即使是ARM架构的处理器&#xff0c;也可以通过高效的文件系统和技术来充分利用磁盘空间。然而&#xff0c;对于使用Mac OS系统的用户来说&#xff0c;仅仅使用一个盘来存储所有文件可能会导致一些残留文件的问…

深入了解 Docker 容器操作命令:掌握容器化管理的关键

Docker 已经成为现代应用程序开发和部署的行业标准。它借助容器化技术&#xff0c;提供了一种轻量、可移植和可扩展的方式来构建、发布和运行应用程序。然而&#xff0c;最近我在工作中发现&#xff0c;一些家人们对 Docker 容器的操作命令还不太熟悉。因此&#xff0c;本文旨在…

C++之std::function类模板定义函数对象应用总结(二百三十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

PWN基础:从源文件到可执行文件

目录 编译原理 GCC编译过程 Preprocess阶段 File命令 Compile阶段 Assemble阶段 Link阶段 高级语言编写的程序想在操作系统运行&#xff0c;需要被翻译为机器指令&#xff0c;在按照可执行目标文件格式打包并以二进制形式存储在文件中 编译原理 编译器作用&#xff1a;…

php实现分页功能跳转和ajax方式实现

实现效果 准备工作 创建数据表和导入测试数据 CREATE TABLE users ( id int(10) unsigned NOT NULL AUTO_INCREMENT, username varchar(30) DEFAULT NULL COMMENT 账号, email varchar(30) DEFAULT NULL COMMENT 密码, PRIMARY KEY (id) ) ENGINEMyISAM AUTO_INCREM…

Docker 容器编排

是什么 Docker-Compose是 Docker 官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Compose 是 Docker 公司推出的一个工具软件&#xff0c;可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml&#xff0c;写好多个…

基于微信小程序的医院门诊体检预约管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

Xcode15下载iOS17一直中断解决办法

1、问题描述 目前的 xcode 15 安装时&#xff0c;跟以前有个差别&#xff1a;以往的 xcode 安装时自带了 ide、sdk 等工具包&#xff0c;安装后即可开始开发&#xff0c;而最新的包则被分开成了不同的包&#xff0c;这里以 ios 开发包为例&#xff1a;Xcode_15.xip 和 iOS_17_…