bio介绍
bio是openssl封装的io库,类型分为source/sink 和filter两种类型
bss_开头的文件是source/sink类型的,bf_开头的是filter类型(主要包括对数据的处理,比如base64编码,cipher加解密等)。网上的介绍很多了,这里主要通过几个测试例子来验证和测试在使用过程中需要注意的事项。
以下例子使用openssl1.1.1m测试通过
#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
//内存bio使用
void membio()
{
BIO *bio = BIO_new(BIO_s_mem());
BIO_write(bio, "openssl", 4);
BIO_printf(bio, "%s", "hellow");
size_t len = BIO_ctrl_pending(bio); //返回pending的数量
if (len > 0)
{
printf("%ld\n", len); //长度为10
char *buf = (char *)OPENSSL_malloc(len + 1);
memset(buf, 0, len + 1);
int readnum = BIO_read(bio, buf, len); //传多大空间读多少数据
printf("%s\n", buf);
}
}
//文件bio使用
void filebio()
{
// BIO * fbio=BIO_new(BIO_s_file());
// BIO_write_filename(fbio,"xx.log");
// BIO_read_filename(fbio,"xx.log");
BIO *bio = BIO_new_file("xx.log", "r");
BIO *wbio = BIO_new_file("yy.log", "a+");
// size_t len=BIO_ctrl_pending(bio);文件类型的bio测试BIO_ctrl_pending只能获取到0。
// int len =BIO_pending(bio);//文件类型的bio测试bio_pending只能获取到0。得到BIO中读缓存或写缓存中字符的数目
// 似乎不能获取到文件长度
char buf[10] = {0};
while (BIO_eof(bio) != 1) // 1代表读到了末尾
{
int len = BIO_gets(bio, buf, 5);
//传5个只读四个,内部使用的是c语言的fgets,这样如果传5只能读取4个字符,其他的用\0填充,比较安全
// c中的gets方法会丢弃回车即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。
if (len > 0)
{
// printf("%s\n", buf);
BIO_printf(wbio, "%s", buf);
}
}
BIO_free(wbio);
BIO_free(bio);
}
//socket bio使用
void socket_server_bio()
{
int socket = BIO_get_accept_socket("5566", 0);
BIO *bio = BIO_new_socket(socket, BIO_NOCLOSE);
char *addr;
int ac_sock = BIO_accept(socket, &addr);
BIO_set_fd(bio, ac_sock, BIO_NOCLOSE);
unsigned char buf[1024];
while (1)
{
memset(buf, 0, sizeof(buf));
int len = BIO_read(bio, buf, sizeof(buf));
if (len > 0)
{
if (buf[0] == 'q')
{
break;
}
else
{
printf("%s\n", buf); // read可能会读1024个,be care
}
}
else
{
break;
}
}
}
void socket_bio_client()
{
BIO *bio, *outbio;
bio = BIO_new_connect("127.0.0.1:9090"); //可以是IP加端口
outbio = BIO_new_fd(1, BIO_NOCLOSE); // 0标准输入,1标准输出
if (BIO_do_connect(bio) <= 0)
{
printf("connect fail");
goto end;
};
char buf[1024];
BIO_puts(bio, "GET / HTTP/1.0\n\n");
while (1)
{
int len = BIO_read(bio, buf, sizeof(buf));
if (len <= 0)
break;
BIO_write(outbio, buf, len);
BIO_flush(outbio);
}
end:
BIO_free(bio);
BIO_free(outbio);
}
//摘要bio
void bioDigist()
{
// md5,base64,cipher这些bio可能没有puts,gets方法,如要要使用这些方法,需要给他附加一个BIO_f_buffer,
// md有gets无puts
BIO *bio = BIO_new(BIO_f_md());
BIO_set_md(bio, EVP_md5());
BIO *fbio = BIO_new_file("xx.txt", "wb");
// BIO * nullbio=BIO_new(BIO_s_null());
// BIO_new(BIO_f_null());//什么也不做,传给下一个bio,和BIO_s_null不同,他直接丢弃
// BIO_new(BIO_f_buffer());
// bio= BIO_push(bio,nullbio);
BIO_push(bio, fbio);
char data[] = "adfdsfdsf";
BIO_write(bio, (const void *)data, strlen(data)); //对称BIO,read write应该都一样
//这个bio_md.c中的write方法如果有next,先把数据写到next,也就是我这里的写入文件的数据是原始数据,然后调用了EVP_DigestUpdate
BIO_flush(bio);
char buf[64];
memset(buf, 0, 64);
int len = BIO_gets(bio, buf, 64); //此方法才会调用EVP_DigestFinal_ex,所以想把进行摘要后保存到文件只能获取这个值自己保存到文件?
// printf("%d,%s\n",len,buf);
char *mdstr = OPENSSL_buf2hexstr(buf, (long)len);
printf("%s\n", mdstr);
// char buf[64];
// memset(buf, 0, sizeof(buf));
// BIO_read(bio, buf, 64);
// printf("%s\n", buf);
BIO_free_all(bio);
}
//加解密bio
void bio_cipher()
{
BIO *bio = BIO_new(BIO_f_cipher()); // bio_enc.c
const EVP_CIPHER *cipher = EVP_aes_128_cbc();
BIO_set_cipher(bio, cipher, "0123456789ABCDEF", "0123456789ABCDEF", 1);
const char *data = "hello";
BIO *cbio = BIO_new_file("cipher.txt", "w");
BIO_push(bio, cbio);
//注意如果没有next bio直接返回,也就是他必须把加密结果输出到一个地方。
//将数据进行加密EVP_CipherUpdate,然后把加密的数据写入到下一个bio,加密一段数据加密的结果在结构体BIO_ENC_CTX中
//每次调用EVP_CipherUpdate(cipher,out,outlen,in ,inlen);都会有输出。
//那什么时候调用EVP_CipherFinal_ex呢,也就是缓存中还剩下的未加密的数据,现在在flush的时候又调用,
//在read的时候也会调用
//也就是说读是从下一个bio中读,读完之后需要进行加解密(至于是加密还是解密时通过上面的BIO_set_cipher方法中的参数决定的)
//当读完了数据不够加解密分组的时候会调用EVP_CipherFinal_ex(不够分组进行填充)
//写只有在flush的时候才看缓存中数据长度不够分组的时候调用EVP_CipherFinal_ex
BIO_write(bio, data, strlen(data));
BIO_flush(bio); //写数据必须调用flush
//----------------------------------------------
//解密
BIO *denBIO = BIO_new(BIO_f_cipher());
const EVP_CIPHER *denCipher = EVP_aes_128_cbc();
BIO_set_cipher(denBIO, denCipher, "0123456789ABCDEF", "0123456789ABCDEF", 0);
BIO *rbio = BIO_new_file("cipher.txt", "r");
BIO_push(denBIO, rbio);
char buf[1024];
memset(buf, 0, 1024);
int readlen = BIO_read(denBIO, buf, 1024);//这里做成循环读取,返回小于等于0时退出。
printf("%s\n",buf);
BIO_free_all(bio);
BIO_free_all(denBIO);
}
//base64编码bio
void bio_base64(){
#if 0
BIO * bio=BIO_new(BIO_f_base64());
BIO *outfile=BIO_new_file("base64","w");
BIO_push(bio,outfile);
char *data="abcdefg打开";
BIO_write(bio,data,strlen(data));//也是需要一个next 否则直接返回,写是编码,读是解码
BIO_flush(bio);//需要调用flush
BIO_free_all(bio);
#endif
//---------------------------------------
BIO * bio=BIO_new(BIO_f_base64());
BIO *outfile=BIO_new_file("base64","r");
BIO_push(bio,outfile);
char buf[1024];
memset(buf,0,1024);
BIO_read(bio,buf,1024);
printf("%s\n",buf);
BIO_free_all(bio);
}
int main()
{
// filebio();
// socket_server_bio();
// socket_bio_client();
// bio 分为source/sink系列和filter系列两种。
//bioDigist();
// bio_cipher();
bio_base64();
return 0;
}