开源加密库Openssl 剖析&实战

开源加密库Openssl 剖析&实战一 OpenSSL 简介 1 1OpenSSL 简介在计算机网络上 OpenSSL 是一个开放源代码的软件库包 应用程序可以使用这个包来进行安全通信 避免窃听 同时确认另一端连接者的身份

大家好,欢迎来到IT知识分享网。

一、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目录下。包括如下内容:
在这里插入图片描述

  1. asn1 DER编码解码(crypto/asn1目录),包含了基本asn1对象的编解码以及数字证书请求、数字证书、 CRL撤销列表以及PKCS8等最基本的编解码函数。这些函数主要通过宏来实现。
  2. 抽象IO(BIO,crypto/bio目录),本目录下的函数对各种输入输出进行抽象,包括文件、内存、标准输入输出、 socket和SSL协议等。
  3. 大数运算(crypto/bn目录),本目录下的文件实现了各种大数运算。这些大数运算主要用于非对称算法中密钥生成以及各种加解密操作。另外还为用户提供了大量辅助函数,比如内存与大数之间的相互转换。
  4. 字符缓存操作(crypto/buffer目录)
  5. 配置文件读取(crypto/conf目录), OpenSSL主要的配置文件为OpenSSL.cnf。本目录下的函数实现了对这种格式配置文件的读取操作。
  6. DSO(动态共享对象,crypto/dso目录),本目录下的文件主要抽象了各种平台的动态库加载函数,为用户提供统一接口。
  7. 硬件引擎(crypto/engine目录),硬件引擎接口。用户如果要写自己的硬件引擎,必须实现它所规定的接口。
  8. 错误处理(crypto/err目录),当程序出现错误时, OpenSSL能以堆栈的形式显示各个错误。本目录下只有基本的错误处理接口,具体的的错误信息由各个模块提供。各个模块专门用于错误处理的文件一般为*_err…c文件。
  9. 对称算法、非对称算法及摘要算法封装(crypto/evp目录)。
  10. HMAC(crypto/hmac目录),实现了基于对称算法的MAC。
  11. hash表(crypto/lhash目录),实现了散列表数据结构。 OpenSSL中很多数据结构都是以散列表来存放的。比如配置信息、 ssl session和asn.1对象信息等。
  12. 数字证书在线认证(crypto/ocsp目录),实现了ocsp协议的编解码以及证书有效性计算等功能。
  13. PEM文件格式处理(crypto/pem),用于生成和读取各种PEM格式文件,包括各种密钥、数字证书请求、数字证书、 PKCS7消息和PKCS8消息等。
  14. pkcs7消息语法(crypto/pkcs7目录),主要实现了构造和解析PKCS7消息;
  15. pkcs12个人证书格式(crypto/pckcs12目录),主要实现了pkcs12证书的构造和解析。
  16. 队列(crypto/pqueue目录),实现了队列数据结构,主要用于DTLS。
  17. 随机数(crypto/rand目录),实现了伪随机数生成,支持用户自定义随机数生成。
  18. 堆栈(crypto/stack目录),实现了堆栈数据结构。
  19. 线程支持(crypto/threads), OpenSSL支持多线程,但是用户必须实现相关接口。
  20. 文本数据库(crypto/txt_db目录)。
  21. x509数字证书(crypto/x509目录和crypto/x509v3),包括数字证书申请、数字证书和CRL的构造、解析和签名验证等功能;
  22. 对称算法(crypto/aes、 crypto/bf、 crypto/cast、 ccrypto/omp和crypto/des等目录)。
  23. 非对称算法(crypto/dh、 crypto/dsa、 crypto/ec和crypto/ecdh)。
  24. 摘要算法(crypto/md2、 crypto/md4、 crypto/md5和crypto/sha)以及密钥交换/认证算法(crypto/dh 和crypto/krb5)。

