当前位置: 首页 > news >正文

openssl中BIO的使用

BIO是OpenSSL的一个重要的结构和概念,是对数据IO与传递处理的一种类型抽象和功能封装,这里所说的数据IO与传递的“介质”包括:内存、文件、日志、标准设备、网络socket等,“处理”包括简单、加/解密、摘要、ssl协议下的读/写等的数据变换。

本文主要介绍了BIO的结构和使用方法,并以示例方式给出了一种对BIO的封装,可为下一步与socket的相关操作结合,编写自定义的“加密/解密流”。

本文示例适用于openssl3.0+。

1.概念

BIO是针对数据传递而设计的逻辑结构,因此它基本的概念模型是“管道”:数据由BIO一端进入,另一端流出,中间也可能进行数据变换。这样的“管道”结构可以依据是否有“缓存”,出入口等条件进行划分。

OpenSSL中将BIO分为两种类型:

  • Filter BIO:就是纯管道型BIO,数据不能保存在其中。
  • source/sink BIO:自带有容器的BIO,数据进行缓存,如果进一步细分,source BIO就是只出不进BIO,sink BIO就是只进不出BIO。

两种类型的BIO的概念示意图如上图所示。可以想象,如果将BIO首尾连接起来,就会构成BIO链,也如上图所示。BIO链在使用上,虽然仅对首尾BIO进行了读写操作,但是这种操作是会依次传递给下一个BIO的,因而BIO链逻辑上看作是一个复杂的BIO。

2.类型

OpenSSL中已经预定义了若干种BIO,直接可以使用它们。

主要Source/Sink类型BIO

  •  BIO_s_file/ BIO_s_fd:文件BIO,BIO_s_file对应FILE*,而BIO_s_fd对应POSIX文件描述符,它们可用于写入和读取文件。
  • BIO_s_socket:网络socketBIO,用于通过网络进行通信。
  • BIO_s_null: 空BIO,类似/dev/null,只能写入,读取数据会导致EOF。
  • BIO_s_mem:内存BIO,用于写入和读取内存。
  • BIO_s_bio:一种特别的BIO,被称为BIO pair,后文单独说明。

主要Filter类型BIO

  • BIO_f_base64:base64 BIO,通过此BIO的BIO_write将数据编码为base64格式, BIO_read通过此BIO解码base64格式的数据。
  • BIO_f_cipher:密码BIO,通过它的数据会被加/解密,密码算法可以设置。
  • BIO_f_md:摘要计算BIO,它不会修改通过它的数据,而仅计算流经其中的数据摘要,摘要算法可以设置,使用特殊功能检索计算出的摘要。
  • BIO_f_buffer:缓冲BIO,它也不会更改通过它的数据。写入此BIO的数据被缓冲,因此并非每次对该BIO的写入操作都会导致将数据写入下一个BIO。至于阅读,情况类似。这样可以减少位于缓冲IO后面的BIO上的IO操作数。
  • BIO_f_ssl :SSL/TLS 协议BIO,通过它的数据会按照协议规则进行加解密
  • 3.基本使用函数

创建/释放函数为:

BIO *BIO_new(const BIO_METHOD *)
BIO_free_all(BIO *)

设置/控制基本函数为:

BIO_ctrl(BIO *,int,long ,void *)

以此函数为基础,定义了一些方便使用的宏:BIO_reset,BIO_tell,BIO_eof,BIO_flush等
读写操作的基本函数为:

int BIO_read_ex(BIO *, void *, size_t, size_t *)
int BIO_write_ex(BIO *, const void *, size_t, size_t *)

对于BIO链,bio_st结构中有变量next_bio,prev_bio,可以指向其前后的BIO,这也是BIO链式操作的基础。BIO链的基本操作函数为:

BIO * BIO_push(BIO *a,BIO *b);
BIO * BIO_pop(BIO *b);

前者将b链接到a之后,返回b,后者将b从链条上摘除,返回b,原来的链条依然完整。

BIO还有一些辅助函数,例如处理错误,获取状态等函数。

4.BIO Pair

BIO对是一种比较特别的BIO,它由两个BIO组成,但从它的实现代码来看,它似乎是与BIO平行的一种实现方式,因而不能单纯的用BIO链来说明,它的逻辑结构如下图所示。

