• 【OpenSSL】OpenSSL实现Base64


    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 
    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;
    }
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    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 
    #include 
    #include 
    #include 
    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;
    }
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    Open SSLBase64编码换行

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

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

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

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

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

    
    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;
    }
    
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55

    执行结果,如下,编码的内容已经不会再换行了。
    在这里插入图片描述
    从上可以看出, 如果没有了末尾的换行符号。解码的时候又出现无法解码的问题, 因为解码是按照末尾的\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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    
    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;
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    执行的结果都可以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
  • 相关阅读:
    Docker-06:仓库
    MySQL索引和优化
    Java判断输入ip是否合法的工具类,拿上就可以使用
    SAP B1 Web Client & MS Teams App集成连载一:先决条件/Prerequisites
    【CSS】选择器优先级,值与单位
    FAT32文件系统---第0章 课程准备
    【网络安全】黑客自学笔记
    【动态规划之完全背包问题】如何将完全背包运用到实际问题,强化完全背包以及一维优化的推导
    C++ 运算符学习资料
    AIoT通用组件服务攻略之设备“收纳”好帮手——分组管理
  • 原文地址:https://blog.csdn.net/maoye/article/details/132994754