1.3、OpenSSL学习方法

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

  • apps目录下的各个程序,对应于OpenSSL的各项命令;
  • demos下的各种源代码;
  • engines下的各种engine实现;
  • test目录下的各种源代码。
  • Linux下主要用man就能查看OpenSSL命令和函数的帮助。
  • Windows用户可用到www.openss.org去查看在线帮助文档,或者用linux下的命令man2html将帮助文档装换为html格式。
  • 用户也可以访问OpenSSL.cn论坛来学习OpenSSL。

二、OpenSSL常用功能

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

2.1 哈希表

2.1.1 定义

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

2.1.2 数据结构
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)

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); 

功能:释放哈希表。

2.1.4 编程示例
#include <stdio.h> #include <openssl/lhash.h> #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 定义
2.2.2 数据结构
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; 
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(设置网络链接)等。

2.2.4 编程示例

(一)Memory BIO

#include <stdio.h> #include <OpenSSL/bio.h> 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; } 

1)服务端

#include <stdio.h> #include <OpenSSL/bio.h> #include <string.h> 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 <OpenSSL/bio.h> 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) <= 0) { 
    fprintf(stderr, "Error connecting to server\n"); } BIO_puts(cbio, "GET / HTTP/1.0\n\n"); //通过BIO发送数据 for(;;) { 
    len = BIO_read(cbio, tmpbuf, 1024); //将web服务响应的数据写入缓存,此函数循环调用直到无数据。 if(len <= 0) break; BIO_write(out, tmpbuf, len); //通过BIO打印收到的数据 } BIO_free(cbio); BIO_free(out); return 0; } 
#include <stdio.h> #include <OpenSSL/bio.h> int main() { 
    BIO *b=NULL; int len=0,outlen=0; char *out=NULL; b=BIO_new_file("bf.txt","w"); len=BIO_write(b,"OpenSSL",4); len=BIO_printf(b,"%s","zcp"); BIO_free(b); b=BIO_new_file("bf.txt","r"); len=BIO_pending(b); len=50; out=(char *)OPENSSL_malloc(len); len=1; while(len>0) { 
    len=BIO_read(b,out+outlen,1); outlen+=len; } BIO_free(b); free(out); return 0; } 
#include <OpenSSL/bio.h> #include <OpenSSL/evp.h> int main() { 
    BIO *bmd=NULL,*b=NULL; const EVP_MD *md=EVP_md5(); int len; char tmp[1024]; bmd=BIO_new(BIO_f_md()); //生成一个md BIO BIO_set_md(bmd,md); //设置md BIO 为md5 BIO b= BIO_new(BIO_s_null()); //生成一个 null BIO b=BIO_push(bmd,b); //构造BIO 链,md5 BIO在顶部 len=BIO_write(b,"OpenSSL",7); //将字符串送入BIO做摘要 len=BIO_gets(b,tmp,1024); //将摘要结果写入tmp缓冲区 BIO_free(b); return 0; } 
#include <string.h> #include <OpenSSL/bio.h> #include <OpenSSL/evp.h> int main() { 
    /* 加密 */ BIO *bc=NULL,*b=NULL; const EVP_CIPHER *c=EVP_des_ecb(); int len,i; char tmp[1024]; unsigned char key[8],iv[8]; for(i=0;i<8;i++) { 
    memset(&key[i],i+1,1); memset(&iv[i],i+1,1); } bc=BIO_new(BIO_f_cipher()); BIO_set_cipher(bc,c,key,iv,1); //设置加密BI b= BIO_new(BIO_s_null()); b=BIO_push(bc,b); len=BIO_write(b,"OpenSSL",7); len=BIO_read(b,tmp,1024); //获取加/解密结果 BIO_free(b); /* 解密 */ BIO *bdec=NULL,*bd=NULL; const EVP_CIPHER *cd=EVP_des_ecb(); bdec=BIO_new(BIO_f_cipher()); BIO_set_cipher(bdec,cd,key,iv,0); //设置解密BIO bd= BIO_new(BIO_s_null()); bd=BIO_push(bdec,bd); len=BIO_write(bdec,tmp,len); len=BIO_read(bdec,tmp,1024); //获取加/解密结果 BIO_free(bdec); return 0; } 
