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建工程,将openssl目录下生成的out32dll文件夹下的libeay32.dll和libeay32.lib到工程。
2、 设置工程的附加库和附加包含目录:
右键工程->属性-> C/C++ ->常规->附加包含目录
右键工程->属性-> 链接器 ->常规->附加库目录
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!
转载:http://www.tuicool.com/articles/MfiE7n