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上,方便统一调用或者方便项目改造替换。 最有实际意义的案例就是国密SM2的改造。 本例将使用国家机密中的抽象算法SM3作为添加对象,在windows下编译调试,仅描述添加步骤,所以本文的前提是基于openssl大数的SM3的c语言实现图书馆已经建成。 这篇文章仅供参考。 要了解原理请查阅其他资料或者自己分析源码,或者等我的下一篇文章。
具体步骤
1. 准备工作
1.下载openssl源码。 openssl 的最新版本是 openssl-1.0.1i。 下载链接:
2、sm3的C语言实现。
3、windows下perl和vc的安装和配置。
4.熟悉openssl的编译流程。
2、SM3代码修改【本步骤涉及的所有文件均为新建,存储目录关系如图所示】
1.涉及7个文件:
1.【新建文件】sm3.h
这是最重要的头文件,定义了算法可以导入的算法,即上面声明的每个函数也会生成对应的libeay.num(第五章会详细介绍)才可以导入进入dll供外部调用。 几个主要声明如下:
定义 sm3 的摘要宽度:
#defineSM3_DIGEST_LENGTH32/*sm3的汇总宽度为256位32字节,md5的汇总宽度为128位不确定*/
定义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
算法测试文件。
7.【新建文件】Makefile
编译后的Makefile。 上面介绍了上面几个文件在编译时的作用、编译过程、依赖关系等。
3.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算法封装后源码编译openssl,需要加载算法来实现SM3的添加:
#ifndefOPENSSL_NO_SM3
EVP_add_digest(EVP_sm3());
#endif
5.【手动修改】Makefile
新的SM3算法会在编译时添加到LIBSRC和LIBOBJ中,但是会添加sm3的各个头文件的依赖。
4.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,切换到该目录下源码的cryptoobjects所在目录,
执行命令:perlobjects.plobjects.txtobj_mac.numobj_mac.h
由于objects.txt的更改,两个文件obj_mac.h和obj_mac.num将被更新。 减少内容,例如:
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
执行命令:perlobj_dat.plobj_mac.hobj_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。 由于第二步中的objects.txt是随机填充的,所以这不是真正的OID。 仅作为示例。
6.【手动更改】obj_xref.txt
添加:
sm3WithRSAEncryption sm3 rsaEncryption
7.【执行命令手动更新】obj_xref.h
执行命令:perlobjxref.plobj_xref.txtobj_xref.h
obj_xref.h更新后添加的内容如下:
五、进行相关设置
1、util下涉及5个文件:
2.【手动更改】mkfiles.pl
添加:
"crypto/sm3",
添加sm3的算法源码目录,其中包含sm3的Makefile。 以后会根据Makefile进行编译。
3.【手动更改】mkdef.pl
添加内容如图:
添加新算法的头文件等,即可在dll中导入算法函数。
4.【执行命令手动更新】libeay.num
执行命令:perlutil/mkdef.plcryptoupdate
更新后Libeay.num的内容减少如:
5.【手动更改】mk1mf.pl
添加内容例如:
6.【手动更改】sp-diff.pl
添加内容例如:
7、crypto下涉及两个文件:
8.【手动更改】crypto-lib.com
添加内容例如:
9.【手动更改】install-crypto.com
添加内容例如:
10、涉及文件根目录下有5个文件:
11.【手动更改】
添加如图内容:Makefile、Makefile.bak、Makefile.org、makevms.com、INSTALL.VMS
此时需要对源码进行添加和更改,下面介绍编译和测试。
6. 编译
1、cmd下,首先设置VC环境,执行VC目录下的vcvars32.BAT文件源码编译openssl,如:
2、cmd下进入openssl源码所在目录,依次执行命令【编辑32位,如果64位可能会缺少ml64.exe】:
1)执行命令:perlConfigureVC-WIN32no-asm
2)执行命令:msdo_ms
3)执行命令:nmake-fmsntdll.mak:这是一个动态库:如果编译成功,最终输出在out32dll目录中:包括可执行文件、两个dll和两个lib文件[nmake-fms麦
这是静态库的编译命令,输出在out32目录下]。 编译成功如图:
4)执行命令:nmake-fmsntdll.maktest
5)执行命令:nmake -fmsntdll.makeinstall
7. 测试
1、API编程测试,在VS中建一个项目,将openssl目录下生成的out32dll文件夹下的libeay32.dll和libeay32.lib添加到项目中。
2、设置项目的附加库和附加包含目录:
右键单击项目->属性->C/C++->常规->附加包含目录
右键单击项目->属性->链接器->常规->附加库目录
3、sm3test.c源码如下:
sm3test.c
#include
#include
#include
#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("x ", list1[i]);
}
printf("rn");
}
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 digestnamen");
exit(1);
}
//根据输入的信息摘要函数的名字得到相应的EVP_MD算法结构
md = EVP_get_digestbyname(argv[1]);
//md = EVP_sm3();
if(!md) {
printf("Unknown message digest %sn", 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("x", 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添加新的算法。 事实上,如果你认为引擎方法更方便,请忽略这篇文章。 本文只是描述如何,而不是为什么。 因为openssl这样的封装机制本身就需要分析源码才能更好的理解为什么上面文章中需要这些步骤,因为这类步骤已经够复杂了,所以分析源码会是另外一篇文章。 这篇文章仅供参考。 要了解原理请查阅其他资料或者自己分析源码,或者等我的下一篇文章。
如果您有任何疑问或见谅,请联系我,谢谢!
伙计们,加油! 祝你好运!
1. 概述
在 Android 开发的这个阶段,注解越来越流行,比如 ButterKnife、Retrofit、Dragger、EventBus 等都选择使用注解来配置。根据处理周期,注解分为两种,一种是运行时注解,另一种是编译时注解,运行时注解因为性能问题而受到一些人的批评。编译时注解的核心依赖于APT(注解处理工具)的实现,原理是在单个代码元素(如类型、函数、字段等)上添加注解,在编译时编译器会检测AbstractProcessor的基类,并调用该类型的进程函数,然后将所有带有注解的元素传递给进程函数。这允许开发人员在编译器中进行相应的处理,例如,基于注解生成新的Java类,这是EventBus,Retrofit,Dragger等开源库的基本原理。
Java API 已经提供了一个用于扫描源代码和解析注解的框架编译生成源码,您可以继承 AbstractProcessor 类来提供自己的解析注解逻辑。下面我们将学习如何从 Android Studio 中的编译时注释生成 java 文件。
2. 创建名为处理器的模块
首先使用 Android Studio 创建一个 Android 项目。然后开始创建一个名为 Processor 的 Java 库。
单击文件>新建>新建模块,如图所示
我们需要创建一个非 Android 库编译生成源码,所以一定要选择一个 Java 库
3. 兼容性配置
由于 Android 目前不完全支持 Java 8 语言功能,因此会发生编译错误。在这里,项目的源和目标兼容性值保留为 Java 7。
在应用程序模块下打开build.gradle
在安卓标签下添加编译选项
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
如图所示
然后打开处理器库的 build.gradle
加
sourceCompatibility = 1.7
targetCompatibility = 1.7
4. 创建注释
在处理器模块下创建注释类
命名自定义批注
详情如下
5. 创建注释处理器
处理器是
继承自 AbstractProcessor 类,待处理注解的全名用 @SupportedAnnotationTypes 填写,@SupportedSourceVersion表示 JAVA 的已处理版本。