#include <OpenSSL/bio.h> #include <OpenSSL/ssl.h> int main() { 
    BIO *sbio, *out; int len; char tmpbuf[1024]; SSL_CTX *ctx; SSL *ssl; SSLeay_add_ssl_algorithms(); OpenSSL_add_all_algorithms(); ctx = SSL_CTX_new(SSLv3_client_method()); sbio = BIO_new_ssl_connect(ctx); BIO_get_ssl(sbio, &ssl); if(!ssl) { 
    fprintf(stderr, "Can not locate SSL pointer\n"); return 0; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); BIO_set_conn_hostname(sbio, "mybank.icbc.com.cn:https"); out = BIO_new_fp(stdout, BIO_NOCLOSE); BIO_printf(out,”链接中….\n”); if(BIO_do_connect(sbio) <= 0) { 
    fprintf(stderr, "Error connecting to server\n"); return 0; } if(BIO_do_handshake(sbio) <= 0) { 
    fprintf(stderr, "Error establishing SSL connection\n"); return 0; } BIO_puts(sbio, "GET / HTTP/1.0\n\n"); for(;;) { 
    len = BIO_read(sbio, tmpbuf, 1024); if(len <= 0) break; BIO_write(out, tmpbuf, len); } BIO_free_all(sbio); BIO_free(out); return 0; } 

2.3 BASE64编解码

BASE64编码是一种常用的将十六进制数据转换为可见字符的编码。与ASCII码相比,它占用的空间较小。BASE64编码在rfc3548种定义。

2.3.1 BASE编码原理

BASE64解码是编码的逆过程。解码时,将BASE64编码根据表展开,根据有几个等号去掉结尾的几个00,然后每8bit恢复即可。

2.3.2 BASE函数
  • EVP_EncodeInit 编码前初始化上下文。
  • EVP_EncodeUpdate 进行BASE64编码,本函数可多次调用。
  • EVP_EncodeFinal 进行BASE64编码,并输出结果。
  • EVP_EncodeBlock 进行BASE64编码。

2)解码函数

  • EVP_DecodeInit 解码前初始化上下文。
  • EVP_DecodeUpdate BASE64解码,本函数可多次调用。
  • EVP_DecodeFinal BASE64解码,并输出结果。
  • EVP_DecodeBlock BASE64解码,可单独调用。
2.3.3 BASE编程示例
#include <string.h> #include <openssl/evp.h> int main() { 
    unsigned char in[100], base64[150], decode[100]; EVP_ENCODE_CTX ectx; EVP_EncodeInit(&ectx); int i = 0; for (i = 0;i < 100;i ++) { 
    in[i] = i; } int outl, inl = 100; EVP_EncodeUpdate(&ectx, base64, &outl, in, inl); int total = outl; EVP_EncodeFinal(&ectx, base64+total, &outl); printf("%s\n", base64); return 0; } 

2.4 摘要算法(MD4、MD5、SHA1)

摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证

摘要是安全协议中不可或却的要素,特别是身份认证与签名。用户需要对数据进行签名时,不可能对大的数据进行运算,这样会严重影响性能。如果只对摘要结果进行计算,则会提供运算速度。

如今常用的“消息摘要”算法经历了多年验证发展而保留下来的算法已经不多,这其中包括MD2、 MD4、 MD5、SHA、 SHA-1/256/383/512等。常用的摘要算法主要有MD5和SHA1。 MD5的输出结果为16字节, SHA1的输出结果为20字节。

2.4.1 OpenSSL摘要实现说明

