OpenSSL BIO源码简析

article/2025/11/7 13:30:50

文章目录

  • 1. BIO简介
    • BIO chain
    • BIO数据结构
    • BIO_METHOD数据结构
  • 2. Base64示例分析
    • 初始化
    • 构造BIO链
    • 写数据
    • free

1. BIO简介

相关文档

/html/man7/bio.html
/html/man3/BIO_*.html

bio - Basic I/O abstraction,即IO抽象层。

BIO有两种:

  • source/sink BIO,即数据源,如socket BIO、file BIO,初始化接口以BIO_s_开头;
  • filter BIO,过滤器,用来接收和传递数据,初始化接口以BIO_f_开头;

BIO chain

BIO可以组成一条链,即使是单个BIO,实质也是有一个节点的链。

一个链通常由1个source/sink BIO、1个或多个filter BIO组成。

数据从第一个BIO写入或读出,并传递到最后一个节点(通常是source/sink BIO)。

相关api:

  • BIO_push
  • BIO_free_all 释放整个链

BIO数据结构

源码位置:\crypto\bio\bio_local.h

struct bio_st {// BIO_new初始化需要提供libctx和method参数// BIO *BIO_new(const BIO_METHOD *method)// {//     return BIO_new_ex(NULL, method);// }OSSL_LIB_CTX *libctx;	// NULL: default contextzconst BIO_METHOD *method;/* bio, mode, argp, argi, argl, ret */
#ifndef OPENSSL_NO_DEPRECATED_3_0BIO_callback_fn callback;
#endifBIO_callback_fn_ex callback_ex;char *cb_arg;               /* first argument for the callback */// 这里用int来作标志应该有点浪费// 用bool或bit更好些int init;		// 初始化标志int shutdown;int flags;                  /* extra storage */int retry_reason;int num;void *ptr;		// BIO_set_data() // bio链本质是双向链表struct bio_st *next_bio;    /* used by filter BIOs */struct bio_st *prev_bio;    /* used by filter BIOs */CRYPTO_REF_COUNT references;uint64_t num_read;uint64_t num_write;CRYPTO_EX_DATA ex_data;CRYPTO_RWLOCK *lock;	// 线程读写锁
};

BIO_METHOD数据结构

源码路径:\include\internal\bio.h

struct bio_method_st {int type;char *name;int (*bwrite) (BIO *, const char *, size_t, size_t *);int (*bwrite_old) (BIO *, const char *, int);int (*bread) (BIO *, char *, size_t, size_t *);int (*bread_old) (BIO *, char *, int);int (*bputs) (BIO *, const char *);int (*bgets) (BIO *, char *, int);long (*ctrl) (BIO *, int, long, void *);int (*create) (BIO *);int (*destroy) (BIO *);long (*callback_ctrl) (BIO *, int, BIO_info_cb *);
};

该结构除了类型和名称,其余均是函数指针。

2. Base64示例分析

借用官网Base64示例:https://www.openssl.org/docs/man3.0/man3/BIO_f_base64.html,它将"hello world \n"的base64输出到stdout:

#include <iostream>
#include <openssl/bio.h>
#include <openssl/evp.h>
int main()
{BIO* bio, * b64;char message[] = "Hello World \n";b64 = BIO_new(BIO_f_base64());bio = BIO_new_fp(stdout, BIO_NOCLOSE);BIO_push(b64, bio);BIO_write(b64, message, strlen(message));BIO_flush(b64);// SGVsbG8gV29ybGQgCg==BIO_free_all(b64);getchar();return 0;
}

用vscode在源码目录搜索“base64”,并没有找到实现源码,于是全局搜索编码表,定位到源码路径:

// \crypto\evp\encode.c
static const unsigned char data_bin2ascii[65] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

把encode.c拖到vs中,在引用编码表的地方下断点,调试示例程序,成功断下:

请添加图片描述

BIO_flush调用栈如下:

