开源加密库Openssl 剖析&实战

您所在的位置:网站首页 sha256解密算法源代码 开源加密库Openssl 剖析&实战

开源加密库Openssl 剖析&实战

2024-07-06 08:17| 来源: 网络整理| 查看: 265

一、OpenSSL简介

在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。

它提供的主要功能有: SSL协议实现(包括SSLv2、 SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、 ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、 CRL编解码、OCSP协议、数字证书验证、 PKCS7标准实现和PKCS12个人数字证书格式实现等功能。OpenSSL采用C语言作为开发语言,这使得它具有优秀的跨平台性能。 OpenSSL支持Linux、 UNIX、 windows、 Mac等平台。广泛被应用在互联网的网页服务器上。

1.1、OpenSSL安装

下载路径:https://www.OpenSSL.org/source/ 在这里插入图片描述

安装步骤

$ tar zxvf OpenSSL-1.1.1i.tar.gz $ cd OpenSSL-1.1.1i $ ./config --prefix=/usr/local/OpenSSL $ make $ sudo make install

1.2、OpenSSL源码结构

OpenSSL源代码主要由eay库、 ssl库、工具源码、范例源码以及测试源码组成。

(一)eay库 eay库是基础的库函数,提供了很多功能。源代码放在crypto目录下。包括如下内容: 在这里插入图片描述

asn1 DER编码解码(crypto/asn1目录),包含了基本asn1对象的编解码以及数字证书请求、数字证书、 CRL撤销列表以及PKCS8等最基本的编解码函数。这些函数主要通过宏来实现。

抽象IO(BIO,crypto/bio目录),本目录下的函数对各种输入输出进行抽象,包括文件、内存、标准输入输出、 socket和SSL协议等。

大数运算(crypto/bn目录),本目录下的文件实现了各种大数运算。这些大数运算主要用于非对称算法中密钥生成以及各种加解密操作。另外还为用户提供了大量辅助函数,比如内存与大数之间的相互转换。

字符缓存操作(crypto/buffer目录)

配置文件读取(crypto/conf目录), OpenSSL主要的配置文件为OpenSSL.cnf。本目录下的函数实现了对这种格式配置文件的读取操作。

DSO(动态共享对象,crypto/dso目录),本目录下的文件主要抽象了各种平台的动态库加载函数,为用户提供统一接口。

硬件引擎(crypto/engine目录),硬件引擎接口。用户如果要写自己的硬件引擎,必须实现它所规定的接口。

错误处理(crypto/err目录),当程序出现错误时, OpenSSL能以堆栈的形式显示各个错误。本目录下只有基本的错误处理接口,具体的的错误信息由各个模块提供。各个模块专门用于错误处理的文件一般为*_err…c文件。

对称算法、非对称算法及摘要算法封装(crypto/evp目录)。

HMAC(crypto/hmac目录),实现了基于对称算法的MAC。

hash表(crypto/lhash目录),实现了散列表数据结构。 OpenSSL中很多数据结构都是以散列表来存放的。比如配置信息、 ssl session和asn.1对象信息等。

数字证书在线认证(crypto/ocsp目录),实现了ocsp协议的编解码以及证书有效性计算等功能。

PEM文件格式处理(crypto/pem),用于生成和读取各种PEM格式文件,包括各种密钥、数字证书请求、数字证书、 PKCS7消息和PKCS8消息等。

pkcs7消息语法(crypto/pkcs7目录),主要实现了构造和解析PKCS7消息;

pkcs12个人证书格式(crypto/pckcs12目录),主要实现了pkcs12证书的构造和解析。

队列(crypto/pqueue目录),实现了队列数据结构,主要用于DTLS。

随机数(crypto/rand目录),实现了伪随机数生成,支持用户自定义随机数生成。

堆栈(crypto/stack目录),实现了堆栈数据结构。

线程支持(crypto/threads), OpenSSL支持多线程,但是用户必须实现相关接口。

文本数据库(crypto/txt_db目录)。

x509数字证书(crypto/x509目录和crypto/x509v3),包括数字证书申请、数字证书和CRL的构造、解析和签名验证等功能;

对称算法(crypto/aes、 crypto/bf、 crypto/cast、 ccrypto/omp和crypto/des等目录)。

非对称算法(crypto/dh、 crypto/dsa、 crypto/ec和crypto/ecdh)。

摘要算法(crypto/md2、 crypto/md4、 crypto/md5和crypto/sha)以及密钥交换/认证算法(crypto/dh 和crypto/krb5)。

(二)ssl库 ssl库所有源代码在ssl目录下,包括了sslv2、 sslv3、 tlsv1和DTLS的源代码。各个版本基本上都有客户端源码(* _clnt.c)、服务源码(* _srvr.c)、通用源码(* _both.c)、底层包源码( * _pkt.c)、方法源码(* _meth.c)以及协议相关的各种密钥计算源码(* _enc.c)等,都很有规律。 在这里插入图片描述 在这里插入图片描述

(三)工具源码 工具源码主要在crypto/apps目录下,默认编译时只编译成OpenSSL可执行文件。该命令包含了各种命令工具。此目录下的各个源码可以单独进行编译。 在这里插入图片描述

(四)范例源码 范例源码在demo目录下,另外engines目录给出了OpenSSL支持的几种硬件的engines源码,也可以作为engine编写参考。 在这里插入图片描述

(五)测试源码 测试源码主要在test目录下。 在这里插入图片描述

1.3、OpenSSL学习方法

1)建立学习环境 建立一个供调试的OpenSSL环境 linux或者其他平台。