OpenSSL摘要实现的源码位于crypto目录下的各个子目录下,如下所示:

  • crypto/ripemd: ripemd摘要实现(包括汇编代码)及其测试程序;
  • crypto/md2: md2摘要实现及其测试程序;
  • crypto/mdc2: mdc2摘要实现及其测试程序;
  • crypto/md4: md4摘要实现及其测试程序;
  • crypto/md5: md5摘要实现及其测试程序;
  • crypto/sha: sha、 sha1、 sha256、 sha512实现及其测试程序(包含汇编源码)。

上述各种摘要源码在OpenSSL中都是底层的函数,相对独立,能单独提取出来,而不必包含OpenSSL的libcrypto库(因为这个库一般比较大)。

2.4.2 函数说明
2.4.3 编码示例
int main() { 
    unsigned char in[] = "EVP_ENCODE_CTX ectx, dctx; \ unsigned char in[100], base64[150], decode[100]; \ EVP_EncodeInit(&ectx); \ int i = 0; \ for (i = 0;i < 100;i ++) { \ in[i] = i;\ } "; unsigned char out[128] = { 
   0}; int n = strlen(in); int i = 0; MD4(in, n, out); printf("MD4 result: \n"); // 16 byte  for (i = 0;i < 16;i ++) { 
    printf("%x", out[i]); } printf("\n"); MD5(in, n, out); printf("MD5 result: \n"); // 16 byte  for (i = 0;i < 16;i ++) { 
    printf("%x", out[i]); } printf("\n"); SHA(in, n, out); printf("SHA result: \n"); // 20 byte  for (i = 0;i < 16;i ++) { 
    printf("%x", out[i]); } printf("\n"); SHA1(in, n, out); printf("SHA1 result: \n"); // 20 byte  for (i = 0;i < 20;i ++) { 
    printf("%x", out[i]); } printf("\n"); SHA256(in, n, out); printf("SHA256 result: \n"); // 32 byte  for (i = 0;i < 32;i ++) { 
    printf("%x", out[i]); } printf("\n"); SHA512(in, n, out); printf("SHA512 result: \n"); // 64 byte  for (i = 0;i < 64;i ++) { 
    printf("%x", out[i]); } printf("\n"); } 

2.5 公钥算法RSA

2.5.1 RSA简介

RSA算法是一个广泛使用的公钥算法。其密钥包括公钥和私钥。它能用于数字签名、身份认证以及密钥交换。 RSA密钥长度一般使用1024位或者更高。 RSA密钥信息主要包括:

  • n:模数
  • e:公钥指数
  • d:私钥指数
  • p:最初的大素数
  • q:最初的大素数
  • dmp1:e*dmp1=1(mod(p-1))
  • dmq1:e*dmp1=1(mod(q-1))
  • iqmp:e*iqmp=1(mod p)
2.5.2 OpenSSL的RSA实现

OpenSSL的RSA实现源码在crypto/rsa目录下。它实现了RSA PKCS1标准。主要源码结构如下:

  • rsa.h:定义 RSA 数据结构以及 RSA_METHOD,定义了 RSA 的各种函数。
  • rsa_asn1.c:实现了RSA密钥的DER编码和解码,包括公钥和私钥。
  • rsa_chk.c:RSA密钥检查。
  • rsa_eay.c:OpenSSL实现的一种RSA_METHOD,作为其默认的一种RSA计算实现方式。此文件未实现rsa_sign、rsa_verify和rsa_keygen回调函数。
  • rsa_err.c:RSA错误处理。
  • rsa_gen.c:RSA密钥生成,如果RSA_METHOD中的rsa_keygen回调函数不为空,则调用它,否则调用其内部实现。
  • rsa_lib.c:主要实现了RSA运算的四个函数(公钥/私钥,加密/解密),它们都调用了RSA_METHOD中相应都回调函数。
  • rsa_none.c:实现了一种填充和去填充。
  • rsa_null.c:实现了一种空的RSA_METHOD。
  • rsa_oaep.c:实现了oaep填充与去填充。
  • rsa_pk1.c:实现了pkcs1填充与去填充。
  • rsa_sign.c:实现了RSA的签名和验签。
  • rsa_ssl.c:实现了ssl填充。
  • rsa_x931.c:实现了一种填充和去填充。
