openssl简单介绍
openssl是一个功能丰富且自包括的开源安全工具箱。它提供的主要功能有:SSL协议实现(包括SSLv2、SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。
openssl採用C语言作为开发语言,这使得它具有优秀的跨平台性能。openssl支持Linux、UNIX、windows、Mac等平台。openssl眼下最新的版本号是openssl-1.0.1i。
很多其它的相关知识请參阅铺天盖地的各种官方非官方文档。今天来实现如何将一个新算法加入进openssl中。
问题描写叙述
openssl中尽管集成了非常多经典算法,对称算法、非对称算法、摘要算法等等,但有时候我们须要用到自己定义的算法,比方要实现一个自己定义的摘要算法,须要借助openssl带的大数库来实现,同一时候又希望将自己实现的算法封装到openssl里面,以便统一调用或者方便项目改造和替换。最具现实意义的案例就是国密SM2等的改造。本例将用国密中的摘要算法SM3作为加入对象,在windows以下进行编译调试,仅描写叙述加入步骤,所以本文的前提是已经完毕了基于openssl大数库的SM3的c语言实现。本文仅供依葫芦画瓢,要理解原理请自行查阅别的资料或者自己解读源代码,或者等我的下一篇文章。
详细步骤
一、准备工作
1、 下载openssl源代码,openssl眼下最新的版本号是openssl-1.0.1i。下载地址:
2、 sm3的c语言实现。
3、 windows下perl、vc的安装配置。
4、 熟悉openssl的编译过程。
二、SM3代码改造【这个步骤涉及到的文件所有新建,存放文件夹关系如图】
1、 涉及到的文件7个:
1、 【新建文件】sm3.h
这是最重要的一个头文件,里面定义了算法可以导出的算法,也就是里面声明的每个函数最后都会生成相应的libeay.num(将在第五章中详细讲),可以导出到dll中供外部调用。几个基本的声明例如以下:
定义sm3的摘要长度:
#define SM3_DIGEST_LENGTH 32 /* sm3 摘要长度为256位 32字节,md5的摘要长度128位 not sure*/
定义SM3_CTX,这个结构事实上就是相应evp封装中EVP_MD_CTX的md_data:
typedef struct SM3state_st { unsigned long long total_length; unsigned char message_buffer[64]; size_t message_buffer_position; size_t V_i[8]; size_t V_i_1[8]; size_t T_j[64]; } SM3_CTX;
声明函数,包含init、update、final,这几个函数会在封装的时候被内部调用,当然在dll中导出了也能够外部调用:
int SM3_Init(SM3_CTX*c); intSM3_Update(SM3_CTX *c, const void *data, size_t len); intSM3_Final(unsigned char *md, SM3_CTX *c); unsignedchar *SM3(unsigned char *d, size_t n, unsigned char *md);
2、 【新建文件】sm3_locl.h
这个头文件里包括一些要用到的宏定义。主要定义了本算法实现中须要用到的内部函数,不能被导出到dll中供外部调用。
宏定义:
#ifndefROTATE #defineROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) #endif #define EE(b,c,d) ((b & c) | ((~ b) & d)) #define FF(b,c,d) ((((c)^ (d)) & (b)) ^ (d)) #define GG(b,c,d) (((b)& (c)) | ((b) & (d)) | ((c) & (d))) #define HH(b,c,d) ((b)^ (c) ^ (d)) #define HH1(a) (a^ (ROTATE(a, 9)) ^ (ROTATE(a, 17))) #define HH2(a) (a^ (ROTATE(a, 15)) ^ (ROTATE(a, 23))) 内部函数: voidinit_T_j(size_t *T_j); voidinit_V_i(size_t *V_i); voidCF(size_t *T_j,size_t *V_i, unsigned char *B_i, size_t *V_i_1);
3、 【新建文件】sm3_dgst.c
实现sm3.h和sm3_locl中声明的函数,除了函数SM3(将在sm3_one.c中实现)。
4、 【新建文件】sm3_one.c
实现sm3.h中声明的函数SM3。
5、 【新建文件】sm3.c
实现对文件的摘要,如用在openssl作为命令行工具,文件名称和摘要算法名作为选项參数实现使用算法对文件进行摘要。
6、 【新建文件】sm3test.c
算法的test文件。
7、 【新建文件】Makefile
编译的Makefile文件。里面描写叙述在编译的时候以上的几个文件的角色、编译过程、依赖关系等。
三、EVP封装相关代码
1、 涉及到的文件4个:
2、 【手动改动】evp.h
evp在openssl里面的主要做的是封装的事情,因此首先在evp.h头文件里加入新算法sm3:#ifndef OPENSSL_NO_SM3 const EVP_MD *EVP_sm3(void); #endif
说明:EVP_sm3()函数将返回返回一个sm3的EVP_MD的结构,这个结构以及EVP_sm3()函数都在m_sm3.c中定义。
3、 【新建文件】m_sm3.c
这是放在EVP文件夹中关于新算法的一个关键文件,定义sm3的EVP_MD结构,EVP_MD结构原型在evp.h中:
sm3的EVP_MD结构:
static const EVP_MD sm3_md= { NID_sm3,//这个须要定义了OID之后生成,这个步骤在第四章。 NID_sm3WithRSAEncryption,//这个地方sm3WithRSA仅仅做演示样例 SM3_DIGEST_LENGTH,//SM3的摘要长度,在sm3.h中定义 0, //flag init, //结构中的init函数,指向自己声明在sm3.h中的SM3_Init update,//同上 final,//同上 NULL, NULL, EVP_PKEY_RSA_method,//同上,仅仅做演示样例 SM3_CBLOCK, //在sm3.h中声明 sizeof(EVP_MD *)+sizeof(SM3_CTX), };
4、 【手动改动】c_alld.c
openssl的算法封装之后使用时须要载入算法,实现SM3的加入:
#ifndefOPENSSL_NO_SM3 EVP_add_digest(EVP_sm3()); #endif
5、 【手动改动】Makefile
将编译时候的LIBSRC、LIBOBJ中加入SM3新算法,而且加入sm3各种头文件的依赖关系。
四、OID生成
1、 涉及到的文件7个:
2、 【手动改动】objects.txt
加入sm3的OID,特别说明:此处仅作演示样例,sm3的实际OID是:
rsadsi 2 12 :SM3 : sm3
加入sm3WithRSAEncryption,这个是相应PKCS1的,仅仅是为了让sm3的EVP_MD结构有值填充,实际加入sm3的本意不用于RSA,这里仅作演示样例。
pkcs1 15 : RSA-SM3 : sm3WithRSAEncryption
3、 【运行命令自己主动更新】obj_mac.h和obj_mac.num
打开cmd,切换到文件夹下源代码的\crypto\objects\所在的文件夹下,
运行命令:perl objects.pl objects.txt obj_mac.num obj_mac.h
obj_mac.h和obj_mac.num这两个文件都会由于objects.txt的改动而更新。添加的内容如:
4、 【手动改动】objects.h
将obj_mac.h中新增的内容同步到objects.h中。(事实上这一步不做也没有影响,由于之后不用这个文件来生成obj_dat.h。objects.h的内容没有obj_mac.h的内容全面,千万不能用objects.h来obj_dat.h,一定要用obj_mac.h来生成才是正确的。切记,血的教训!)
5、 【运行命令自己主动更新】obj_dat.h
运行命令:perl obj_dat.pl obj_mac.h obj_dat.h
将会自己主动更新obj_dat.h这个文件,新增的内容例如以下:
注意看图上最后两排
Line 4672: 921, /* OBJ_sm3 1 2 840 113549 2 12 */ Line 4843: 920, /* OBJ_sm3WithRSAEncryption 1 2 840 113549 1 1 15 */
最后带的一串数字本来是算法的OID,由于上面第2步的objects.txt中是乱填的,所以这个不是真实的OID。仅作演示样例。
6、 【手动改动】obj_xref.txt
加入:sm3WithRSAEncryption sm3 rsaEncryption
7、 【运行命令自己主动更新】obj_xref.h
运行命令:perl objxref.pl obj_xref.txt obj_xref.h
obj_xref.h更新后的加入的内容如:
五、make相关设置
1、 涉及到的文件\util\以下5个:
2、 【手动改动】mkfiles.pl
加入:
"crypto/sm3",
加入上包括sm3的算法源代码的文件夹,这个文件夹中包括sm3的Makefile文件。之后就会依照Makefile进行编译。
3、 【手动改动】mkdef.pl
加入内容如图:
加上新算法的头文件等,能够在dll中导出算法函数。
4、 【运行命令自己主动更新】libeay.num
运行命令:perl util/mkdef.pl crypto update
Libeay.num更新后添加的内容如:
5、 【手动改动】mk1mf.pl
加入内容如:
6、 【手动改动】sp-diff.pl
加入内容如:
7、 涉及到的文件\crypto\以下2个:
8、 【手动改动】crypto-lib.com
加入内容如:
9、 【手动改动】install-crypto.com
加入内容如:
10、 涉及到的文件根文件夹\以下5个:
11、 【手动改动】
加入内容如图:Makefile、Makefile.bak、Makefile.org、makevms.com、INSTALL.VMS
到这里,源代码须要加入和改动的就结束了,以下将介绍编译和測试。
六、编译
1、 在cmd下,首先设置VC环境,运行VC文件夹下的vcvars32.BAT文件,如:
2、 cmd下进入openssl源代码所在的文件夹,依次运行命令【编32位,假设64位可能缺ml64.exe】:
1)运行命令:perl Configure VC-WIN32 no-asm
2) 运行命令:ms\do_ms
3)运行命令:nmake-f ms\ntdll.mak:这个是动态库:假设编译成功,最后的输出都在out32dll文件夹下: 包含可运行文件 、两个dll和两个lib文件【nmake -fms\nt.mak
这是静态库的编译命令,输出在out32文件夹下】。编译成功如图:
4) 运行命令:nmake -fms\ntdll.mak test
5)运行命令:nmake -f ms\ntdll.mak install
七、測试
1、 API编程測试,在vs建project,将openssl文件夹下生成的out32dll文件夹下的libeay32.dll和libeay32.lib到project。
2、 设置project的附加库和附加包括文件夹:
右键project->属性-> C/C++ ->常规->附加包括文件夹
右键project->属性-> 链接器 ->常规->附加库文件夹
3、 sm3test.c源代码如:
sm3test.c
#include <stdio.h> #include <openssl/evp.h> #include <openssl/sm3.h> #pragma comment(lib, "libeay32.lib") static size_t hash[8] = {0}; void out_hex(size_t *list1) { size_t i = 0; for (i = 0; i < 8; i++) { printf("%08x ", list1[i]); } printf("\r\n"); } main(int argc, char *argv[]) { EVP_MD_CTX mdctx; const EVP_MD *md; char mess1[] = "abc"; char mess2[] = "abc"; unsigned char md_value[EVP_MAX_MD_SIZE]; int md_len, i; //使EVP_Digest系列函数支持全部有效的信息摘要算法 OpenSSL_add_all_digests(); argv[1] = "sm3"; if(!argv[1]) { printf("Usage: mdtest digestname\n"); exit(1); } //依据输入的信息摘要函数的名字得到对应的EVP_MD算法结构 md = EVP_get_digestbyname(argv[1]); //md = EVP_sm3(); if(!md) { printf("Unknown message digest %s\n", argv[1]); exit(1); } //初始化信息摘要结构mdctx,这在调用EVP_DigestInit_ex函数的时候是必须的。 EVP_MD_CTX_init(&mdctx); //使用md的算法结构设置mdctx结构,impl为NULL,即使用缺省实现的算法(openssl本身提供的信息摘要算法) EVP_DigestInit_ex(&mdctx, md, NULL); //開始真正进行信息摘要运算,能够多次调用该函数,处理很多其它的数据,这里仅仅调用了两次 EVP_DigestUpdate(&mdctx, mess1, strlen(mess1)); //EVP_DigestUpdate(&mdctx, mess2, strlen(mess2)); //完毕信息摘要计算过程,将完毕的摘要信息存储在md_value里面,长度信息存储在md_len里面 EVP_DigestFinal_ex(&mdctx, md_value, &md_len); //使用该函数释放mdctx占用的资源,假设使用_ex系列函数,这是必须调用的。 EVP_MD_CTX_cleanup(&mdctx); printf("Digest is: "); for(i = 0; i < md_len; i++) printf("%02x", md_value[i]); printf("\n"); //SM3("abc",3,hash); //out_hex(hash); system("pause"); } int main1(int argc, char* argv[]) { SM3_CTX *c = (SM3_CTX*)malloc(sizeof(SM3_CTX)); SM3_Init(c); //SM3_Final_dword(hash, c); SM3_Update(c, "abc", 3); SM3_Final(hash, c); out_hex(hash); //66c7f0f4 62eeedd9 d1f2d46b dc10e4e2 4167c487 5cf2f7a2 297da02b 8f4ba8e0 SM3_Init(c); SM3_Update(c, "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", 64); /*for (i = 0; i<16; i++){ SM3_Update(c, "abcd", 4); }*/ SM3_Final(hash,c); out_hex(hash); //debe9ff9 2275b8a1 38604889 c18e5a4d 6fdb70e5 387e5765 293dcba3 9c0c5732 //SM3("abc",3,hash); //out_hex(hash); system("pause"); return 0; }
4、 sm3test.c执行使用sm3算法对“abc”进行摘要后的结果如图:
和国密标准的附录演示样例一致:
八、总结
本例以摘要算法为例,详细介绍了一下通过直接加入源代码的方式如何在openssl里面加入新算法,而非engine方式。当然假设觉得engine方式更方便,则请忽略本文。本文仅仅是在描写叙述怎么做,可是没有讲为什么。由于比如openssl本身的封装机制是须要解读源代码才干更好地理解上述文中为什么须要做那些步骤,由于这些步骤已经够复杂了,所以解读源代码将另起一篇文章。本文仅供依葫芦画瓢,要理解原理请自行查阅别的资料或者自己解读源代码,或者等我的下一篇文章。
不论什么疑问或者指正请联系我,谢谢!
小伙伴们加油!GOOD LUCK!