BIO pair连接两个外部端A,B,从外部来看,A端写,则可从B端读出;B端写,则可从A端读出。从内部来看,有两个内存型BIOA和BIOB,分别与A端和B端相连,它们有各自的缓存,A端存入和B端读取的数据,利用的是BIOA缓存,B端写入A端读取则利用的是BIOB的缓存。

因此BIO pair类似于“双向有缓存管道”,从任一端写入,另一端读出,由于内存缓存的利用,使得一端的读/写操作都是“即刻”完成的,它不用关心另一端什么时候做写/读操作,这就是典型的异步操作。目前很多网络通信库采用的都是异步读写,因而BIO pair这种应用模型是OpenSSL适配这些网络库的一个重要方法。

创建BIO pair的简洁方法是BIO_new_bio_pair,它实际上是 BIO_new, BIO_make_bio_pair,  BIO_set_write_buf_size的组合。

5.构造自定义BIO类型

OpenSSL已定义了若干BIO,当然也可以自定义一个。下面的示例构造了一个简单的CMYBIO,以此来说明BIO的工作原理。

class CMYBIO
{
private:static int write_ex(BIO* h, const char* buf, size_t num, size_t * len){printf("CMYBIO::write_ex\n");for (int i = 0; i < num; ++i)  printf("%c", buf[i]);printf("\n");*len = num;return 1;}static int read_ex(BIO* h, char* buf, size_t size, size_t* len){printf("CMYBIO::read_ex\n");unsigned int* opt = (unsigned int*)BIO_get_data(h);if (*opt == 0){size = size > 6 ? 6 : size;memcpy(buf, "openss", size);*len = size;
//			BIO_clear_retry_flags(h);
//			BIO_copy_next_retry(h);return 1;}if (*opt == 1){size = 9;memcpy(buf, "MTIzNDU2\n", size);           //base64("123456")*len = size;BIO_clear_retry_flags(h);
//			BIO_copy_next_retry(h);return 1;}return 0;}static long ctrl(BIO* h, int cmd, long arg1, void* arg2){printf("CMYBIO::ctrl[%d]\n", cmd);if (cmd == 0xff){unsigned int* opt = (unsigned int*)BIO_get_data(h);*opt = arg1;}return 1;}static int create(BIO* bio){printf("CMYBIO::create\n");BIO_set_init(bio, 1);            //”init” must be set explicitly,otherwise “read”/”write” methods will be invoked. Although it seems odd, the “framework” would not do It for you by the returned value. unsigned int* opt =(unsigned int*) malloc(sizeof(int));*opt = 0;BIO_set_data(bio,opt);return 1;}static int destory(BIO* bio){printf("CMYBIO::destory\n");unsigned int* opt=(unsigned int*)BIO_get_data(bio);free(opt);return 1;}static BIO_METHOD* method;
public:static BIO_METHOD* BIO_s_my(){return CMYBIO::method;}static void UnInit(){BIO_meth_free(CMYBIO::method);}static void Init(void){CMYBIO::method = BIO_meth_new((100 | BIO_TYPE_SOURCE_SINK), "My BIO");int r;r = BIO_meth_set_create(CMYBIO::method, CMYBIO::create);r = BIO_meth_set_destroy(CMYBIO::method, CMYBIO::destory);r = BIO_meth_set_write_ex(CMYBIO::method,  CMYBIO::write_ex);r=BIO_meth_set_read_ex(CMYBIO::method, CMYBIO::read_ex);r=BIO_meth_set_puts(CMYBIO::method,  nullptr);r=BIO_meth_set_gets(CMYBIO::method, nullptr);        r=BIO_meth_set_ctrl(CMYBIO::method, CMYBIO::ctrl);r=BIO_meth_set_callback_ctrl(CMYBIO::method, nullptr);}};BIO_METHOD* CMYBIO::method=nullptr;

下面的代码使用了上面构造的CMYBIO,其中第一段是单独使用CMYBIO,第二段是与BIO_f_base64组成链式应用。CMYBIO作为单独应用,构造的功能已经够用,但组成链式应用,由于链式调用需要在每个具体的实现方法内来完成,因此上面的代码还不够,为简化,这里仅把CMYBIO作为链式应用的最后一级来使用。

long bio_cb(BIO* b, int oper, const char* argp, size_t len, int argi, long argl, int ret, size_t* processed)
{printf("bio callback:%d, %u\n", oper,(unsigned int)len);return 1;
}
void test_bio()
{BIO* bmy = NULL;size_t s = 0;int len = 0;char* out = NULL;char cc[24];memset(cc, 0, sizeof(cc));CMYBIO::Init();bmy = BIO_new(CMYBIO::BIO_s_my());BIO_set_callback_ex(bmy, bio_cb);len = BIO_write_ex(bmy, "openssl", 7, &s);printf("BIO_write_ex return [%d, %u]\n\n", len, (unsigned int)s);len = 7;out = (char*)OPENSSL_malloc(len);memset(out, 0, len);len = BIO_read_ex(bmy, out, len-1, &s);printf("BIO_read_ex return [%d,%u]%s\n", len, (unsigned int)s, out);OPENSSL_free(out);BIO_free(bmy);printf("--------------------------------\n");BIO* b64 = BIO_new(BIO_f_base64());BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);bmy = BIO_new(CMYBIO::BIO_s_my());BIO_push(b64, bmy);len=BIO_write_ex(b64, "123456", 6, &s);printf("BIO_write_ex return [%d, %u]\n\n", len, (unsigned int)s);BIO_flush(b64);             //important!!!BIO_ctrl(bmy, 0xff, 1, nullptr);len = BIO_read_ex(b64, cc, sizeof(cc)- 1, &s);printf("BIO_read_ex return [%d,%u]%s\n\n", len, (unsigned int)s, cc);//OPENSSL_free(out);BIO_free_all(b64);CMYBIO::UnInit();
}

整个过程输出如下图所示。前一个示例主要展示CMYBIO中各方法的调用过程,其中还设置了“钩子”函数,从输出可以清晰看出BIO的工作过程。后一个示例稍有些麻烦,其中增加了CMYBIO内部的设置,从而可以实现不同的效果。

需要注意的是,BIO_f_base64有其特殊之处,对于“write”,需要在完成后调用flush才能正确工作,对于“read”,编码串结束的标志是“\n”,但还是要设置BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL),否则可能会有错误。