2.5.3 RSA 签名与验证过程

(一)签名过程
1)对用户数据进行摘要;
2)构造X509_SIG结构并DER编码,其中包括了摘要算法以及摘要结果。
3)对2)的结果进行填充,填满RSA密钥长度字节数。比如1024位RSA密钥必须填满128字节。具体的填充方式由用户指定。
4)对3)的结果用RSA私钥加密。



RSA_eay_private_encrypt函数实现了3)和4)过程。

RSA_eay_public_decrypt实现了1)和2)过程。

2.5.4 RSA数据结构

RSA主要数据结构定义在crypto/rsa/rsa.h中。

(一)RSA_METHOD

struct rsa_meth_st { 
    const char *name; int (*rsa_pub_enc)(int flen,const unsigned char *from,unsigned char*to,RSA *rsa,int padding); int (*rsa_pub_dec)(int flen,const unsigned char *from,unsigned char*to,RSA *rsa,int padding); int (*rsa_priv_enc)(int flen,const unsigned char *from,unsigned char*to,RSA *rsa,int padding); int (*rsa_priv_dec)(int flen,const unsigned char *from,unsigned char*to,RSA *rsa,int padding); /* 其他函数 */ int (*rsa_sign)(int type,const unsigned char *m, unsigned int m_length,unsigned char *sigret, unsigned int *siglen, const RSA *rsa); int (*rsa_verify)(int dtype,const unsigned char *m, unsigned int m_length,unsigned char *sigbuf, unsigned int siglen, const RSA *rsa); int (*rsa_keygen)(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); }; 

参数说明:

  • name: RSA_METHOD名称;
  • rsa_pub_enc:公钥加密函数, padding为其填充方式,输入数据不能太长,否则无法填充;
  • rsa_pub_dec:公钥解密函数, padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数;
  • rsa_priv_enc:私钥加密函数, padding为其填充方式,输入数据长度不能太长,否则无法填充;
  • rsa_priv_dec:私钥解密函数, padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数;
  • rsa_sign:签名函数;
  • rsa_verify:验签函数;
  • rsa_keygen: RSA密钥对生成函数。

用户可实现自己的RSA_METHOD来替换OpenSSL提供的默认方法。

struct rsa_st { 
    /* 其他 */ const RSA_METHOD *meth; ENGINE *engine; BIGNUM *n; BIGNUM *e; BIGNUM *d; BIGNUM *p; BIGNUM *q; BIGNUM *dmp1; BIGNUM *dmq1; BIGNUM *iqmp; CRYPTO_EX_DATA ex_data; int references; /* 其他数据项 */ }; 

参数说明:

  • meth: RSA_METHOD结构,指明了本RSA密钥的各种运算函数地址;
  • engine:硬件引擎;
  • n, e, d, p, q, dmp1, dmq1, iqmp: RSA密钥的各个值;
  • ex_data:扩展数据结构,用于存放用户数据;
  • references: RSA结构引用数。
2.5.5 函数说明

1) RSA_check_key:检查RSA密钥。

2) RSA_new:生成一个RSA密钥结构,并采用默认的rsa_pkcs1_eay_meth RSA_METHOD方法。

3) RSA_free:释放RSA结构。

  1. RSA *RSA_generate_key(int bits, unsigned long e_value,void (*callback)(int,int,void *), void *cb_arg) :生成RSA密钥, bits是模数比特数, e_value是公钥指数e, callback回调函数由用户实现,用于干预密钥生成过程中的一些运算,可为空。

5) RSA_get_default_method:获取默认的RSA_METHOD,为rsa_pkcs1_eay_meth。

6) RSA_get_ex_data:获取扩展数据。