libcrypto-3.dll!evp_encodeblock_int(evp_Encode_Ctx_st * ctx, unsigned char * t, const unsigned char * f, int dlen) Line 238	C
libcrypto-3.dll!EVP_EncodeFinal(evp_Encode_Ctx_st * ctx, unsigned char * out, int * outl) Line 222	C
libcrypto-3.dll!b64_ctrl(bio_st * b, int cmd, long num, void * ptr) Line 506	C
libcrypto-3.dll!BIO_ctrl(bio_st * b, int cmd, long larg, void * parg) Line 579	C
TestOpenSSL.exe!main() Line 13	C++

EVP是OpenSSL的算法实现接口,结合evp文档(/html/man7/evp.html),这里正是base64编解码逻辑:

The EVP_EncodeXXX and EVP_DecodeXXX functions implement base 64 encoding and decoding.

初始化

b64 = BIO_new(BIO_f_base64());
bio = BIO_new_fp(stdout, BIO_NOCLOSE);

BIO_new_fp(),内部其实是调用了BIO_new(BIO_s_file())初始化一个source/sink BIO。所以只分析第一个base64 filter BIO。

BIO_f_base64()返回一个base64的静态BIO_METHOD

// \crypto\evp\bio_b64.c
static const BIO_METHOD methods_b64 = {BIO_TYPE_BASE64,"base64 encoding",bwrite_conv,b64_write,bread_conv,b64_read,b64_puts,NULL,                       /* b64_gets, */b64_ctrl,b64_new,b64_free,b64_callback_ctrl,
};const BIO_METHOD *BIO_f_base64(void)
{return &methods_b64;
}

通过BIO_TYPE_BASE64这个宏type,可以在\include\openssl\bio.h定位到其它method type:

/*在源码层面,BIO其实是有3种。
*/
/* There are the classes of BIOs */
# define BIO_TYPE_DESCRIPTOR     0x0100 /* socket, fd, connect or accept */
# define BIO_TYPE_FILTER         0x0200
# define BIO_TYPE_SOURCE_SINK    0x0400/* These are the 'types' of BIOs */
# define BIO_TYPE_NONE             0
# define BIO_TYPE_MEM            ( 1|BIO_TYPE_SOURCE_SINK)
# define BIO_TYPE_FILE           ( 2|BIO_TYPE_SOURCE_SINK)# define BIO_TYPE_FD             ( 4|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
// ...
# define BIO_TYPE_BASE64         (11|BIO_TYPE_FILTER)

BIO_new则根据base 64 method初始化filter BIO:

请添加图片描述
method->createb64_new(),用于初始化bio->ptr==b64_ctx

// \crypto\evp\bio_b64.c
typedef struct b64_struct {/** BIO *bio; moved to the BIO structure*/int buf_len;int buf_off;int tmp_len;                /* used to find the start when decoding */int tmp_nl;                 /* If true, scan until '\n' */int encode;int start;                  /* have we started decoding yet? */int cont;                   /* <= 0 when finished */EVP_ENCODE_CTX *base64;		char buf[EVP_ENCODE_LENGTH(B64_BLOCK_SIZE) + 10];char tmp[B64_BLOCK_SIZE];
} BIO_B64_CTX;

函数栈如下:

>	libcrypto-3.dll!b64_new(bio_st * bi) Line 71	CBIO_B64_CTX *ctx;//...BIO_set_data(bi, ctx); // set bi->ptrBIO_set_init(bi, 1);	// initialized libcrypto-3.dll!BIO_new_ex(ossl_lib_ctx_st * libctx, const bio_method_st * method) Line 104	Cif (method->create != NULL && !method->create(bio)) {// ...goto err;}if (method->create == NULL)bio->init = 1;	// 若没有单独的create初始化函数,则直接设置init标志libcrypto-3.dll!BIO_new(const bio_method_st * method) Line 122	Creturn BIO_new_ex(NULL, method);TestOpenSSL.exe!main() Line 9	C++

构造BIO链

源码位置:\crypto\bio\bio_lib.c

// BIO_push(b64, bio);
BIO *BIO_push(BIO *b, BIO *bio)
{BIO *lb;if (b == NULL)return bio;lb = b;while (lb->next_bio != NULL)lb = lb->next_bio;lb->next_bio = bio;if (bio != NULL)bio->prev_bio = lb;/* called to do internal processing */BIO_ctrl(b, BIO_CTRL_PUSH, 0, lb);return b;
}