http://www.xdnf.cn/news/351073.html

相关文章:

  • PostgreSQL创建只读账号
  • 数据中台建设系列(五):SQL2API驱动的数据共享与服务化实践
  • 游戏引擎学习第266天:添加顶部时钟概览视图。
  • TensorFlow深度学习实战(15)——编码器-解码器架构
  • 可视化图解算法36: 序列化二叉树-I(二叉树序列化与反序列化)
  • 用 Java 实现 哲学家就餐问题
  • 数字信号处理|| 离散序列的基本运算
  • IPv6协议
  • 基于Transformer与SHAP可解释性分析的神经网络回归预测模型【MATLAB】
  • 英文单词 do、play、go 的区别
  • 大模型的RAG技术系列(二)
  • ADV7842KBCZ - 5 富利威长期稳定供应
  • MLX-Audio:高效音频合成的新时代利器
  • 【图片识别内容改名】图片指定区域OCR识别并自动重命名,批量提取图片指定内容并重命名,基于WPF和阿里云OCR识别的解决
  • wpf UserControl 更换 自定义基类
  • 三款实用电脑工具
  • 【CTFSHOW_Web入门】命令执行
  • K8S - GitLab CI 自动化构建镜像入门
  • 按位宽提取十六进制值
  • OpenCV的 ccalib 模块用于自定义标定板的检测和处理类cv::ccalib::CustomPattern()----函数calibrate
  • uniapp开发的项目上传到国内主流应用市场(华为、小米、oppo、vivo)
  • COLT_CMDB_aix_diskinfo.sh
  • OCCT中的基础变换
  • C++卡特兰数讲解
  • Java 显式锁与 Condition 的使用详解
  • Android MVC架构的现代化改造:构建清晰单向数据流
  • AI搜索的未来:技术纵深发展与关键突破路径
  • Kubernetes 手动部署 Prometheus 学习计划
  • 【计算机网路】--tcp四次挥手关闭连接
  • pm2 list查询服务时如何通过name或者namespace进行区分