7) RSA_get_method:获取RSA结构的RSA_METHOD。

10) int RSA_print(BIO *bp, const RSA *x, int off):将RSA信息输出到BIO中, off为输出信息在BIO中的偏移量,比如是屏幕BIO,则表示打印信息的位置离左边屏幕边缘的距离。

11) int DSA_print_fp(FILE *fp, const DSA *x, int off):将RSA信息输出到FILE中, off为输出偏移量。

12) RSA_public_decrypt:RSA公钥解密。

13) RSA_public_encrypt:RSA公钥加密。

14) RSA_set_default_method/ RSA_set_method:设置RSA结构中的method,当用户实现了一个RSA_METHOD时,调用此函数来设置,使RSA运算采用用户的方法。

15) RSA_set_ex_data:设置扩展数据。

16) RSA_sign:RSA签名。

17) RSA_sign_ASN1_OCTET_STRING:另外一种RSA签名,不涉及摘要算法,它将输入数据作为ASN1_OCTET_STRING进行DER编码,然后直接调用RSA_private_encrypt进行计算。

18) RSA_size:获取RSA密钥长度字节数。

19) RSA_up_ref:给RSA密钥增加一个引用。

20) RSA_verify:RSA验证。

21) RSA_verify_ASN1_OCTET_STRING:另一种RSA验证,不涉及摘要算法,与RSA_sign_ASN1_OCTET_STRING对应。

22) RSAPrivateKey_asn1_meth:获取 RSA 私钥的 ASN1_METHOD,包括 i2d、 d2i、 new 和 free 函数地址。

23) RSAPrivateKey_dup:复制RSA私钥。

24) RSAPublicKey_dup:复制RSA公钥。

2.5.6 编程示例

(一)密钥生成

#include <OpenSSL/rsa.h> int main() { 
    RSA *r; int bits=512,ret; unsigned long e=RSA_3; BIGNUM *bne; r=RSA_generate_key(bits,e,NULL,NULL); RSA_print_fp(stdout,r,11); RSA_free(r); bne=BN_new(); ret=BN_set_word(bne,e); r=RSA_new(); ret=RSA_generate_key_ex(r,bits,bne,NULL); if(ret!=1) { 
    printf("RSA_generate_key_ex err!\n"); return -1; } RSA_free(r); return 0; } 
