文章目录
- openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例
- 概述
- 笔记
- END
openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例
概述
BIO_push()形成了一个链.
理论上, 多个BIO连在一起, 只需要向BIO链头写数据, 然后BIO_flush(链头), 就可以从BIO链尾读出已经处理过的数据.
这样, 就不用逐个处理每个逻辑节点的数据输入输出, 简化了逻辑处理.
在openssl源码中找例子, 没找到. openssl中, 能看到的都是BIO_push(bio_head, bio_tail), 然后就向bio_head中写, BIO_flush(bio_head), 从bio_tail中读, 结束.
就想试试, 到底可不可以将大于2个的BIO用BIO_push()连在一起, 方便逻辑处理.
现在还没在openssl源码中找到例子, 那就自己试试.
前面做了实验, base64, aes128-cbc.
正好要将aes之后, base64的结果放到配置文件中的配置项的值中, 拿这个场景来试试.
整了2天, 将这个实验做完了.
结论:
BIO_push中的BIO数据输入输出方向可能随着操作(write, read), 语意发生变化.
并不能完全用BIO_push()来一并处理多个BIO节点.
对于我的这个实验, 加密时是可以用整个的BIO链, 如下
BIO_push(bio_cipher, bio_to_base64);BIO_push(bio_to_base64, bio_container);// 形成了一个BIO链 head => bio_cipher => bio_to_base64 => bio_container <= tail
加密时, 将BIO链push好之后, 从链头bio_cipher写, 然后 BIO_flush(bio_cipher).
再从链尾bio_container中读, 就可以得到先aes, 再base64的结果. 确实方便.
但是, 对于(aes + base64)的结果进行解密, 就不行了.
必须先base64, 再将结果进行aes解密.
原因:
使用base64编码时, 写的方向为 data_in =>bio_to_base64 => bio_container
使用base64解码时, 写的方向为 data_in =>bio_container => bio_to_base64
这就使BIO链处理的语意发生了变化, 编码/解密的操作不一致.
其实, 用openssl的最直白的方法, 就是将每个操作封装成函数, 要用哪个功能, 就调用对应封装好的函数.
用BIO_push()来连接多个BIO, 有点得不尝试(主要是openssl没有做到用BIO读写操作在语意上的一致性, 不是所有的BIO在语义上方向都一致). 形成BIO链后, 结果是否正确, 没办法保证. 调试和验证的时间远大于使用单独封装的函数.
本来想着用BIO_push()能简化逻辑操作, 实际上写出的实现也是一大坨.
在实际应用中, 如果是操作一大堆逻辑节点的操作, 可以试试, 是否可以用BIO链的方式实现读写, 如果能的话, 确实能简化逻辑操作.
如果要操作的逻辑节点就2~3个, 还是自己调用自己封装的单独函数, 分多步来干活, 这样逻辑清晰一些, 也不用做多余验证, 就可以保证结果是对的.
经过这次实验, 可以知道读写语义不一样的BIO对象包含base64.
如果逻辑节点中有base64, 那就确定不能用同一个BIO链来完成逻辑.
笔记
/*!
* \file prj-aes-128-cbc-base64.cpp
* openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例
* 用BIO链, 在一个函数中完成加密和baser64的组合动作
* 加密时, 先 aes-128-cbc enc 再 base64
* 解密时, 先 unbase64 再 aes-128-cbc dec
*/#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>#include <stdlib.h>
#include <stdio.h>
#include <assert.h>#include "CMemHookRec.h"#include <openssl/evp.h>
#include <openssl/bioerr.h>
#include <openssl/err.h>void my_openssl_app();
bool aes_128_cbc_base64(bool isEnc,IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);bool aes_128_cbc_base64_enc(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);bool aes_128_cbc_base64_dec(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);int main(int argc, char** argv)
{setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞mem_hook();my_openssl_app();mem_unhook();/*! run resultbefore enc:0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c !"#$%&'()*+,after enc:0000 - 78 71 45 37 4e 34 65 50-57 34 4a 76 54 34 46 69 xqE7N4ePW4JvT4Fi0010 - 6f 63 6a 59 65 54 58 5a-33 4e 75 43 6e 2b 77 7a ocjYeTXZ3NuCn+wz0020 - 55 75 65 2f 45 4c 68 4c-35 4b 55 7a 5a 31 4c 71 Uue/ELhL5KUzZ1Lq0030 - 36 6d 65 4f 54 51 36 33-5a 4d 78 6b 37 6f 32 2f 6meOTQ63ZMxk7o2/0040 - 0a .after dec:0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c !"#$%&'()*+,enc / dec all okfree map, g_mem_hook_map.size() = 0D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\prj-aes-128-cbc-base64\x64\Debug\prj-aes-128-cbc-base64.exe (进程 129580)已退出,代码为 0。要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。按任意键关闭此窗口. . .*/return 0;
}void my_openssl_app()
{UCHAR ucBuf[0x30 - 3];int lenBuf = sizeof(ucBuf);int i = 0;UCHAR* pEncBuf = NULL;int lenEncBuf = 0;UCHAR* pDecBuf = NULL;int lenDecBuf = 0;// 可以在EVP_CipherInit_ex()之后, 用EVP_CIPHER_CTX_get_key_length()/EVP_CIPHER_CTX_get_iv_length()看长度UCHAR key[0x10]; // aes-128-cbc's key len = 0x10UCHAR iv[0x10]; // aes-128-cbc's iv len = 0x10for (i = 0; i < 0x10; i++){key[i] = (UCHAR)i;iv[i] = (UCHAR)i;}for (i = 0; i < lenBuf; i++){ucBuf[i] = (UCHAR)i;}do {printf("before enc:\n");BIO_dump_fp(stdout, ucBuf, lenBuf);// enc// // 如果输入不是0x10对齐, 加密后, 就会自动0x10对齐(多出几个字节)// 所以要自己记录加密前的长度, 且加密时, 要将输出(加密后)buffer 16对齐(或直接比输入的长度多16字节)// 且加密后, 要自己记录加密后的长度if (!aes_128_cbc_base64(true, ucBuf, lenBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pEncBuf, lenEncBuf)){assert(false);break;}printf("after enc:\n");BIO_dump_fp(stdout, pEncBuf, lenEncBuf);// 加密后base64是对的...// decif (!aes_128_cbc_base64(false, pEncBuf, lenEncBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pDecBuf, lenDecBuf)){assert(false);break;}// 解密后的数据长度和解密前一样了printf("after dec:\n");BIO_dump_fp(stdout, pDecBuf, lenDecBuf);// 比较明文和解密后的明文是否相同if ((lenDecBuf != lenBuf) || (0 != memcmp(ucBuf, pDecBuf, lenBuf))){assert(false);break;}printf("enc / dec all ok\n");} while (false);if (NULL != pEncBuf){OPENSSL_free(pEncBuf);pEncBuf = NULL;}if (NULL != pDecBuf){OPENSSL_free(pDecBuf);pDecBuf = NULL;}
}bool aes_128_cbc_base64(bool isEnc,IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{if (isEnc){return aes_128_cbc_base64_enc(pszBufIn, lenBufIn, key, lenKey, iv, lenIv, pOutBuf, lenOutBuf);}else {return aes_128_cbc_base64_dec(pszBufIn, lenBufIn, key, lenKey, iv, lenIv, pOutBuf, lenOutBuf);}
}bool aes_128_cbc_base64_enc(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{bool b_rc = false;int i_rc = 0;int len = 0;size_t sz_rc = 0;const EVP_CIPHER* _evp_cipher = NULL;EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;BIO* bio_container = NULL;BIO* bio_cipher = NULL;BIO* bio_to_base64 = NULL;BIO* bio_header = NULL;BIO* bio_tail = NULL;BIO* bio_write_to = NULL;BIO* bio_read_from = NULL;// data(len = 0x2d) = > aes - 128 - cbc = > data_enc(len = 0x30)// data(len = 0x30) = > base64 = > data_base64(len = 0x41)do {lenOutBuf = 0;if ((NULL == pszBufIn) || (lenBufIn <= 0) ||(NULL == key) || (lenKey <= 0) ||(NULL == iv) || (lenIv <= 0)){break;}_evp_cipher = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串// 可以从算法对象得到算法名称//psz = EVP_CIPHER_get0_name(c);//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);// EVP_aes_128_cbc()'s alg name is : AES-128-CBCi_rc = EVP_CIPHER_get_key_length(_evp_cipher);if (i_rc != lenKey){break;}i_rc = EVP_CIPHER_get_iv_length(_evp_cipher);if (i_rc != lenIv){break;}bio_cipher = BIO_new(BIO_f_cipher());// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher// 此时 _evp_cipher_ctx 是 NULL// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对BIO_get_cipher_ctx(bio_cipher, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址if (NULL == _evp_cipher_ctx){break;}// 向ctx中单独设置加密算法/key/iv// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)if (!EVP_CipherInit_ex(_evp_cipher_ctx, _evp_cipher, NULL, key, iv, 1)) {// ERR_print_errors(bio_err);ERR_print_errors_fp(stderr);break;}bio_container = BIO_new(BIO_s_mem());if (NULL == bio_container){break;}bio_to_base64 = BIO_new(BIO_f_base64());if (NULL == bio_to_base64){break;}// BIO_push返回的就是参数1 bio_out// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错// !!! 对于aes加解密, 都是从bio_header写, 从 bio_tail读// 算法类的BIO是没有办法存数据的, 所以后面要有一个能存数据的BIO(file bio or mem bio)作为容器容纳数据// 加密时 bio_cipher => bio_to_base64 => bio_containerBIO_push(bio_cipher, bio_to_base64);BIO_push(bio_to_base64, bio_container);bio_header = bio_cipher;bio_tail = bio_container;bio_write_to = bio_header;bio_read_from = bio_tail;// 必须向链条的顶部写(write to bio_filter)// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了i_rc = BIO_write_ex(bio_write_to, pszBufIn, lenBufIn, &sz_rc); // 相当于 EVP_CipherUpdate()if ((1 != i_rc) && (lenBufIn != sz_rc)){break;}BIO_flush(bio_write_to);len = BIO_pending(bio_read_from);// 加密时, 在这里读到的数据长度是加密后的长度pOutBuf = (UCHAR*)OPENSSL_malloc(len + 1); // 多留一个字节(可选)if (NULL == pOutBuf){break;}pOutBuf[len] = '\0';lenOutBuf = 0;i_rc = BIO_read_ex(bio_read_from, pOutBuf, len, &sz_rc);// 这里读最后一块的时候// 加密时, 已经是0x10对齐了if ((1 != i_rc) || (sz_rc <= 0)){ERR_print_errors_fp(stderr);break;}lenOutBuf = sz_rc;b_rc = true;} while (false);if (NULL != bio_header){BIO_free_all(bio_header); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header = NULL;}// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filterif (!b_rc){if (NULL != pOutBuf){OPENSSL_free(pOutBuf);pOutBuf = NULL;}}return b_rc;
}bool aes_128_cbc_base64_dec(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{bool b_rc = false;int i_rc = 0;int len = 0;size_t sz_rc = 0;UCHAR* pucBufBase64Out = NULL;const EVP_CIPHER* _evp_cipher = NULL;EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;BIO* bio_cipher_aes = NULL;BIO* bio_cipher_base64 = NULL;// for unbase64BIO* bio_container = NULL;BIO* bio_header = NULL;BIO* bio_tail = NULL;BIO* bio_write_to = NULL;BIO* bio_read_from = NULL;// for unaesBIO* bio_container_1 = NULL;BIO* bio_header_1 = NULL;BIO* bio_tail_1 = NULL;BIO* bio_write_to_1 = NULL;BIO* bio_read_from_1 = NULL;// data(len = 0x2d) = > aes - 128 - cbc = > data_enc(len = 0x30)// data(len = 0x30) = > base64 = > data_base64(len = 0x41)do {lenOutBuf = 0;if ((NULL == pszBufIn) || (lenBufIn <= 0) ||(NULL == key) || (lenKey <= 0) ||(NULL == iv) || (lenIv <= 0)){break;}_evp_cipher = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串// 可以从算法对象得到算法名称//psz = EVP_CIPHER_get0_name(c);//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);// EVP_aes_128_cbc()'s alg name is : AES-128-CBCi_rc = EVP_CIPHER_get_key_length(_evp_cipher);if (i_rc != lenKey){break;}i_rc = EVP_CIPHER_get_iv_length(_evp_cipher);if (i_rc != lenIv){break;}bio_cipher_aes = BIO_new(BIO_f_cipher());// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher// 此时 _evp_cipher_ctx 是 NULL// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对BIO_get_cipher_ctx(bio_cipher_aes, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址if (NULL == _evp_cipher_ctx){break;}// 向ctx中单独设置加密算法/key/iv// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)if (!EVP_CipherInit_ex(_evp_cipher_ctx, _evp_cipher, NULL, key, iv, 0)) {// ERR_print_errors(bio_err);ERR_print_errors_fp(stderr);break;}bio_container = BIO_new(BIO_s_mem());if (NULL == bio_container){break;}bio_cipher_base64 = BIO_new(BIO_f_base64());if (NULL == bio_cipher_base64){break;}// base64的BIO的数据读写方向和aes的BIO读写方向不一样, 不能用BIO_push压入一个BIO链// 为了能正确解密, 这里要分为2步(先unbase64, 再un aes)才行// // ---------- unbase64 ----------//// BIO_push返回的就是参数1 bio_out// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错// !!! 对于aes加解密, 都是从bio_header写, 从 bio_tail读// 算法类的BIO是没有办法存数据的, 所以后面要有一个能存数据的BIO(file bio or mem bio)作为容器容纳数据// 解密时 bio_container => bio_to_base64 => bio_cipher// 解base64时, BIO_push左边是bio_base64, 右边是bio_container, 且是从右向左写, 才能正确解开base64BIO_push(bio_cipher_base64, bio_container); // <<=bio_header = bio_cipher_base64;bio_tail = bio_container;bio_write_to = bio_tail;bio_read_from = bio_header;// 必须向链条的顶部写(write to bio_filter)// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了i_rc = BIO_write_ex(bio_write_to, pszBufIn, lenBufIn, &sz_rc); // 相当于 EVP_CipherUpdate()if ((1 != i_rc) && (lenBufIn != sz_rc)){break;}BIO_flush(bio_write_to);len = BIO_pending(bio_read_from);if (len <= 0){break;}pucBufBase64Out = (UCHAR*)OPENSSL_malloc(len + 1);if (NULL == pucBufBase64Out){break;}pucBufBase64Out[len] = '\0';// ---------- unAES ----------bio_container_1 = BIO_new(BIO_s_mem());if (NULL == bio_container_1){break;}BIO_push(bio_cipher_aes, bio_container_1); // =>bio_header_1 = bio_cipher_aes;bio_tail_1 = bio_container_1;bio_write_to_1 = bio_header_1;bio_read_from_1 = bio_tail_1;i_rc = BIO_read_ex(bio_read_from, pucBufBase64Out, len, &sz_rc);// 这里读最后一块的时候// 解密时, 已经是实际的size了if ((1 != i_rc) || (sz_rc <= 0)){break;}i_rc = BIO_write_ex(bio_write_to_1, pucBufBase64Out, sz_rc, &sz_rc);if (1 != i_rc){break;}BIO_flush(bio_write_to_1);len = BIO_pending(bio_read_from_1);if (len <= 0){break;}// 解密时, 在这里读到的数据长度还是密文长度. 不过可以拿这个len开buffer, 因为明文比密文短// bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);// 加密时, 如果明文长度不是0x10对齐, 那么加密后的长度可能比明文长最多0x10个字节pOutBuf = (UCHAR*)OPENSSL_malloc(len + 1); // 多留一个字节(可选)if (NULL == pOutBuf){break;}pOutBuf[len] = '\0';lenOutBuf = 0;// read unAES to out bufferi_rc = BIO_read_ex(bio_read_from_1, pOutBuf, len, &sz_rc);if ((1 != i_rc) || (len != sz_rc)){break;}lenOutBuf = sz_rc;b_rc = true;} while (false);if (NULL != pucBufBase64Out){OPENSSL_free(pucBufBase64Out);pucBufBase64Out = NULL;}if (NULL != bio_header){BIO_free_all(bio_header); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header = NULL;}if (NULL != bio_header_1){BIO_free_all(bio_header_1); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header_1 = NULL;}// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filterif (!b_rc){if (NULL != pOutBuf){OPENSSL_free(pOutBuf);pOutBuf = NULL;}}return b_rc;
}