2)学习OpenSSL的命令 通过OpenSSL命令的学习,对OpenSSL有基本的了解。

3)学习OpenSSL源代码并调试 对于OpenSSL函数的学习,主要查看OpenSSL自身是如何调用的,或者查看函数的实现。对于OpenSSL中只有实现而没有调用的函数,读者需要自己写源码或研究源代码去学习。主要需要学习的源代码有:

apps目录下的各个程序,对应于OpenSSL的各项命令;demos下的各种源代码;engines下的各种engine实现;test目录下的各种源代码。

4)学会使用 OpenSSL 的 asn.1 编解码 OpenSSL中很多函数和源码都涉及到asn1编解码,比如数字证书申请、数字证书、 crl、ocsp、 pkcs7、 pkcs8、 pkcs12等。

5)查找资料

Linux下主要用man就能查看OpenSSL命令和函数的帮助。Windows用户可用到www.openss.org去查看在线帮助文档,或者用linux下的命令man2html将帮助文档装换为html格式。用户也可以访问OpenSSL.cn论坛来学习OpenSSL。

二、OpenSSL常用功能

OpenSSL的功能非常丰富,本文主要介绍常用的功能,包括:哈希表、BIO、BASE64编解码、摘要算法(MD4、MD5、SHA1)、公钥算法RSA等。

2.1 哈希表

假设有10亿条数据: (1)如何快速查找一个数据是否存在?–bitmap (2)如何存储,快速查找是否存在?–布隆过滤器+哈希表

2.1.1 定义

在一般的数据结构如线性表和树中,记录在结构中的相对位置是与记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列的关键字比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立确定的对应关系,使每个关键字和结构中一个唯一的存储位置相对应。在查找时,只需根据这个对应关系找到给定值。这种对应关系既是哈希函数,按这个思想建立的表为哈希表。哈希表存在冲突现象:不同的关键字可能得到同一哈希地址。在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。

2.1.2 数据结构

OpenSSL函数使用哈希表来加快查询操作,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等。其源码在crypto/lhash目录下。 OpenSSL中的哈希表数据结构在lhash_local.h中定义如下:

struct lhash_node_st { void *data; //用于存放数据地址 struct lhash_node_st *next; //下一个数据地址 unsigned long hash; //数据哈希计算值 }; struct lhash_st { OPENSSL_LH_NODE **b; //指针数组用于存放所有的数据,数组中的每一个值为数据链表的头指针 OPENSSL_LH_COMPFUNC comp; //用于存放数据比较函数地址 OPENSSL_LH_HASHFUNC hash; //用于存放计算哈希值函数的地址 unsigned int num_nodes; //链表个数 unsigned int num_alloc_nodes; //为b分配空间的大小 unsigned int p; unsigned int pmax; unsigned long up_load; /* load times 256 */ unsigned long down_load; /* load times 256 */ unsigned long num_items; unsigned long num_expands; unsigned long num_expand_reallocs; unsigned long num_contracts; unsigned long num_contract_reallocs; TSAN_QUALIFIER unsigned long num_hash_calls; TSAN_QUALIFIER unsigned long num_comp_calls; unsigned long num_insert; unsigned long num_replace; unsigned long num_delete; unsigned long num_no_delete; TSAN_QUALIFIER unsigned long num_retrieve; TSAN_QUALIFIER unsigned long num_retrieve_miss; TSAN_QUALIFIER unsigned long num_hash_comps; int error; };

基本结构如下示图: 在这里插入图片描述

2.1.3 函数说明

1)创建hash LHASH *lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)