#include <OpenSSL/rsa.h> #include <OpenSSL/sha.h> int main() { 
    RSA *r; int bits=1024,ret,len,flen,padding,i; unsigned long e=RSA_3; BIGNUM *bne; unsigned char *key,*p; BIO *b; unsigned char from[500],to[500],out[500]; bne=BN_new(); ret=BN_set_word(bne,e); r=RSA_new(); ret=RSA_generate_key_ex(r,bits,bne,NULL); if(ret!=1) { 
    printf("RSA_generate_key_ex err!\n"); return -1; } /* 私钥i2d */ b=BIO_new(BIO_s_mem()); ret=i2d_RSAPrivateKey_bio(b,r); key=malloc(1024); len=BIO_read(b,key,1024); BIO_free(b); b=BIO_new_file("rsa.key","w"); ret=i2d_RSAPrivateKey_bio(b,r); BIO_free(b); /* 私钥d2i */ /* 公钥i2d */ /* 公钥d2i */ /* 私钥加密 */ flen=RSA_size(r); printf("please select private enc padding : \n"); printf("1.RSA_PKCS1_PADDING\n"); printf("3.RSA_NO_PADDING\n"); printf("5.RSA_X931_PADDING\n"); scanf("%d",&padding); if(padding==RSA_PKCS1_PADDING) flen-=11; else if(padding==RSA_X931_PADDING) flen-=2; else if(padding==RSA_NO_PADDING) flen=flen; else { 
    printf("rsa not surport !\n"); return -1; } for(i=0;i<flen;i++) memset(&from[i],i,1); len=RSA_private_encrypt(flen,from,to,r,padding); if(len<=0) { 
    printf("RSA_private_encrypt err!\n"); return -1; } len=RSA_public_decrypt(len,to,out,r,padding); if(len<=0) { 
    printf("RSA_public_decrypt err!\n"); return -1; } if(memcmp(from,out,flen)) { 
    printf("err!\n"); return -1; } /* */ printf("please select public enc padding : \n"); printf("1.RSA_PKCS1_PADDING\n"); printf("2.RSA_SSLV23_PADDING\n"); printf("3.RSA_NO_PADDING\n"); printf("4.RSA_PKCS1_OAEP_PADDING\n"); scanf("%d",&padding); flen=RSA_size(r); if(padding==RSA_PKCS1_PADDING) flen-=11; else if(padding==RSA_SSLV23_PADDING) flen-=11; else if(padding==RSA_X931_PADDING) flen-=2; else if(padding==RSA_NO_PADDING) flen=flen; else if(padding==RSA_PKCS1_OAEP_PADDING) flen=flen-2 * SHA_DIGEST_LENGTH-2 ; else { 
    printf("rsa not surport !\n"); return -1; } for(i=0;i<flen;i++) memset(&from[i],i+1,1); len=RSA_public_encrypt(flen,from,to,r,padding); if(len<=0) { 
    printf("RSA_public_encrypt err!\n"); return -1; } len=RSA_private_decrypt(len,to,out,r,padding); if(len<=0) { 
    printf("RSA_private_decrypt err!\n"); return -1; } if(memcmp(from,out,flen)) { 
    printf("err!\n"); return -1; } printf("test ok!\n"); RSA_free(r); return 0; } 
#include <string.h> #include <OpenSSL/objects.h> #include <OpenSSL/rsa.h> int main() { 
    int ret; RSA *r; int i,bits=1024,signlen,datalen,alg,nid; unsigned long e=RSA_3; BIGNUM *bne; unsigned char data[100],signret[200]; bne=BN_new(); ret=BN_set_word(bne,e); r=RSA_new(); ret=RSA_generate_key_ex(r,bits,bne,NULL); if(ret!=1) { 
    printf("RSA_generate_key_ex err!\n"); return -1; } for(i=0;i<100;i++) memset(&data[i],i+1,1); printf("please select digest alg: \n"); printf("1.NID_md5\n"); printf("2.NID_sha\n"); printf("3.NID_sha1\n"); printf("4.NID_md5_sha1\n"); scanf("%d",&alg); if(alg==1) { 
    datalen=55; nid=NID_md5; } else if(alg==2) { 
    datalen=55; nid=NID_sha; } else if(alg==3) { 
    datalen=55; nid=NID_sha1; } else if(alg==4) { 
    datalen=36; nid=NID_md5_sha1; } ret=RSA_sign(nid,data,datalen,signret,&signlen,r); if(ret!=1) { 
    printf("RSA_sign err!\n"); RSA_free(r); return -1; } ret=RSA_verify(nid,data,datalen,signret,signlen,r); if(ret!=1) { 
    printf("RSA_verify err!\n"); RSA_free(r); return -1; } RSA_free(r); printf("test ok!\n"); return 0; } 

注意:本示例并不是真正的数据签名示例,因为没有做摘要计算。

ret=RSA_sign(nid,data,datalen,signret,&signlen,r)将需要运算的数据放入X509_ALGOR数据结构并将其DER编码,对编码结果做RSA_PKCS1_PADDING再进行私钥加密。

被签名数据应该是摘要之后的数据,而本例没有先做摘要,直接将数据拿去做运算。因此datalen不能太长,要保证RSA_PKCS1_PADDING私钥加密运算时输入数据的长度限制。ret=RSA_verify(nid,data,datalen,signret,signlen,r)用来验证签名。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/139829.html

(0)
上一篇 2025-06-01 19:10
下一篇 2025-06-01 19:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信