在BIO链表中,stdout source/sink BIO是在 base64 filter BIO之后的。

写数据

BIO_write(b64, message, strlen(message));
BIO_flush(b64);

再看一下methods_b64:

static const BIO_METHOD methods_b64 = {BIO_TYPE_BASE64,"base64 encoding",bwrite_conv,	// int (*bwrite) (BIO *, const char *, size_t, size_t *);b64_write,		// int (*bwrite_old) (BIO *, const char *, int);//...
}

调用BIO_write,其实是调用base64 method的bwritebwrite_old函数,形成如下函数栈:

libcrypto-3.dll!EVP_EncodeInit(evp_Encode_Ctx_st * ctx) Line 156	C
libcrypto-3.dll!b64_write(bio_st * b, const char * in, int inl) Line 346	C
libcrypto-3.dll!bwrite_conv(bio_st * bio, const char * data, unsigned int datal, unsigned int * written) Line 77	Cret = bio->method->bwrite_old(bio, data, (int)datal);
libcrypto-3.dll!bio_write_intern(bio_st * b, const void * data, unsigned int dlen, unsigned int * written) Line 362	Cret = b->method->bwrite(b, data, dlen, &local_written);
libcrypto-3.dll!BIO_write(bio_st * b, const void * data, int dlen) Line 384	C
TestOpenSSL.exe!main() Line 12	C++

但经过调试,执行BIO_write后标准输出(就是屏幕)并没有回显,在evp_encodeblock_int的断点也没有断下。而根据最开始贴出的BIO_flush调用栈,它在执行缓冲区刷新时才开始执行evp_encodeblock_int,文档如下:

https://www.openssl.org/docs/man3.0/man3/BIO_flush.html
BIO_flush() normally writes out any internally buffered data, in some cases it is used to signal EOF and that no more data will be written.

该刷新函数底层是调用BIO_ctrl()实现的,该类控制函数通常并不直接使用,而是通过BIO_flush等定义于\include\openssl\bio.h中的宏函数来调用的。

执行evp_encodeblock_int后,b64_ctrl()会将base64写入bio链下一节点,并执行下一节点的ctrl函数:

libcrypto-3.dll!b64_write(bio_st * b, const char * in, int inl) Line 354	Cnext = BIO_next(b);// ...while (n > 0) {i = BIO_write(next, &(ctx->buf[ctx->buf_off]), n);	// 写入stdout bio  这时屏幕会出现base64值// ...}
libcrypto-3.dll!b64_ctrl(bio_st * b, int cmd, long num, void * ptr) Line 490	Ccase BIO_CTRL_FLUSH:/* do a final write */while (ctx->buf_len != ctx->buf_off) {i = b64_write(b, NULL, 0);	// <--// ...}// .../* Finally flush the underlying BIO */ret = BIO_ctrl(next, cmd, num, ptr);	// stdout bio的ctrl函数为file_ctrl()
libcrypto-3.dll!BIO_ctrl(bio_st * b, int cmd, long larg, void * parg) Line 579	Cret = b->method->ctrl(b, cmd, larg, parg);
TestOpenSSL.exe!main() Line 13	C++

free

BIO_free_all(b64);

这个函数的作用,猜也能猜的到,遍历链表逐个释放空间。

// \crypto\bio\bio_lib.c
void BIO_free_all(BIO *bio)
{BIO *b;int ref;while (bio != NULL) {b = bio;	// 从第一个节点开始释放ref = b->references;bio = bio->next_bio;BIO_free(b);/* Since ref count > 1, don't free anyone else.意思是别人还在用 别删*/if (ref > 1)break;}
}// 需要释放的东西还是很多的
int BIO_free(BIO *a)
{int ret;if (a == NULL)return 0;// 引用数减1if (CRYPTO_DOWN_REF(&a->references, &ret, a->lock) <= 0)return 0;REF_PRINT_COUNT("BIO", a);if (ret > 0)return 1;REF_ASSERT_ISNT(ret < 0);if (HAS_CALLBACK(a)) {ret = (int)bio_call_callback(a, BIO_CB_FREE, NULL, 0, 0, 0L, 1L, NULL);if (ret <= 0)return 0;}if ((a->method != NULL) && (a->method->destroy != NULL))a->method->destroy(a);	// b64_free()CRYPTO_free_ex_data(CRYPTO_EX_INDEX_BIO, a, &a->ex_data);CRYPTO_THREAD_lock_free(a->lock);OPENSSL_free(a);return 1;
}

http://chatgpt.dhexx.cn/article/ko1AgDdN.shtml

相关文章

二、JAVA BIO

NIO 目录 文章目录 二、JAVA BIO1、 Java BIO基本介绍2、 java BIO工作机制3、传统的BIO编程实例回顾3.1、客户端案例如下3.2、服务端案例如下3.3、输出3.4、小结 4、BIO模式下多发和多收消息4.1、客户端代码如下4.2、服务端代码如下4.3、输出 5、BIO模式下接收多个客户端5.1、…

BIO和NIO的区别

1.BIO基本介绍 BIO是传统的Java IO编程&#xff0c;其基本的类和接口在java.io包中BIO(blocking I/O)&#xff1a;同步阻塞&#xff0c;服务器实现模式为一个连接一个线程&#xff0c;即客户端有连接请求时服务器端就需要启动一个线程进行处理&#xff0c;如果这个连接不做任何…

网络编程之中篇——BIO模型详述

1、BIO介绍 1.1、BIO的概念 BIO&#xff08;Blocking IO&#xff09;同步阻塞IO模型&#xff0c;在JDK 1.4之前&#xff0c;建立网络链接采用的只有BIO的模型 需要服务端首先启动建立一个ServerSocket实例&#xff0c;然后客户端启动Socket实例对服务端进行连接通信&#xf…

BIO,NIO,AIO分别是什么?他们有什么区别?

1、BIO 概念&#xff1a; BIO是一种同步阻塞I/O模式&#xff0c;服务实现模式为一个连接对应一个线程&#xff0c;即客户端发送一个连接&#xff0c;服务端要有一个线程来处理。 存在的问题&#xff1a; 一旦有高并发的大量请求,就会有如下问题&#xff1a; 1&#xff09;线程…

JAVA BIO与NIO、AIO的区别(这个容易理解)

IO的方式通常分为几种&#xff0c;同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。 一、BIO 在JDK1.4出来之前&#xff0c;我们建立网络连接的时候采用BIO模式&#xff0c;需要先在服务端启动一个ServerSocket&#xff0c;然后在客户端启动Socket来对服务端进行通信&#…

Quartus II 上手攻略

第一次接触EDA实验&#xff0c;对这方面的相关操作并不熟悉。本篇文章结合上课内容和B站Quartus进行整理&#xff0c;总结一下Quartus 这款软件的基本使用。 参考的B站教学链接&#xff1a;《Quartus II 软件安装与入门教程》 Quartus 软件简介 Quartus II 是Altera公司为其FP…

完全卸载quartus ii 9.0

即将毕业了&#xff0c;把电脑一些不用的软件清清&#xff0c;发现quartus软件贼占空间&#xff0c;删除又貌似找不到卸载的exe&#xff0c;百度了好多都不靠谱 下面介绍一种方法&#xff0c;可以很好的卸载掉quartus&#xff0c;原先我的quartus是安装在D盘下&#xff0c;结果…

Quartus II与Modelsim软件安装教程

Quartus II与Modelsim软件安装教程 一、Quartus II软件安装1、Quartus II安装2、器件安装3、Quartus 破解4、USB Blaster 驱动安装 二、Modelsim软件安装1、modelsim安装2、modelsim注册 三、参考资料 一、Quartus II软件安装 本节主要讲述Quartus II13.1软件的安装使用&#x…

Quartus II13.1安装教程

安装前先关闭杀毒软件和360卫士&#xff0c;注意安装路径不能有中文&#xff0c;安装包路径也不要有中文。 1.鼠标右击【Quartus II 13.1】压缩包选择【解压到Quartus II 13.1】。 2.双击打开解压后的【Quartus II 13.1】文件夹。 3.双击打开【Quartus】文件夹。 4.鼠标右击【Q…

Quartus II下载器件库

Quartus II下载器件库 1、在浏览器中输入网址 https://fpgasoftware.intel.com/18.1/?editionstandard&platformwindows&#xff0c; 或https://fpgasoftware.intel.com/ 进入如下图所示界面。 2、在版本类型和版本中输入Quartus II所对应的版本 3、输入完版本后&#…

Quartus II软件的使用

在这里&#xff0c;我们只是简单的介绍了一下上述的流程图&#xff0c;让大家有个大致的了解&#xff0c;接下来我们就以流水灯实验的工程为例&#xff0c;对每个流程进行详细的操作演示&#xff0c;一步步、手把手带领大家学习使用Quartus II软件。 在创建工程之前&#xff0c…

QuartusII中LPM_COUNTER的使用

ALTERA建议&#xff0c;在设计时时序允许的情况下尽量使用Megafunction的资源&#xff0c;因为在多数情况下Megafunction的综合和实现结果更为优化。现在&#xff0c;就LPM_COUNTER的使用&#xff0c;浅谈一下。 Megafunction中LPM_COUNTER的参数设定主要是以下三部分&#xf…

quartus II 18.1 下载

quartus II 18.1 下载链接 以及解析 链接:https://pan.baidu.com/s/1warS-Vvv1maDmOKu8RsteQ 提取码&#xff1a;awxd 这个链接是已经下好的安装包 链接:https://pan.baidu.com/s/13HuyxUZvZ19vdYUmlLJujQ 提取码&#xff1a;gudn 第二个链接解压密码&#xff1a; wqlx.13542…

Quartus II14.1安装教程

安装前先关闭杀毒软件和360卫士&#xff0c;注意安装路径不能有中文&#xff0c;安装包路径也不要有中文。 1.鼠标右击【Quartus II 14.1】压缩包选择【解压到Quartus II 14.1】。 2.双击打开解压后的【Quartus II 14.1】文件夹。 3.双击打开【Quartus】文件夹。 4.鼠标右击【Q…

quartus ii matlab,基於Quartus II和MATLAB的FIR濾波器設計與仿真(二)

接上文 基於Quartus II和MATLAB的FIR濾波器設計與仿真(一)&#xff1a; 3 QuartusII 調用 IP 核生成 FIR 濾波器模塊 在 Quartus II 中&#xff0c; Altera 提供了一系列可供用戶免費使用的 IP 核&#xff0c; FIR濾波器就包含其中&#xff0c;所以只需要在 Quartus II 中調用…

安装Quartus II教程

下载Quartus安装包&#xff0c;给大家一个11.3版本的安装包 链接&#xff1a;https://pan.baidu.com/s/1eXtjL2JZVGV1RBC0VozqVQ?pwdhmnv 提取码&#xff1a;hmnv 1.打开安装程序&#xff0c;点击next 2.点击接受&#xff0c;下一步 3.选择安装路径&#xff0c;这里最好选择…

Quartus II 仿真

Quartus II 使用university program VWF仿真 1.File->new->university program VWF->OK打开仿真页面 2.edit->insert->insert node or bus或者直接双击左边空白地方弹出insert node or bus对话框。 3.node finder->list-> >> ->OK->OK 4.设…

QuartusII9.0--项目文件的新建

第一步&#xff1a;打开QuartusII软件&#xff0c;界面如下&#xff1a; 第二步&#xff1a;选择File->New Project Wizard菜单项&#xff0c;则弹出New Project Wizard:Indroduction对话框&#xff0c;如下图所示&#xff1a; 单击Next按钮&#xff0c;则进入项目工程的目…

Quartus II报错

使用如下电路语句创建异步时序实现D触发器时 一直报错 Error (10200): Verilog HDL Conditional Statement error at flip_flop.v(9): cannot match operand(s) in the condition to the corresponding edges in the enclosing event control of the always construct 在网上…

【QuartusII】0-创建工程模板

一、创建工程 1、激活安装quartus II软件后&#xff0c;打开即见如下界面 2、在菜单栏 “File -> New Project Wizard…”中&#xff0c;进入创建工程流程 3、第一部分&#xff0c;如下图&#xff0c;配置路径、项目名称、以及顶层文件&#xff08;类似C语言的main&#xf…