功能:生成hash表 说明:输入参数h为哈希函数, c为比较函数。这两个函数都是回调函数。 因为哈希表用于存放任意的数据结构,哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算。所以,用户必须提供这两个回调函数。

2)往哈希表中添加数据 void *lh_insert(LHASH *lh, void *data)

功能:往哈希表中添加数据。 说明:data为需要添加数据结构的指针地址。

3)遍历哈希数据 void lh_doall(LHASH *lh, LHASH_DOALL_FN_TYPE func)

功能:处理哈希表中的所有数据。 说明:func为外部提供的回调函数,本函数遍历所有存储在哈希表中的数据,每个数据被func处理。

4)检索 void *lh_retrieve(LHASH *lh, const void *data)

功能:查询数据。 说明:从哈希表中查询数据,data为i数据结构地址,此数据结构中必须提供关键项(这些关键项对应用户提供的哈希函数和比较函数)以供查询,如果查询成功,返回数据结构的地址,否则返回NULL。比如SSL握手中服务端查询以前存储的SESSION时,它需要提供其中关键的几项:

SSL_SESSION *ret=NULL,data; data.ssl_version=s->version; data.session_id_length=len; memcpy(data.session_id,session_id,len); ret=(SSL_SESSION *)lh_retrieve(s->ctx->sessions,&data);

5)删除 void *lh_delete(LHASH *lh, const void *data)

功能:删除散列表中的一个数据。 说明:data为数据结构指针。

6)释放 void lh_free(LHASH *lh)

功能:释放哈希表。

2.1.4 编程示例 #include #include #define NAME_LENGTH 32 typedef struct _Person { char name[NAME_LENGTH]; int high; char otherInfo[NAME_LENGTH]; } Person; static int person_cmp(const void *a, const void *b) { char *namea = ((Person*)a)->name; char *nameb = ((Person*)b)->name; return strcmp(namea, nameb); } void print_value(void *a) { Person *p = (Person*)a; printf("name: %s\n", p->name); printf("high: %d\n", p->high); printf("other info : %s\n", p->otherInfo); } int main() { _LHASH *h = lh_new(NULL, person_cmp); if (h == NULL) { printf("err.\n"); return -1; } Person p1 = {"Zhangsan", 170, "xxxx"}; Person p2 = {"Lisi", 175, "xxxx"}; Person p3 = {"Wangwu", 170, "xxxx"}; Person p4 = {"Zhaoliu", 170, "xxxx"}; lh_insert(h, &p1); lh_insert(h, &p2); lh_insert(h, &p3); lh_insert(h, &p4); lh_doall(h, print_value); printf("\n\n\n------------------------------\n\n\n"); void *data = lh_retrieve(h, (const char *)"Zhangsan"); if (data == NULL) { return -1; } print_value(data); lh_free(h); return 0; }

2.2 BIO 2.2.1 定义

OpenSSL抽象IO(I/O abstraction,即BIO)是OpenSSL对于io类型的抽象封装,包括:内存、文件、日志、标准输入输出、 socket( TCP/UDP)、加/解密、摘要和ssl通道等。 OpenSSL BIO通过回调函数为用户隐藏了底层实现细节,所有类型的bio的调用大体上是类似的。 BIO中的数据能从一个BIO传送到另外一个BIO或者是应用程序。

2.2.2 数据结构

BIO数据结构主要有2个,在crypto/bio.h(不同版本位置可能有差异)中定义如下: 1)BIO_METHOD

typedef struct bio_method_st{ int type; //具体BIO类型 const char *name; //具体BIO名字 int (*bwrite)(BIO *, const char *, int); //具体BIO写操作回调函数 int (*bread)(BIO *, char *, int); //具体BIO读操作回调函数 int (*bputs)(BIO *, const char *); //具体BIO中写入字符串回调函数 int (*bgets)(BIO *, char *, int); //具体BIO中读取字符串函数 long (*ctrl)(BIO *, int, long, void *); //具体BIO的控制回调函数 int (*create)(BIO *); //生成具体BIO回调函数 int (*destroy)(BIO *); //销毁具体BIO回调函数 long (*callback_ctrl)(BIO *, int, bio_info_cb *); //具体BIO控制回调函数,与ctrl回调函数不一样,该函数可由调用者(而不是实现者)来实现,然后通过BIO_set_callback等函数来设置 } BIO_METHOD;

2)BIO

struct bio_st { BIO_METHOD *method; /* bio, mode, argp, argi, argl, ret */ long (*callback)(struct bio_st *,int,const char *,int, long,long); char *cb_arg; /* first argument for the callback */ int init; int shutdown; int flags; /* extra storage */ int retry_reason; int num; void *ptr; struct bio_st *next_bio; /* used by filter BIOs */ struct bio_st *prev_bio; /* used by filter BIOs */ int references; nsigned long num_read; unsigned long num_write; CRYPTO_EX_DATA ex_data; };

结构体参数说明:

init:具体句柄初始化标记,初始化后为1。比如文件BIO中,通过BIO_set_fp关联一个文件指针时,该标记则置1; socket BIO中通过BIO_set_fd关联一个链接时设置该标记为1。shutdown: BIO关闭标记,当该值不为0时,释放资源;改值可以通过控制函数来设置。flags:有些 BIO 实现需要它来控制各个函数的行为。比如文件 BIO 默认该值为BIO_FLAGS_UPLINK,这时文件读操作调用 UP_fread 函数而不是调用 fread 函数。retry_reason:重试原因,主要用在socket和ssl BIO 的异步阻塞。比如socket bio中,遇到WSAEWOULDBLOCK错误时, OpenSSL告诉用户的操作需要重试。num:该值因具体BIO而异,比如socket BIO中num用来存放链接字。ptr:指针,具体bio有不同含义。比如文件BIO中它用来存放文件句柄; mem bio中它用来存放内存地址; connect bio中它用来存放BIO_CONNECT数据, accept bio中它用来存放BIO_ACCEPT数据。next_bio:下一个BIO地址, BIO数据可以从一个BIO传送到另一个BIO,该值指明了下一个BIO的地址。references:被引用数量。num_read: BIO中已读取的字节数。num_write: BIO中已写入的字节数。ex_data:用于存放额外数据。

2.2.3 函数说明

(一)BIO相关函数 BIO_new_file(生成新文件)和BIO_get_fd(设置网络链接)等。

(二)通用抽象函数 BIO_read和BIO_write,另外,有很多函数是由宏定义通过控制函数BIO_ctrl实现,比如BIO_set_nbio、BIO_get_fd和BIO_eof等等。

2.2.4 编程示例

(一)Memory BIO

#include #include int main() { BIO *b=NULL; int len=0; char *out=NULL; b=BIO_new(BIO_s_mem()); //生成一个mem类型的BIO len=BIO_write(b,"OpenSSL",7); //将字符串"OpenSSL"写入bio len=BIO_printf(b,"%s","zcp"); //将字符串"zcp"写入bio len=BIO_ctrl_pending(b); //得到缓冲区中待读取大小 out=(char *)OPENSSL_malloc(len); len=BIO_read(b,out,len); //将bio中的内容写入out缓冲区 OPENSSL_free(out); BIO_free(b); return 0; }

(二)Socket BIO 示例:获取本机的web服务信息

1)服务端

#include #include #include int main() { BIO *b=NULL,*c=NULL; int sock,ret,len; char *addr=NULL; char out[80]; sock=BIO_get_accept_socket("2323",0); b=BIO_new_socket(sock, BIO_NOCLOSE); ret=BIO_accept(sock,&addr); BIO_set_fd(b,ret,BIO_NOCLOSE); while(1) { memset(out,0,80); len=BIO_read(b,out,80); if(out[0]=='q') break; printf("%s",out); } BIO_free(b); return 0; }

2)客户端

#include int main() { BIO *cbio, *out; int len; char tmpbuf[1024]; cbio = BIO_new_connect("localhost:http"); //建立连接到本地web服务的BIO out = BIO_new_fp(stdout, BIO_NOCLOSE); //生成一个输出到屏幕的BIO if(BIO_do_connect(cbio)


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3