Openssl编程--源码分析
Openssl编程
赵春平 著
Email: forxy@126.com
第一章 基础知识 8
1.1 对称算法 8
1.2 摘要算法 9
1.3 公钥算法 9
1.4 回调函数 11
第二章 openssl简介 13
2.1 openssl简介 13
2.2 openssl安装 13
2.2.1 linux下的安装 13
2.2.2 windows编译与安装 14
2.3 openssl源代码 14
2.4 openssl学习方法 16
第三章 堆栈 17
3.1 openssl堆栈 17
3.2 数据结构 17
3.3 源码 18
3.4 定义用户自己的堆栈函数 18
3.5 编程示例 19
第四章 哈希表 21
4.1 哈希表 21
4.2 哈希表数据结构 21
4.3 函数说明 23
4.4 编程示例 25
第五章 内存分配 27
5.1 openssl内存分配 27
5.2 内存数据结构 27
5.3 主要函数 28
5.4 编程示例 29
第六章 动态模块加载 30
6.1 动态库加载 30
6.2 DSO概述 30
6.3 数据结构 31
6.4 编程示例 32
第七章 抽象IO 34
7.1 openssl抽象IO 34
7.2 数据结构 34
7.3 BIO 函数 36
7.4 编程示例 36
7.4.1 mem bio 36
7.4.2 file bio 37
7.4.3 socket bio 38
7.4.4 md BIO 39
7.4.5 cipher BIO 40
7.4.6 ssl BIO 41
7.4.7 其他示例 42
第八章 配置文件 43
8.1 概述 43
8.2 openssl配置文件读取 43
8.3 主要函数 44
8.4 编程示例 44
第九章 随机数 46
9.1 随机数 46
9.2 openssl随机数数据结构与源码 46
9.3 主要函数 48
9.4 编程示例 48
第十章 文本数据库 50
10.1 概述 50
10.2 数据结构 51
10.3 函数说明 51
10.4 编程示例 52
第十一章 大数 54
11.1 介绍 54
11.2 openssl大数表示 54
11.3 大数函数 55
11.4 使用示例 58
第十二章 BASE64编解码 64
12.1 BASE64编码介绍 64
12.2 BASE64编解码原理 64
12.3 主要函数 65
12.4 编程示例 66
第十三章 ASN1库 68
13.1 ASN1简介 68
13.2 DER编码 70
13.3 ASN1基本类型示例 70
13.4 openssl 的ASN.1库 73
13.5 用openssl的ASN.1库DER编解码 74
13.6 Openssl的ASN.1宏 74
13.7 ASN1常用函数 75
13.8 属性证书编码 89
第十四章 错误处理 93
14.1 概述 93
14.2 数据结构 93
14.3 主要函数 95
14.4 编程示例 97
第十五章 摘要与HMAC 100
15.1 概述 100
15.2 openssl摘要实现 100
15.3 函数说明 101
15.4 编程示例 101
15.5 HMAC 103
第十六章 数据压缩 104
16.1 简介 104
16.2 数据结构 104
16.3 函数说明 105
16.4 openssl中压缩算法协商 106
16.5 编程示例 106
第十七章 RSA 107
17.1 RSA介绍 107
17.2 openssl的RSA实现 107
17.3 RSA签名与验证过程 108
17.4 数据结构 109
17.4.1 RSA_METHOD 109
17.4.2 RSA 110
17.5 主要函数 110
17.6编程示例 112
17.6.1密钥生成 112
17.6.2 RSA加解密运算 113
17.6.3签名与验证 116
第十八章 DSA 119
18.1 DSA简介 119
18.2 openssl的DSA实现 120
18.3 DSA数据结构 120
18.4 主要函数 121
18.5 编程示例 122
18.5.1密钥生成 122
18.5.2签名与验证 124
第十九章DH 126
19.1 DH算法介绍 126
19.2 openssl的DH实现 127
19.3数据结构 127
19.4 主要函数 128
19.5 编程示例 129
第二十章 椭圆曲线 132
20.1 ECC介绍 132
20.2 openssl的ECC实现 133
20.3 主要函数 135
20.3.1参数设置 135
20.3.2参数获取 136
20.3.3转化函数 137
20.3.4其他函数 137
20.4 编程示例 139
第二十一章 EVP 143
21.1 EVP简介 143
21.2 数据结构 143
21.2.1 EVP_PKEY 144
21.2.2 EVP_MD 144
21.2.3 EVP_CIPHER 145
21.2.4 EVP_CIPHER_CTX 146
21.3 源码结构 147
21.4 摘要函数 147
21.5 对称加解密函数 148
21.6 非对称函数 149
21.7 BASE64编解码函数 149
21.8其他函数 150
21.9 对称加密过程 152
21.10 编程示例 152
第二十二章 PEM格式 159
22.1 PEM概述 159
22.2 openssl的PEM实现 160
22.3 PEM函数 161
22.4 编程示例 161
第二十三章 Engine 165
23.1 Engine概述 165
23.2 Engine支持的原理 165
23.3 Engine数据结构 166
23.4 openssl 的Engine源码 167
23.5 Engine函数 167
23.6 实现Engine示例 169
第二十四章 通用数据结构 182
24.1通用数据结构 182
24.2 X509_ALGOR 182
24.3 X509_VAL 184
24.4 X509_SIG 185
24.5 X509_NAME_ENTRY 186
24.6 X509_NAME 187
24.7 X509_EXTENSION 193
24.8 X509_ATTRIBUTE 199
24.9 GENERAL_NAME 200
第二十五章 证书申请 203
25.1 证书申请介绍 203
25.2 数据结构 203
25.3 主要函数 204
25.4 编程示例 206
25.4.1生成证书请求文件 206
25.4.2 解码证书请求文件 208
第二十六章 X509数字证书 210
26.1 X509数字证书 210
26.2 opessl实现 210
26.3 X509数据结构 210
26.4 X509_TRUST与X509_CERT_AUX 214
26.5 X509_PURPOSE 215
26.6 主要函数 218
26.7 证书验证 221
26.7.1证书验证项 221
26.7.2 Openssl中的证书验证 221
第二十七章 OCSP 222
27.1 概述 222
27.2 openssl实现 222
27.3 主要函数 222
27.4编程示例 227
第二十八章 CRL 228
28.1 CRL介绍 228
28.2 数据结构 228
28.3 CRL函数 230
28.4 编程示例 231
第二十九章 PKCS7 233
29.1概述 233
29.2 数据结构 233
29.3 函数 234
29.4 消息编解码 235
29.4.1 data 235
29.4.2 signed data 236
29.4.3 enveloped 237
29.4.4 signed_and_enveloped 238
29.4.5 digest 238
29.4.6 encrypted 239
29.4.7 读取PEM 239
29.4.8 解码pkcs7 240
第三十章 PKCS12 241
30.1 概述 241
30.2 openss实现 241
30.3数据结构 242
30.4函数 243
30.5 编程示例 245
第三十一章 SSL实现 254
31.1概述 254
31.2 openssl实现 254
31.3 建立SSL测试环境 254
31.4 数据结构 256
31.5 加密套件 256
31.6 密钥信息 257
31.7 SESSION 258
31.8 多线程支持 258
31.9 编程示例 259
31.10 函数 270
第三十二章 Openssl命令 272
32.1概述 272
32.2 asn1parse 272
32.3 dgst 274
32.4 gendh 275
32.5 passwd 276
32.6 rand 276
32.7 genrsa 277
32.8 req 278
32.9 x509 280
32.10 version 283
32.11 speed 283
32.12 sess_id 284
32.13 s_server 284
32.14 s_client 286
32.15 rsa 288
32.16 pkcs7 289
32.17 dsaparam 290
32.18 gendsa 291
32.19 enc 291
32.20 ciphers 292
32.21 CA 293
32.22 verify 296
32.23 rsatul 297
32.24 crl 299
32.25 crl2pkcs7 300
32.26 errstr 300
32.27 ocsp 301
32.28 pkcs12 304
32.29 pkcs8 306
32.30 s_time 307
32.31 dhparam和dh 308
32.32 ecparam 309
32.33 ec 310
32.34 dsa 311
32.35 nseq 312
32.36 prime 313
32.37 smime 313
第一章 基础知识
1.1 对称算法
对称算法使用一个密钥。给定一个明文和一个密钥,加密产生密文,其长度和明文大致相同。解密时,使用读密钥与加密密钥相同。
对称算法主要有四种加密模式:
(1) 电子密码本模式 Electronic Code Book(ECB)
这种模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
其缺点是:电子编码薄模式用一个密钥加密消息的所有块,如果原消息中重复明文块,则加密消息中的相应密文块也会重复,因此,电子编码薄模式适于加密小消息。
(2)加密块链模式 Cipher Block Chaining(CBC)
CBC模式的加密首先也是将明文分成固定长度的块,然后将前面一个加密块输出的密文与下一个要加密的明文块进行异或操作,将计算结果再用密钥进行加密得到密文。第一明文块加密的时候,因为前面没有加密的密文,所以需要一个初始化向量。跟ECB方式不一样,通过连接关系,使得密文跟明文不再是一一对应的关系,破解起来更困难,而且克服了只要简单调换密文块可能达到目的的攻击。
(3)加密反馈模式 Cipher Feedback Mode(CFB)
面向字符的应用程序的加密要使用流加密法,可以使用加密反馈模式。在此模式下,数据用更小的单元加密,如可以是8位,这个长度小于定义的块长(通常是64位)。其加密步骤是:
a) 使用64位的初始化向量。初始化向量放在移位寄存器中,在第一步加密,产生相应的64位初始化密文;
b) 始化向量最左边的8位与明文前8位进行异或运算,产生密文第一部分(假设为c),然后将c传输到接收方;
c) 向量的位(即初始化向量所在的移位寄存器内容)左移8位,使移位寄存器最右边的8位为不可预测的数据,在其中填入c的内容;
d) 第1-3步,直到加密所有的明文单元。
解密过程相反
4)输出反馈模式 Output Feedback Mode(OFB)
输出反馈模式与CFB相似,惟一差别是,CFB中密文填入加密过程下一阶段,而在OFB中,初始化向量加密过程的输入填入加密过程下一阶段。
1.2 摘要算法
摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。
如今常用的“消息摘要”算法经历了多年验证发展而保留下来的算法已经不多,这其中包括MD2、MD4、MD5、SHA、SHA-1/256/383/512等。
常用的摘要算法主要有MD5和SHA1。D5的输出结果为16字节,sha1的输出结果为20字节。
1.3 公钥算法
在公钥密码系统中,加密和解密使用的是不同的密钥,这两个密钥之间存在着相互依存关系:即用其中任一个密钥加密的信息只能用另一个密钥进行解密。这使得通信双方无需事先交换密钥就可进行保密通信。其中加密密钥和算法是对外公开的,人人都可以通过这个密钥加密文件然后发给收信者,这个加密密钥又称为公钥;而收信者收到加密文件后,它可以使用他的解密密钥解密,这个密钥是由他自己私人掌管的,并不需要分发,因此又成称为私钥,这就解决了密钥分发的问题。
主要的公钥算法有:RSA、DSA、DH和ECC。
(1)RSA算法
当前最著名、应用最广泛的公钥系统RSA是在1978年,由美国麻省理工学院(MIT)的Rivest、Shamir和Adleman在题为《获得数字签名和公开钥密码系统的方法》的论文中提出的。它是一个基于数论的非对称(公开钥)密码体制,是一种分组密码体制。其名称来自于三个发明者的姓名首字母。 它的安全性是基于大整数素因子分解的困难性,而大整数因子分解问题是数学上的著名难题,至今没有有效的方法予以解决,因此可以确保RSA算法的安全性。RSA系统是公钥系统的最具有典型意义的方法,大多数使用公钥密码进行加密和数字签名的产品和标准使用的都是RSA算法。
RSA算法是第一个既能用于数据加密也能用于数字签名的算法,因此它为公用网络上信息的加密和鉴别提供了一种基本的方法。它通常是先生成一对RSA 密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册,人们用公钥加密文件发送给个人,个人就可以用私钥解密接受。为提高保密强度,RSA密钥至少为500位长,一般推荐使用1024位。
RSA算法是R.Rivest、A.Shamir和L.Adleman于1977年在美国麻省理工学院开发,于1978年首次公布。
RSA公钥密码算法是目前网络上进行保密通信和数字签名的最有效的安全算法之一。RSA算法的安全性基于数论中大素数分解的困难性,所以,RSA需采用足够大的整数。因子分解越困难,密码就越难以破译,加密强度就越高。
其算法如下:
- 选择两质数p、q
- 计算n = p * q
- 计算n的欧拉函数Φ(n) = (p - 1)(q - 1)
- 选择整数e,使e与Φ(n)互质,且1 < e < Φ(n)
- 计算d,使d * e = 1 mod Φ(n)
其中,公钥KU={e, n},私钥KR={d, n}。
加密/解密过程:
利用RSA加密,首先需将明文数字化,取长度小于log2n位的数字作为明文块。
对于明文块M和密文块C,加/解密的形式如下:
加密: C = Me mod n
解密: M = Cd mod n = (Me)d mod n = Med mod n
RSA的安全性基于大数分解质因子的困难性。因为若n被分解为n = p * q,则Φ(n)、e、d可依次求得。目前,因式分解速度最快的方法的时间复杂性为exp(sqrt(ln(n)lnln(n)))。统计数据表明,在重要应用中,使用512位的密钥已不安全,需要采用1024位的密钥。
(2)DSA算法
DSA(Digital Signature Algorithm,数字签名算法,用作数字签名标准的一部分),它是另一种公开密钥算法,它不能用作加密,只用作数字签名。DSA使用公开密钥,为接受者验证数据的完整性和数据发送者的身份。它也可用于由第三方去确定签名和所签数据的真实性。DSA算法的安全性基于解离散对数的困难性,这类签字标准具有较大的兼容性和适用性,成为网络安全体系的基本构件之一。
DSA签名算法中用到了以下参数:
p是L位长的素数,其中L从512到1024且是64的倍数。
q是160位长且与p-1互素的因子 ,其中h是小于p-1并且满足 大于1的任意数。
x是小于q的数。
另外,算法使用一个单向散列函数H(m)。标准指定了安全散列算法(SHA)。三个参数p,q和g是公开的,且可以被网络中所有的用户公有。私人密钥是x,公开密钥是y。
对消息m签名时:
(1) 发送者产生一个小于q的随机数k。
(2) 发送者产生:
r和s就是发送者的签名,发送者将它们发送给接受者。
(3) 接受者通过计算来验证签名:
如果v=r,则签名有效。
(3)Diffie-Hellman密钥交换
DH算法是W.Diffie和M.Hellman提出的。此算法是最早的公钥算法。它实质是一个通信双方进行密钥协定的协议:两个实体中的任何一个使用自己的私钥和另一实体的公钥,得到一个对称密钥,这一对称密钥其它实体都计算不出来。DH算法的安全性基于有限域上计算离散对数的困难性。离散对数的研究现状表明:所使用的DH密钥至少需要1024位,才能保证有足够的中、长期安全。
(4) 椭圆曲线密码体制(ECC)
1985年,N. Koblitz和V. Miller分别独立提出了椭圆曲线密码体制(ECC),其依据就是定义在椭圆曲线点群上的离散对数问题的难解性。
为了用椭圆曲线构造密码系统,首先需要找到一个单向陷门函数,椭圆曲线上的数量乘就是这样的单向陷门函数。
椭圆曲线的数量乘是这样定义的:设E为域K上的椭圆曲线,G为E上的一点,这个点被一个正整数k相乘的乘法定义为 k个G相加,因而有
kG = G + G + … + G (共有k个G)
若存在椭圆曲线上的另一点N ≠ G,满足方程kG = N。容易看出,给定k和G,计算N相对容易。而给定N和G,计算k = logG N相对困难。这就是椭圆曲线离散对数问题。
离散对数求解是非常困难的。椭圆曲线离散对数问题比有限域上的离散对数问题更难求解。对于有理点数有大素数因子的椭圆离散对数问题,目前还没有有效的攻击方法。
1.4 回调函数
Openssl中大量用到了回调函数。回调函数一般定义在数据结构中,是一个函数指针。通过回调函数,客户可以自行编写函数,让openssl函数来调用它,即用户调用openssl提供的函数,openssl函数再回调用户提供的函数。这样方便了用户对openssl函数操作的控制。在openssl实现函数中,它一般会实现一个默认的函数来进行处理,如果用户不设置回调函数,则采用它默认的函数。
回调函数举例:
头文件:
#ifndef RANDOM_H
#define RANDOM_H 1
typedef int *callback_random(char *random,int len);
void set_callback(callback_random *cb);
int genrate_random(char *random,int len);
#endif
源代码:
#include "random.h"
#include <stdio.h>
callback_random *cb_rand=NULL;
static int default_random(char *random,int len
{
memset(random,0x01,len);
return 0;
}
void set_callback(callback_random *cb)
{
cb_rand=cb;
}
int genrate_random(char *random,int len)
{
if(cb_rand==NULL)
return default_random(random,len);
else
return cb_rand(random,len);
return 0;
}
测试代码:
#include "random.h"
static int my_rand(char *rand,int len)
{
memset(rand,0x02,len);
return 0;
}
int main()
{
char random[10];
int ret;
set_callback(my_rand);
ret=genrate_random(random,10);
return 0;
}
本例子用来生产简单的随机数,如果用户提供了生成随机数回调函数,则生成随机数采用用户的方法,否则采用默认的方法。
第二章 openssl简介
2.1 openssl简介
openssl是一个功能丰富且自包含的开源安全工具箱。它提供的主要功能有:SSL协议实现(包括SSLv2、SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。
openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能。openssl支持Linux、UNIX、windows、Mac等平台。openssl目前最新的版本是0.9.8e.
2.2 openssl安装
对应不同的操作系统,用户可以参考INSTALL、INSTALL.MacOS、INSTALL.NW、INSTALL.OS2、INSTALL.VMS、INSTALL.W32、INSTALL.W64和INSTALL.WCE等文件来安装openssl。安装时,需要如下条件:
Make工具、Perl 5、编译器以及C语言库和头文件。
2.2.1 linux下的安装
1)解压openssl开发包文件;
2)运行./config --prefix=/usr/local/openssl (更多选项用./config --help来查看),可用的选项有:no-mdc2、no-cast no-rc2、no-rc5、no-ripemd、no-rc4 no-des 、no-md2、no-md4、no-idea 、no-aes、no-bf、no-err、no-dsa、no-dh、no-ec、no-hw、no-asm、no-krb5、no-dso 、no-threads 、no-zlib、-DOPENSSL_NO_HASH_COMP、-DOPENSSL_NO_ERR、-DOPENSSL_NO_HW 、-DOPENSSL_NO_OCSP、-DOPENSSL_NO_SHA256和-DOPENSSL_NO_SHA512等。去掉不必要的内容可以减少生成库的大小。 若要生成debug版本的库和可执行程序加-g或者-g3(openssl中有很多宏,需要调试学习最好加上-g3)。
3)make test (可选)
4)make install
完成后,openssl会被安装到/usr/local/openssl目录,包括头文件目录include、可执行文件目录bin、man在线帮助、库目录lib以及配置文件目录(ssl)。
2.2.2 windows编译与安装
安装步骤如下:
1) 安装VC6.0;0.9.7i及以上版本支持VC++ 2005
2) 安装perl5;
3) 解压openssl;
4) 在控制台下进入openssl目录;
5) 运行perl Configure VC-WIN32,其他可选项参见2.2.1节;
6) ms\do_ms.bak
7) nmake -f ms\ntdll.mak(动态库)或者nmake –f ms\nt.mak(静态库);
编译debug版本在ms\do_ms.bat中加上debug,,见INSTALL.W32,具体做法如下:
编辑do_ms.bak,修改前内容如下:
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl no-asm VC-WIN32 >ms\nt.mak
perl util\mk1mf.pl dll no-asm VC-WIN32 >ms\ntdll.mak
perl util\mk1mf.pl no-asm VC-CE >ms\ce.mak
perl util\mk1mf.pl dll no-asm VC-CE >ms\cedll.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
添加debug后如下:
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl debug no-asm VC-WIN32 >ms\nt.mak #添加debug
perl util\mk1mf.pl debug dll no-asm VC-WIN32 >ms\ntdll.mak #添加debug
perl util\mk1mf.pl debug no-asm VC-CE >ms\ce.mak #添加debug
perl util\mk1mf.pl debug dll no-asm VC-CE >ms\cedll.mak #添加debug
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
安装完毕后,生成的头文件放在inc32目录,动/静态库和可执行文件放在outdll目录。
另外用户可以在windows集成环境下建自己的工作环境,来编译openssl,操作比较烦琐,也可以从网上址下载已有的vc6.0工程。
2.3 openssl源代码
openssl源代码主要由eay库、ssl库、工具源码、范例源码以及测试源码组成。
eay库是基础的库函数,提供了很多功能。源代码放在crypto目录下。包括如下内容:
1) asn.1 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)。
ssl库所有源代码在ssl目录下,包括了sslv2、sslv3、tlsv1和DTLS的源代码。各个版本基本上都有客户端源码(*_clnt.c)、服务源码(*_srvr.c)、通用源码(*_both.c)、底层包源码(*_pkt.c)、方法源码(*_meth.c)以及协议相关的各种密钥计算源码(*_enc.c)等,都很有规律。
工具源码主要在crypto/apps目录下,默认编译时只编译成openssl(windows下为openssl.exe)可执行文件。该命令包含了各种命令工具。此目录下的各个源码可以单独进行编译。
范例源码在demo目录下,另外engines目录给出了openssl支持的几种硬件的engines源码,也可以作为engine编写参考。
测试源码主要在test目录下。
2.4 openssl学习方法
通过学习openssl,用户能够学到PKI方面的各种知识,其重要性不言而喻。以下为学习openssl的方法,供参考。
1) 建立学习环境
建立一个供调试的openssl环境,可以是windows平台,也可以是linux或者其他平台。用户需有在这些平台下调试源代码的能力。
2)学习openssl的命令
通过openssl命令的学习,对openssl有基本的了解。
3) 学习openssl源代码并调试
主要的源代码有:
apps目录下的各个程序,对应于openssl的各项命令;
demos下的各种源代码;
engines下的各种engine实现;
test目录下的各种源代码。
对于openssl函数的学习,主要查看openssl自身是如何调用的,或者查看函数的实现。对于openssl中只有实现而没有调用的函数,读者需要自己写源码或研究源代码去学习。
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。
6) 学习openssl相关书籍
读者可以参考《OpenSSL与网络信息安全--基础、结构和指令》、《Network Security with OpenSSL》(OReilly出版)和《OpenSSL for windows Developer’s Guide》。
第三章 堆栈
3.1 openssl堆栈
堆栈是一种先进后出的数据结构。openssl大量采用堆栈来存放数据。它实现了一个通用的堆栈,可以方便的存储任意数据。它实现了许多基本的堆栈操作,主要有:堆栈拷贝(sk_dup)、构建新堆栈(sk_new_null,sk_new)、插入数据(sk_insert)、删除数据(sk_delete)、查找数据(sk_find,sk_find_ex)、入栈(sk_push)、出栈(sk_pop)、获取堆栈元素个数(sk_num)、获取堆栈值(sk_value)、设置堆栈值(sk_set)和堆栈排序(sk_sort)。
3.2 数据结构
openssl堆栈数据结构在stack.h中定义如下:
typedef struct stack_st
{
int num;
char **data;
int sorted;
int num_alloc;
int (*comp)(const char * const *, const char * const *);
} STACK;
各项意义如下:
num: 堆栈中存放数据的个数。
data: 用于存放数据地址,每个数据地址存放在data[0]到data[num-1]中。
sorted: 堆栈是否已排序,如果排序则值为1,否则为0,堆栈数据一般是无序的,只有当用户调用了sk_sort操作,其值才为1。
comp: 堆栈内存放数据的比较函数地址,此函数用于排序和查找操作;当用户生成一个新堆栈时,可以指定comp为用户实现的一个比较函数;或当堆栈已经存在时通过调用sk_set_cmp_func函数来重新指定比较函数。
注意,用户不需要调用底层的堆栈函数(sk_sort、sk_set_cmp_func等),而是调用他通过宏实现的各个函数。
3.3 源码
openssl堆栈实现源码位于crypto/stack目录下。下面分析了部分函数。
1) sk_set_cmp_func
此函数用于设置堆栈存放数据的比较函数。由于堆栈不知道用户存放的是什么数据,所以,比较函数必须由用户自己实现。
2) sk_find
根据数据地址来查找它在堆栈中的位置。当堆栈设置了比较函数时,它首先对堆栈进行排序,然后通过二分法进行查找。如果堆栈没有设置比较函数,它只是简单的比较数据地址来查找.
3) sk_sort
本函数对堆栈数据排序。它首先根据sorted来判断是否已经排序,如果未排序则调用了标准C函数qsort进行快速排序。
4) sk_pop_free
本函数用于释放堆栈内存放的数据以及堆栈本身,它需要一个由用户指定的针对具体数据的释放函数。如果用户仅调用sk_free函数,则只会释放堆栈本身所用的内存,而不会释放数据内存。
3.4 定义用户自己的堆栈函数
用户直接调用最底层的堆栈操作函数是一个麻烦的事情,对此openssl提供了用宏来帮助用户实现接口。用户可以参考safestack.h来定义自己的上层堆栈操作函数,举例如下,safestack.h定义了如下关于GENERAL_NAME数据结构的堆栈操作:
#define sk_GENERAL_NAME_new(st) SKM_sk_new(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_new_null() SKM_sk_new_null(GENERAL_NAME)
#define sk_GENERAL_NAME_free(st) SKM_sk_free(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_num(st) SKM_sk_num(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_value(st, i) SKM_sk_value(GENERAL_NAME, (st), (i))
#define sk_GENERAL_NAME_set(st, i, val) SKM_sk_set(GENERAL_NAME, (st), (i), (val))
#define sk_GENERAL_NAME_zero(st) SKM_sk_zero(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_push(st, val) SKM_sk_push(GENERAL_NAME, (st), (val))
#define sk_GENERAL_NAME_unshift(st, val) SKM_sk_unshift(GENERAL_NAME, (st), (val))
#define sk_GENERAL_NAME_find(st, val) SKM_sk_find(GENERAL_NAME, (st), (val))
#define sk_GENERAL_NAME_find_ex(st, val) SKM_sk_find_ex(GENERAL_NAME, (st), (val))
#define sk_GENERAL_NAME_delete(st, i) SKM_sk_delete(GENERAL_NAME, (st), (i))
#define sk_GENERAL_NAME_delete_ptr(st, ptr) SKM_sk_delete_ptr(GENERAL_NAME, (st), (ptr))
#define sk_GENERAL_NAME_insert(st, val, i) SKM_sk_insert(GENERAL_NAME, (st), (val), (i))
#define sk_GENERAL_NAME_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(GENERAL_NAME, (st), (cmp))
#define sk_GENERAL_NAME_dup(st) SKM_sk_dup(GENERAL_NAME, st)
#define sk_GENERAL_NAME_pop_free(st, free_func) SKM_sk_pop_free(GENERAL_NAME, (st), (free_func))
#define sk_GENERAL_NAME_shift(st) SKM_sk_shift(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_pop(st) SKM_sk_pop(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_sort(st) SKM_sk_sort(GENERAL_NAME, (st))
#define sk_GENERAL_NAME_is_sorted(st) SKM_sk_is_sorted(GENERAL_NAME, (st))
当用户想对GENERAL_NAME数据进行堆栈操作时,调用上面由宏定义的函数即可,即直观又方便。比如用户想设置堆栈数据的比较函数和对堆栈排序时,他分别调用:sk_GENERAL_NAME_set_cmp_func和sk_GENERAL_NAME_sort。
3.5 编程示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/safestack.h>
#define sk_Student_new(st) SKM_sk_new(Student, (st))
#define sk_Student_new_null() SKM_sk_new_null(Student)
#define sk_Student_free(st) SKM_sk_free(Student, (st))
#define sk_Student_num(st) SKM_sk_num(Student, (st))
#define sk_Student_value(st, i) SKM_sk_value(Student, (st), (i))
#define sk_Student_set(st, i, val) SKM_sk_set(Student, (st), (i), (val))
#define sk_Student_zero(st) SKM_sk_zero(Student, (st))
#define sk_Student_push(st, val) SKM_sk_push(Student, (st), (val))
#define sk_Student_unshift(st, val) SKM_sk_unshift(Student, (st), (val))
#define sk_Student_find(st, val) SKM_sk_find(Student, (st), (val))
#define sk_Student_delete(st, i) SKM_sk_delete(Student, (st), (i))
#define sk_Student_delete_ptr(st, ptr) SKM_sk_delete_ptr(Student, (st), (ptr))
#define sk_Student_insert(st, val, i) SKM_sk_insert(Student, (st), (val), (i))
#define sk_Student_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(Student, (st), (cmp))
#define sk_Student_dup(st) SKM_sk_dup(Student, st)
#define sk_Student_pop_free(st, free_func) SKM_sk_pop_free(Student, (st), (free_func))
#define sk_Student_shift(st) SKM_sk_shift(Student, (st))
#define sk_Student_pop(st) SKM_sk_pop(Student, (st))
#define sk_Student_sort(st) SKM_sk_sort(Student, (st))
typedef struct Student_st
{
char *name;
int age;
char *otherInfo;
}Student;
typedef STACK_OF(Student) Students;
Student *Student_Malloc()
{
Student *a=malloc(sizeof(Student));
a->name=malloc(20);
strcpy(a->name,"zcp");
a->otherInfo=malloc(20);
strcpy(a->otherInfo,"no info");
return a;
}
void Student_Free(Student *a)
{
free(a->name);
free(a->otherInfo);
free(a);
}
static int Student_cmp(Student *a,Student *b)
{
int ret;
ret=strcmp(a->name,b->name);
return ret;
}
int main()
{
Students *s,*snew;
Student *s1,*one,*s2;
int i,num;
s=sk_Student_new_null();
snew=sk_Student_new(Student_cmp);
s2=Student_Malloc();
sk_Student_push(snew,s2);
i=sk_Student_find(snew,s2);
s1=Student_Malloc();
sk_Student_push(s,s1);
num=sk_Student_num(s);
for(i=0;i<num;i++)
{
one=sk_Student_value(s,i);
printf("student name : %s\n",one->name);
printf("sutdent age : %d\n",one->age);
printf("student otherinfo : %s\n\n\n",one->otherInfo);
}
sk_Student_pop_free(s,Student_Free);
sk_Student_pop_free(snew,Student_Free);
return 0;
}
第四章 哈希表
4.1 哈希表
在一般的数据结构如线性表和树中,记录在结构中的相对位置是与记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列的关键字比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立确定的对应关系,使每个关键字和结构中一个唯一的存储位置相对应。在查找时,只需根据这个对应关系找到给定值。这种对应关系既是哈希函数,按这个思想建立的表为哈希表。
哈希表存在冲突现象:不同的关键字可能得到同一哈希地址。在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。
4.2 哈希表数据结构
openssl函数使用哈希表来加快查询操作,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等。其源码在crypto/lhash目录下。
openssl中的哈希表数据结构在lhash.h中定义如下:
typedef struct lhash_node_st
{
void *data;
struct lhash_node_st *next;
#ifndef OPENSSL_NO_HASH_COMP
unsigned long hash;
#endif
} LHASH_NODE;
本结构是一个单链表。其中,data用于存放数据地址,next为下一个数据地址,hash为数据哈希计算值。
typedef struct lhash_st
{
LHASH_NODE **b;
LHASH_COMP_FN_TYPE comp;
LHASH_HASH_FN_TYPE hash;
unsigned int num_nodes;
unsigned int num_alloc_nodes;
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;
unsigned long num_hash_calls;
unsigned long num_comp_calls;
unsigned long num_insert;
unsigned long num_replace;
unsigned long num_delete;
unsigned long num_no_delete;
unsigned long num_retrieve;
unsigned long num_retrieve_miss;
unsigned long num_hash_comps;
int error;
} LHASH;
其中,b指针数组用于存放所有的数据,数组中的每一个值为数据链表的头指针;comp用于存放数据比较函数地址;hash用于存放计算哈希值函数的地址;num_nodes为链表个数;num_alloc_nodes为b分配空间的大小。
基本的结构如下示图:
4.3 函数说明
1) LHASH *lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
功能:生成哈希表
源文件:lhash.c
说明:输入参数h为哈希函数,c为比较函数。这两个函数都是回调函数。 因为哈希表用于存放任意的数据结构,哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算。所以,用户必须提供这两个回调函数。
2) void *lh_delete(LHASH *lh, const void *data)
源文件:lhash.c
功能:删除散列表中的一个数据
说明:data为数据结构指针。
3) void lh_doall(LHASH *lh, LHASH_DOALL_FN_TYPE func)
源文件:lhash.c
功能:处理哈希表中的所有数据
说明:func为外部提供的回调函数,本函数遍历所有存储在哈希表中的数据,每个数据被func处理。
4) void lh_doall_arg(LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void *arg)
源文件:lhash.c
功能:处理哈希表中所有数据
说明:此参数类似于lh_doall 函数,func为外部提供的回调函数,arg为传递给func函数的参数。本函数遍历所有存储在哈希表中的数据,每个数据被func处理。
5) void lh_free(LHASH *lh)
源文件:lhash.c
功能:释放哈希表。
6) void *lh_insert(LHASH *lh, void *data)
源文件:lhash.c
功能:往哈希表中添加数据。
说明:data为需要添加数据结构的指针地址。
7) void *lh_retrieve(LHASH *lh, const void *data)
源文件:lhash.c
功能:查询数据。
说明:从哈希表中查询数据,data为数据结构地址,此数据结构中必须提供关键项(这些关键项对应于用户提供的哈希函数和比较函数)以供查询,如果查询成功,返回数据结构的地址,否则返回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);
8) void lh_node_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:将哈希表中每个链表下的数据状态输出到BIO中。
9) void lh_node_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:将哈希表中每个链表下数据到个数输出到FILE中。
说明:此函数调用了lh_node_stats_bio函数。
10)void lh_node_usage_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:将哈希表的使用状态输出到BIO中。
11) void lh_node_usage_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:将哈希表的使用状态输出到FILE中
说明:此函数调用了lh_node_usage_stats_bio函数
12)unsigned long lh_num_items(const LHASH *lh)
源文件:lhash.c
功能:获取哈希表中元素的个数。
13)void lh_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:输出哈希表统计信息到BIO中
14)void lh_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:打印哈希表的统计信息,此函数调用了lh_stats_bio。
15)unsigned long lh_strhash(const char *c)
源文件:lhash.c
功能:计算文本字符串到哈希值。
4.4 编程示例
#include <string.h>
#include <openssl/lhash.h>
typedef struct Student_st
{
char name[20];
int age;
char otherInfo[200];
}Student;
static int Student_cmp(const void *a, const void *b)
{
char *namea=((Student *)a)->name;
char *nameb=((Student *)b)->name;
return strcmp(namea,nameb);
}
/* 打印每个值*/
static void PrintValue(Student *a)
{
printf("name :%s\n",a->name);
printf("age :%d\n",a->age);
printf("otherInfo : %s\n",a->otherInfo);
}
static void PrintValue_arg(Student *a,void *b)
{
int flag=0;
flag=*(int *)b;
printf("用户输入参数为:%d\n",flag);
printf("name :%s\n",a->name);
printf("age :%d\n",a->age);
printf("otherInfo : %s\n",a->otherInfo);
}
int main()
{
int flag=11;
LHASH *h;
Student s1={"zcp",28,"hu bei"},
s2={"forxy",28,"no info"},
s3={"skp",24,"student"},
s4={"zhao_zcp",28,"zcp's name"},
*s5;
void *data;
h=lh_new(NULL,Student_cmp);
if(h==NULL)
{
printf("err.\n");
return -1;
}
data=&s1;
lh_insert(h,data);
data=&s2;
lh_insert(h,data);
data=&s3;
lh_insert(h,data);
data=&s4;
lh_insert(h,data);
/* 打印*/
lh_doall(h,PrintValue);
lh_doall_arg(h,PrintValue_arg,(void *)(&flag));
data=lh_retrieve(h,(const void*)"skp");
if(data==NULL)
{
printf("can not look up skp!\n");
lh_free(h);
return -1;
}
s5=data;
printf("student name : %s\n",s5->name);
printf("sutdent age : %d\n",s5->age);
printf("student otherinfo : %s\n",s5->otherInfo);
lh_free(h);
getchar();
return 0;
}
第五章 内存分配
5.1 openssl内存分配
用户在使用内存时,容易犯的错误就是内存泄露。当用户调用内存分配和释放函数时,查找内存泄露比较麻烦。openssl提供了内置的内存分配/释放函数。如果用户完全调用openssl的内存分配和释放函数,可以方便的找到内存泄露点。openssl分配内存时,在其内部维护一个内存分配哈希表,用于存放已经分配但未释放的内存信息。当用户申请内存分配时,在哈希表中添加此项信息,内存释放时删除该信息。当用户通过openssl函数查找内存泄露点时,只需查询该哈希表即可。用户通过openssl回调函数还能处理那些泄露的内存。
openssl供用户调用的内存分配等函数主要在crypto/mem.c中实现,其内置的分配函数在crypto/mem_dbg.c中实现。默认情况下mem.c中的函数调用mem_dbg.c中的实现。如果用户实现了自己的内存分配函数以及查找内存泄露的函数,可以通过调用CRYPTO_set_mem_functions函数和CRYPTO_set_mem_debug_functions函数来设置。下面主要介绍了openssl内置的内存分配和释放函数。
5.2 内存数据结构
openssl内存分配数据结构是一个内部数据结构,定义在crypto/mem_dbg.c中。如下所示:
typedef struct app_mem_info_st
{
unsigned long thread;
const char *file;
int line;
const char *info;
struct app_mem_info_st *next; /* tail of thread's stack */
int references;
} APP_INFO;
typedef struct mem_st
{
void *addr;
int num;
const char *file;
int line;
unsigned long thread;
unsigned long order;
time_t time;
APP_INFO *app_info;
} MEM;
各项意义:
addr:分配内存的地址。
num:分配内存的大小。
file:分配内存的文件。
line:分配内存的行号。
thread:分配内存的线程ID。
order:第几次内存分配。
time:内存分配时间。
app_info:用于存放用户应用信息,为一个链表,里面存放了文件、行号以及线程ID等信息。
references:被引用次数。
5.3 主要函数
1) CRYPTO_mem_ctrl
本函数主要用于控制内存分配时,是否记录内存信息。如果不记录内存信息,将不能查找内存泄露。开启内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON),关闭内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)。一旦CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON)被调用,直到用户调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)前,用户 所有的opessl内存分配都会被记录。
2) CRYPTO_is_mem_check_on
查询内存记录标记是否开启。
3) CRYPTO_dbg_malloc
本函数用于分配内存空间,如果内存记录标记开启,则记录用户申请的内存。当需要记录内存信息时,该函数本身也需要申请内存插入哈希表,为了防止递归申请错误,它申请内存记录信息前必须暂时关闭内存记录标记,申请完毕再放开。
4) CRYPTO_dbg_free
释放内存,如果内存记录标记开启,还需要删除哈希表中对应的记录。
5) CRYPTO_mem_leaks
将内存泄露输出到BIO中。
6) CRYPTO_mem_leaks_fp
将内存泄露输出到FILE中(文件或者标准输出),该函数调用了CRYPTO_mem_leaks。
7) CRYPTO_mem_leaks_cb
处理内存泄露,输入参数为用户自己实现的处理内存泄露的函数地址。该函数只需要处理一个内存泄露,openssl通过lh_doall_arg调用用户函数来处理所有记录(泄露的内存)。
5.4 编程示例
1)示例1
#include <string.h>
#include <openssl/crypto.h>
int main()
{
char *p;
int i;
p=OPENSSL_malloc(4);
p=OPENSSL_remalloc(p,40);
p=OPENSSL_realloc(p,32);
for(i=0;i<32;i++)
memset(&p[i],i,1);
/* realloc时将以前的内存区清除(置乱) */
p=OPENSSL_realloc_clean(p,32,77);
p=OPENSSL_remalloc(p,40);
OPENSSL_malloc_locked(3);
OPENSSL_free(p);
return 0;
}
上述示例使用了基本的openssl内存分配和释放函数。
OPENSSL_malloc: 分配内存空间。
OPENSSL_remalloc: 重新分配内存空间。
OPENSSL_realloc_clean: 重新分配内存空间,将老的数据进行拷贝,置乱老的数据空间并释放。
OPENSSL_malloc_locked 与锁有关。
OPENSSL_free: 释放空间。
2)示例2
include <openssl/crypto.h>
#include <openssl/bio.h>
int main()
{
char *p;
BIO *b;
CRYPTO_malloc_debug_init();
CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
p=OPENSSL_malloc(4);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF);
b=BIO_new_file("leak.log","w");
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
CRYPTO_mem_leaks(b);
OPENSSL_free(p);
BIO_free(b);
return 0;
}
第六章 动态模块加载
6.1 动态库加载
动态库加载函数能让用户在程序中加载所需要的模块,各个平台下的加载函数是不一样的。动态加载函数一般有如下功能:
1) 加载动态库
比如windows下的函数LoadLibraryA;linux下的函数dlopen。这些函数一般需要动态库的名字作为参数。
2) 获取函数地址
比如windows下的函数GetProcAddress已及linux下的函数dlsym。这些函数一般需要函数名作为参数,返回函数地址。
3) 卸载动态库
比如windows下的函数FreeLibrary和linux下的函数dlclose。
6.2 DSO概述
DSO可以让用户动态加载动态库来进行函数调用。各个平台下加载动态库的函数是不一样的,openssl的DSO对各个平台台下的动态库加载函数进行了封装,增加了源码的可移植性。Openssl的DSO功能主要用于动态加载压缩函数(ssl协议)和engine(硬件加速引擎)。Openssl的DSO功能除了封装基本的功能外还有其他辅助函数,主要用于解决不同系统下路径不同的表示方式以及动态库全名不一样的问题。比如windows系统下路径可以用“\\”和“/”表示,而linux下只能使用“/”;windows下动态库的后缀为.dll而linux下动态库名字一般为libxxx.so。
6.3 数据结构
dso数据结定义在crypto/dso/dso.h中,如下所示:
struct dso_st
{
DSO_METHOD *meth;
STACK *meth_data;
int references;
int flags;
CRYPTO_EX_DATA ex_data;
DSO_NAME_CONVERTER_FUNC name_converter;
DSO_MERGER_FUNC merger;
char *filename;
char *loaded_filename;
};
meth:指出了操作系统相关的动态库操作函数。
meth_data:堆栈中存放了加载动态库后的句柄。
reference:引用计数,DSO_new的时候置1,DSO_up_ref时加1,DSO_free时减1。
当调用DSO_free时,只有当前的references为1时才真正释放meth_data中存放的句柄。
flag:与加载动态库时加载的文件名以及加载方式有关,用于DSO_ctrl函数。
DSO_convert_filename:当加载动态库时会调用DSO_convert_filename函数来确定所加载的文件。而DSO_convert_filename函数会调用各个系统自己的convert函数来获取这个文件名。
对于flag有三种种操作命令:设置、读取和或的关系,对应定义如下:
#define DSO_CTRL_GET_FLAGS 1
#define DSO_CTRL_SET_FLAGS 2
#define DSO_CTRL_OR_FLAGS 3
而flag可以设置的值有如下定义:
#define DSO_FLAG_NO_NAME_TRANSLATION 0x01
#define DSO_FLAG_NAME_TRANSLATION_EXT_ONLY 0x02
#define DSO_FLAG_UPCASE_SYMBOL 0x10
#define DSO_FLAG_GLOBAL_SYMBOLS 0x20
意义说明如下:
DSO_FLAG_NO_NAME_TRANSLATION
加载的文件名与指定的文件名一致,不加后缀.dll(windows)或.so(linux或unix)。
DSO_FLAG_NAME_TRANSLATION_EXT_ONLY
加载的文件名会加上lib串,比如用户加载eay32,真正加载时会加载libeay32(适用于linux或unix)。
DSO_FLAG_UPCASE_SYMBOL
适用于OpenVMS。
DSO_FLAG_GLOBAL_SYMBOLS
适用于unix,当在unix下调用加载函数dlopen时,参数会被或上RTLD_GLOBAL。
ex_data:扩展数据,没有使用。
name_converter::指明了具体系统需要调用的名字计算函数。
loaded_filename:指明了加载动态库的全名。
6.4 编程示例
示例1:
#include <openssl/dso.h>
#include <openssl/bio.h>
int main()
{
DSO *d;
void (*f1)();
void (*f2)();
BIO *(*BIO_newx)(BIO_METHOD *a);
BIO *(*BIO_freex)(BIO_METHOD *a);
BIO *test;
d=DSO_new();
d=DSO_load(d,"libeay32",NULL,0);
f1=DSO_bind_func(d,"BIO_new");
f2=DSO_bind_var(d,"BIO_free");
BIO_newx=(BIO *(*)(BIO_METHOD *))f1;
BIO_freex=(BIO *(*)(BIO_METHOD *))f2;
test=BIO_newx(BIO_s_file());
BIO_set_fp(test,stdout,BIO_NOCLOSE);
BIO_puts(test,"abd\n\n");
BIO_freex(test);
DSO_free(d);
return 0;
}
本例动态加载libeay32动态库,获取BIO_new和BIO_free的地址并调用。
示例2:
#include <openssl/dso.h>
#include <openssl/bio.h>
int main()
{
DSO *d;
void (*f)();
BIO *(*BIO_newx)(BIO_METHOD *a);
BIO *test;
char *load_name;
const char *loaded_name;
int flags;
d=DSO_new();
#if 0
DSO_set_name_converter
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NO_NAME_TRANSLATION,NULL);
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NAME_TRANSLATION_EXT_ONLY,NULL);
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_GLOBAL_SYMBOLS,NULL);
/* 最好写成libeay32而不是libeay32.dll, 除非前面调用了DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NO_NAME_TRANSLATION,NULL)否则它会加载libeay32.dll.dll
*/
load_name=DSO_merge(d,"libeay32","D:\\zcp\\OpenSSL\\openssl-0.9.8b\\out32dll\\Debug");
#endif
d=DSO_load(d,"libeay32",NULL,0);
if(d==NULL)
{
printf("err");
return -1;
}
loaded_name=DSO_get_loaded_filename(d);
if(loaded_name!=NULL)
{
printf("loaded file is %s\n",loaded_name);
}
flags=DSO_flags(d);
printf("current falgs is %d\n",flags);
DSO_up_ref(d);
f=(void (*)())DSO_bind_var(d,"BIO_new");
BIO_newx=(BIO *(*)(BIO_METHOD *))f;
test=BIO_newx(BIO_s_file());
BIO_set_fp(test,stdout,BIO_NOCLOSE);
BIO_puts(test,"abd\n\n");
BIO_free(test);
DSO_free(d);
printf("handle in dso number is : %d\n",d->meth_data->num);
DSO_free(d);
printf("handle in dso number is : %d\n",d->meth_data->num);
return 0;
}
本例主要演示了DSO的控制函数。
第七章 抽象IO
7.1 openssl抽象IO
openssl抽象IO(I/O abstraction,即BIO)是openssl对于io类型的抽象封装,包括:内存、文件、日志、标准输入输出、socket(TCP/UDP)、加/解密、摘要和ssl通道等。Openssl BIO通过回调函数为用户隐藏了底层实现细节,所有类型的bio的调用大体上是类似的。Bio中的数据能从一个BIO传送到另外一个BIO或者是应用程序。
7.2 数据结构
BIO数据结构主要有2个,在crypto/bio.h中定义如下:
1)BIO_METHOD
typedef struct bio_method_st
{
int type;
const char *name;
int (*bwrite)(BIO *, const char *, int);
int (*bread)(BIO *, char *, int);
int (*bputs)(BIO *, const char *);
int (*bgets)(BIO *, char *, int);
long (*ctrl)(BIO *, int, long, void *);
int (*create)(BIO *);
int (*destroy)(BIO *);
long (*callback_ctrl)(BIO *, int, bio_info_cb *);
} BIO_METHOD;
该结构定义了IO操作的各种回调函数,根据需要,具体的bio类型必须实现其中的一种或多种回调函数,各项意义如下:
type:具体BIO类型;
name:具体BIO的名字;
bwrite:具体BIO写操作回调函数;
bread:具体BIO读操作回调函数;
bputs:具体BIO中写入字符串回调函数;
bgets:具体BIO中读取字符串函数;
ctrl:具体BIO的控制回调函数;
create:生成具体BIO回调函数;
destroy:销毁具体BIO回调函数;
callback_ctrl:具体BIO控制回调函数,与ctrl回调函数不一样,该函数可由调用者(而不是实现者)来实现,然后通过BIO_set_callback等函数来设置。
2)BIO
truct 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:用于存放额外数据。
7.3 BIO 函数
BIO各个函数定义在crypto/bio.h中。所有的函数都由BIO_METHOD中的回调函数来实现。函数主要分为几类:
1) 具体BIO相关函数
比如:BIO_new_file(生成新文件)和BIO_get_fd(设置网络链接)等。
2) 通用抽象函数
比如BIO_read和BIO_write等。
另外,有很多函数是由宏定义通过控制函数BIO_ctrl实现,比如BIO_set_nbio、BIO_get_fd和BIO_eof等等。
7.4 编程示例
7.4.1 mem 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());
len=BIO_write(b,"openssl",4);
len=BIO_printf(b,"%s","zcp");
len=BIO_ctrl_pending(b);
out=(char *)OPENSSL_malloc(len);
len=BIO_read(b,out,len);
OPENSSL_free(out);
BIO_free(b);
return 0;
}
说明:
b=BIO_new(BIO_s_mem());生成一个mem类型的BIO。
len=BIO_write(b,"openssl",7);将字符串"openssl"写入bio。
len=BIO_printf(b,"bio test",8);将字符串"bio test"写入bio。
len=BIO_ctrl_pending(b);得到缓冲区中待读取大小。
len=BIO_read(b,out,50);将bio中的内容写入out缓冲区。
7.4.2 file bio
#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;
}
7.4.3 socket bio
服务端:
#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;
}
客户端telnet此端口成功后,输入字符,服务端会显示出来(linux下需要输入回车)。
客户端:
#include <openssl/bio.h>
int main()
{
BIO *cbio, *out;
int len;
char tmpbuf[1024];
cbio = BIO_new_connect("localhost:http");
out = BIO_new_fp(stdout, BIO_NOCLOSE);
if(BIO_do_connect(cbio) <= 0)
{
fprintf(stderr, "Error connecting to server\n");
}
BIO_puts(cbio, "GET / HTTP/1.0\n\n");
for(;;)
{
len = BIO_read(cbio, tmpbuf, 1024);
if(len <= 0) break;
BIO_write(out, tmpbuf, len);
}
BIO_free(cbio);
BIO_free(out);
return 0;
}
说明:本示例用来获取本机的web服务信息。
cbio = BIO_new_connect("localhost:http");用来生成建立连接到本地web服务的BIO。
out = BIO_new_fp(stdout, BIO_NOCLOSE);生成一个输出到屏幕的BIO。
BIO_puts(cbio, "GET / HTTP/1.0\n\n");通过BIO发送数据。
len = BIO_read(cbio, tmpbuf, 1024);将web服务响应的数据写入缓存,此函数循环调用
直到无数据。
BIO_write(out, tmpbuf, len);通过BIO打印收到的数据。
7.4.4 md BIO
#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());
BIO_set_md(bmd,md);
b= BIO_new(BIO_s_null());
b=BIO_push(bmd,b);
len=BIO_write(b,"openssl",7);
len=BIO_gets(b,tmp,1024);
BIO_free(b);
return 0;
}
说明:本示例用md BIO对字符串"opessl"进行md5摘要。
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缓冲区。
7.4.5 cipher BIO
加/解密示例:
#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);
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);
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;
}
说明:本示例采用cipher BIO对字符串"openssl"进行加密和解密,本示例编译需要用c++编译器;
关键说明:
BIO_set_cipher(bc,c,key,iv,1);设置加密BI。
BIO_set_cipher(bdec,cd,key,iv,0);设置解密BIO。
其中key为对称密钥,iv为初始化向量。
加/解密结果通过BIO_read获取。
7.4.6 ssl BIO
编程示例:
#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;
}
本函数用ssl bio来链接mybank.icbc.com.cn的https服务,并请求首页文件。其中SSLeay_add_ssl_algorithms和OpenSSL_add_all_algorithms函数必不可少,否则不能找到ssl加密套件并且不能找到各种算法。
7.4.7 其他示例
#include <openssl/bio.h>
#include <openssl/asn1.h>
int main()
{
int ret,len,indent;
BIO *bp;
char *pp,buf[5000];
FILE *fp;
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
fp=fopen("der.cer","rb");
len=fread(buf,1,5000,fp);
fclose(fp);
pp=buf;
indent=5;
ret=BIO_dump_indent(bp,pp,len,indent);
BIO_free(bp);
return 0;
}
第八章 配置文件
8.1 概述
Openssl采用自定义的配置文件来获取配置信息。Openssl的配置文件主要由如下内容组成:
注释信息,注释信息由#开头;
段信息,段信息由[xxx]来表示,其中xxx为段标识;
属性-值信息,表示方法为a = b,这种信息可以在一个段内也可以不属于任何段。
典型配置文件为apps/openssl.cnf(同时该文件也是openssl最主要的配置文件)。摘取部分内容如下:
# OpenSSL example configuration file.
oid_section = new_oids
[ CA_default ]
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
default_days = 365 #注意,这里是一个数字
8.2 openssl配置文件读取
Openssl读取配置文件的实现源码在crypto/conf中,主要函数定义在conf.h中。函数一般以CONF或NCONF(new conf,新函数)开头。本文主要介绍了新的conf函数的使用方。主要的数据结构在crypto/conf.h中定义如下:
typedef struct
{
char *section;
char *name;
char *value;
} CONF_VALUE;
section表明配置文件的段,name表示这个段中的一个属性,value则是这个属性的值。Openssl采用哈希表来存放这些信息,便于快速查找。
8.3 主要函数
1) NCONF_new
生成一个CONF结构。
2) CONF_free
释放空间,以及释放存储在散列表中的数据。
3) CONF_load
函数定义:LHASH *CONF_load(LHASH *conf, const char *file, long *eline),该函数根据输入配置文件名,读取信息存入散列表,如果有错,eline为错误行。
4) CONF_load_bio/ CONF_load_fp
根据bio或者文件句柄读取配置信息并存入散列表。
5) CONF_get_section
给定段信息,得到散列表中的所有对应值。用于获取配置文件中指定某个段下的所有信息,这些信息存放在CONF_VALUE的堆栈中。
6) CONF_get_string
给定段以及属性值,得到对应的字符串信息。
7) CONF_get_number
给定段和属性值,获取对应的数值信息。
8) CONF_get1_default_config_file
获取默认的配置文件名,比如openssl.cnf。
8.4 编程示例
示例1:
#include <openssl/conf.h>
int main()
{
CONF *conf;
long eline,result;
int ret;
char *p;
BIO *bp;
conf=NCONF_new(NULL);
#if 0
bp=BIO_new_file("openssl.cnf","r");
NCONF_load_bio(conf,bp,&eline);
#else
ret=NCONF_load(conf,"openssl.cnf",&eline);
if(ret!=1)
{
printf("err!\n");
return -1;
}
#endif
p=NCONF_get_string(conf,NULL,"certs");
if(p==NULL)
printf("no global certs info\n");
p=NCONF_get_string(conf,"CA_default","certs");
printf("%s\n",p);
p=NCONF_get_string(conf,"CA_default","default_days");
printf("%s\n",p);
ret=NCONF_get_number_e(conf,"CA_default","default_days",&result);
printf("%d\n",result);
ret=NCONF_get_number(conf,"CA_default","default_days",&result);
printf("%d\n",result);
NCONF_free(conf);
return 0;
}
本示例用来读取配置文件信息,这些信息可以是字符串也可以是数字。
示例2:
NCONF_get_section的用法:
#include <openssl/conf.h>
int main()
{
CONF *conf;
BIO *bp;
STACK_OF(CONF_VALUE) *v;
CONF_VALUE *one;
int i,num;
long eline;
conf=NCONF_new(NULL);
bp=BIO_new_file("openssl.cnf","r");
if(bp==NULL)
{
printf("err!\n");
return -1;
}
NCONF_load_bio(conf,bp,&eline);
v=NCONF_get_section(conf,"CA_default");
num=sk_CONF_VALUE_num(v);
printf("section CA_default :\n");
for(i=0;i<num;i++)
{
one=sk_CONF_VALUE_value(v,i);
printf("%s = %s\n",one->name,one->value);
}
BIO_free(bp);
printf("\n");
return 0;
}
第九章 随机数
9.1 随机数
随机数是一种无规律的数,但是真正做到完全无规律也较困难,所以一般将它称之为伪随机数。随机数在密码学用的很多,比如ssl握手中的客户端hello和服务端hello消息中都有随机数;ssl握手中的预主密钥是随机数;RSA密钥生成也用到随机数。如果随机数有问题,会带来很大的安全隐患。
软件生成随机数一般预先设置随机数种子,再生成随机数。设置随机数种子可以说是对生成随机数过程的一种扰乱,让产生的随机数更加无规律可循。
生成随机数有多种方法,可以是某种算法也可以根据某种或多种随机事件来生成。比如,鼠标的位置、系统的当前时间、本进程/线程相关信息以及机器噪声等。
安全性高的应用一般都采用硬件方式(随机数发生器)来生成随机数。
9.2 openssl随机数数据结构与源码
openssl生成随机数的源码位于crypto/rand目录下。rand.h定义了许多与随机数生成相关的函数。openssl通过使用摘要算法来生成随机数,可用的摘要算法有:sha1、md5、mdc2和md2。具体采用那种摘要算法在crypto/rand_lcl.h中由宏来控制。Openssl维护一个内部随机状态数据(md_rand.c中定义的全局变量state和md),通过对这些内部数据计算摘要来生成随机数。
Openssl随机数相关的数据结构如下,定义在rand.h中:
struct rand_meth_st
{
void (*seed)(const void *buf, int num);
int (*bytes)(unsigned char *buf, int num);
void (*cleanup)(void);
void (*add)(const void *buf, int num, double entropy);
int (*pseudorand)(unsigned char *buf, int num);
int (*status)(void);
};
本结构主要定义了各种回调函数,如果用户需要实现自己的随机数生成函数,他需要实现本结构中的各个函数。Openssl给出了一个默认的基于摘要的rand_meth实现(crypto/md_rand.c)。各项意义如下:
seed:种子函数,为了让openssl内部维护的随机数据更加无序,可使用本函数。buf为用户输入的随机数地址,num为其字节数。Openssl将用户提供的buf中的随机内容与其内部随机数据进行摘要计算,更新其内部随机数据。本函数无输出;
bytes:生成随机数,openssl根据内部维护的随机数状态来生成结果。buf用于存放生成的随机数。num为输入参数,用来指明生成随机数的字节长度;
cleanup:清除函数,本函数将内部维护的随机数据清除;
add:与seed类似,也是为了让openssl内部随机数据更加无序,其中entropy(信息熵)可以看作用户本次加入的随机数的个数。Openssl默认的随机数熵为32字节,在rand_lcl.h中由ENTROPY_NEEDED定义。Openssl给出随机数之前,用户提供的所有的随机种子数之和必须达到32字节。在openssl实现的md_rand中,即使用户不调用种子函数来直接生成随机数,openssl也会调用RAND_poll函数来完成该操作。
pseudorand:本函数与bytes类似也是来生成随机数。
status:查看熵值是否达到预定值,openssl中为32字节,如果达到则返回1,否则返回0。在openssl实现的md_rand中该函数会调用RAND_poll函数来使熵值合格。如果本函数返回0,则说明此时用户不应生成随机数,需要调用seed和add函数来添加熵值。
cypto/rand目录下的主要源码有:
1) md_rand.c
它实现了基于摘要的随机数生成。
2) rand_lib.c
该文件中的源码简单调用了rand_meth中的回调函数。
3) rand_win.c/rand_unix.c/rand_os2.c等
这些源码主要提供了平台相关的RAND_poll函数实现和其他系统特有函数的实现。比如rand_win.c实现了RAND_screen函数,用户根据屏幕来设置随机数种子。
4) randfile.c
用于从随机文件中加载种子、生成随机数文件以及获取随机文件名。比如默认的随机数文件为.rnd文件,如果找不到该文件,openbsd可能会返回/dev/arandom。
9.3 主要函数
1) int RAND_load_file(const char *file, long bytes)
本函数将file指定的随机数文件中的数据读取bytes字节(如果bytes大于1024,则读取1024字节),调用RAND_add进行计算,生成内部随机数。
2) RAND_write_file
生成一个随机数文件。
3) const char *RAND_file_name(char *file,size_t num)
获取随机数文件名,如果随机数文件长度小于num则返回空,否则返回文件名。
4) RAND_poll
用于计算内部随机数,各个平台有各自的实现。
5) RAND_screen/RAND_event
Windows特有函数,用来计算内部随机数,他们调用了RAND_seed。
6) RAND_seed/RAND_add
用来计算内部随机数。
7) RAND_bytes/RAND_pseudo_bytes
用来计算随机数。
8) RAND_cleanup
清除内部随机数。
10) RAND_set_rand_method
用来设置rand_meth,当用户实现了自己的随机数生成函数时(实现rand_meth中的回调函数),调用该方法来替换openssl 所提供的随机数功能。
11) RAND_status
用来查看内部随机数熵值是否已达到预定值,如果未达到,则不应该生成随机数。
9.4 编程示例
#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/rand.h>
int main()
{
char buf[20],*p;
unsigned char out[20],filename[50];
int ret,len;
BIO *print;
RAND_screen();
strcpy(buf,"我的随机数");
RAND_add(buf,20,strlen(buf));
strcpy(buf,"23424d");
RAND_seed(buf,20);
while(1)
{
ret=RAND_status();
if(ret==1)
{
printf("seeded enough!\n");
break;
}
else
{
printf("not enough sedded!\n");
RAND_poll();
}
}
p=RAND_file_name(filename,50);
if(p==NULL)
{
printf("can not get rand file\n");
return -1;
}
ret=RAND_write_file(p);
len=RAND_load_file(p,1024);
ret=RAND_bytes(out, 20);
if(ret!=1)
{
printf("err.\n");
return -1;
}
print=BIO_new(BIO_s_file());
BIO_set_fp(print,stdout,BIO_NOCLOSE);
BIO_write(print,out,20);
BIO_write(print,"\n",2);
BIO_free(print);
RAND_cleanup();
return 0;
}
第十章 文本数据库
10.1 概述
Openss实现了一个简单的文本数据库,它可以从文件读取数据和将数据写到文件中,并且可以根据关键字段来查询数据。Openssl的文本数据库供apps/目录下的文件调用,比如apps.c、ca.c和ocsp.c。openssl文本数据库典型的例子为apps/demoCA/index.txt。文本数据库一行代表数据库的一行,各个列之间必须用一个\t隔开,用#进行注释(#必须在开始位置),以空行结束。比如下面的例子:
赵春平 28 湖北
zcp 28 荆门
文本数据库的查找用到了哈希表。openssl读取的所有行数据存放在堆栈中,并为每一列数据建立一个单独的哈希表。每个哈希表中存放了所有行数据的地址。查询时,用户指定某一列,openssl根据对应的哈希表进行查找。
10.2 数据结构
数据结构在crypto/txt_db/txt_db.h中定义,如下:
typedef struct txt_db_st
{
int num_fields;
STACK *data;
LHASH **index;
int (**qual)(char **);
long error;
long arg1;
long arg2;
char **arg_row;
} TXT_DB;
意义如下:
num_fields:表明文本数据库的列数。
data:用来存放数据,每一行数据组织成为一个字符串数组(每个数组值对应该行的一列), 并将此数组地址push到堆栈中。
index:哈希表数组,每一列对应一个哈希表。每一列都可以建哈希表,如果不建哈希表将不能查找该列数据。
qual:一个函数地址数组,数组的每个元素对应一列,进行插入该列哈希表前的过滤。这些函数用于判断一行数据的一列或者多列是否满足某种条件,如果满足将不能插入到哈希表中去(但是能存入堆栈)。每一列都可以设置一个这样的函数。这些函数由用户实现。比如,一个文本数据库中,有名字列和年龄列,并且要求名字长度不能小于2,年龄不能小于0和大于200。用户为名字列实现了一个qual函数,只用来检查名字长度,对于年龄列实现一个qual函数,只用来检查年龄。当用户要插入一条记录,名字长度为1,但是年龄合法,那么该记录能插入到年龄列对应的哈希表中,而不能插入名字列对应的哈希表。
error、arg1、arg2和arg_row用于存放错误信息。
10.3 函数说明
1) TXT_DB *TXT_DB_read(BIO *in, int num)
用于从BIO中读入数据,转换为TXT_DB,num用于明确指明列数,本函数不建立哈希表。
2) long TXT_DB_write(BIO *out, TXT_DB *db)
将TXT_DB内容写入BIO;
3) int TXT_DB_create_index(TXT_DB *db,int field,int (*qual)(char **),
LHASH_HASH_FN_TYPE hash, LHASH_COMP_FN_TYPE cmp)
给field指定的列建立哈希表。db为需要建索引的TXT_DB,hash为一行数据的hash运算回调函数,cmp为一行数据的比较函数。
4) char **TXT_DB_get_by_index(TXT_DB *db, int idx, char **value)
根据关键字段来查询数据,查询结果返回一行数据db为文本数据库,idx表明采用哪一列的哈希表来查找;value为查询条件。
5) int TXT_DB_insert(TXT_DB *db,char **value)
往TXT_DB中插入一行数据。value数组以NULL表示结束。
6) void TXT_DB_free(TXT_DB *db)
清除TXT_DB。
10.4 编程示例
/* txtdb.dat的内容
赵春平 28 湖北 无
zcp 28 荆门 无
*/
#include <openssl/bio.h>
#include <openssl/txt_db.h>
#include <openssl/lhash.h>
/* 名字过滤 */
static int name_filter(char **in)
{
if(strlen(in[0])<2)
return 0;
return 1;
}
static unsigned long index_name_hash(const char **a)
{
const char *n;
n=a[0];
while (*n == '0') n++;
return(lh_strhash(n));
}
static int index_name_cmp(const char **a, const char **b)
{
const char *aa,*bb;
for (aa=a[0]; *aa == '0'; aa++);
for (bb=b[0]; *bb == '0'; bb++);
return(strcmp(aa,bb));
}
int main()
{
TXT_DB *db=NULL,*out=NULL;
BIO *in;
int num,ret;
char **added=NULL,**rrow=0,**row=NULL;
in=BIO_new_file("txtdb.dat","r");
num=1024;
db=TXT_DB_read(in,4);
added=(char **)OPENSSL_malloc(sizeof(char *)*(3+1));
added[0]=(char *)OPENSSL_malloc(10);
#if 1
strcpy(added[0],"skp");
#else
strcpy(added[0],"a"); /* 不能插入名字对应的哈希表 */
#endif
added[1]=(char *)OPENSSL_malloc(10);
strcpy(added[1],"22");
added[2]=(char *)OPENSSL_malloc(10);
strcpy(added[2],"chairman");
added[3]=NULL;
ret=TXT_DB_insert(db,added);
if(ret!=1)
{
printf("err!\n");
return -1;
}
ret=TXT_DB_create_index(db,0, name_filter,index_name_hash,index_name_cmp);
if(ret!=1)
{
printf("err\n");
return 0;
}
row=(char **)malloc(2*sizeof(char *));
row[0]=(char *)malloc(10);
strcpy(row[0],"skp");
row[1]=NULL;
rrow=TXT_DB_get_by_index(db,0,row);
if(rrow!=NULL)
printf("%s %s %s\n",rrow[0],rrow[1],rrow[2]);
out=BIO_new_file("txtdb2.dat","w");
ret=TXT_DB_write(out,db);
TXT_DB_free(db);
BIO_free(in);
BIO_free(out);
return 0;
}
本示例只对第一列做了哈希。需要注意的是,added数组及其元素申请空间时尽量采用OPENSSL_malloc而不是malloc,且其申请的空间由TXT_DB_free(调用OPENSSL_free)释放。
第十一章 大数
11.1 介绍
大数一般指的是位数很多的数。计算机表示的数的大小是有限的,精度也是有限的,它不能支持大数运算。密码学中采用了很多大数计算,为了让计算机实现大数运算,用户需要定义自己的大数表示方式并及实现各种大数运算。Openssl为我们提供了这些功能,主要用于非对称算法。
11.2 openssl大数表示
crypto/bn.h中定义了大数的表示方式,如下:
struct bignum_st
{
BN_ULONG *d;
int top;
int dmax;
int neg;
int flags;
};
各项意义如下:
d:BN_ULONG(应系统而异,win32下为4个字节)数组指针首地址,大数就存放在这里面,不过是倒放的。比如,用户要存放的大数为12345678000(通过BN_bin2bn放入),则d的内容如下:0x30 0x30 0x30 0x38 0x37 0x36 0x35 0x34 0x33 0x32 0x31;
top:用来指明大数占多少个BN_ULONG空间,上例中top为3。
dmax:d数组的大小。
neg:是否为负数,如果为1,则是负数,为0,则为正数。
flags:用于存放一些标记,比如flags含有BN_FLG_STATIC_DATA时,表明d的内存是静态分配的;含有BN_FLG_MALLOCED时,d的内存是动态分配的。
11.3 大数函数
大数函数一般都能根据函数名字知道其实现的功能。下面简单介绍了几个函数。
1) BN_rand/BN_pseudo_rand
生成一个随机的大数。
2) BN_rand_range/BN_pseudo_rand_range
生成随机数,但是给出了随机数的范围。
3) BN_dup
大数复制。
4) BN_generate_prime
生成素数。
5) int BN_add_word(BIGNUM *a, BN_ULONG w)
给大数a加上w,如果成功,返回1。
示例:
#include <openssl/bn.h>
int main()
{
int ret;
BIGNUM *a;
BN_ULONG w;
a=BN_new();
BN_one(a);
w=2685550010;
ret=BN_add_word(a,w);
if(ret!=1)
{
printf("a+=w err!\n");
BN_free(a);
return -1;
}
BN_free(a);
return 0;
}
6) BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret)
将内存中的数据转换为大数,为内存地址,len为数据长度,ret为返回值。
示例:
#include <openssl/bn.h>
int main()
{
BIGNUM *ret1,*ret2;
ret1=BN_new();
ret1=BN_bin2bn("242424ab",8, ret1);
ret2=BN_bin2bn("242424ab",8,NULL);
BN_free(ret1);
BN_free(ret2);
return 0;
}
注意:输入参数“242424ab”是asc码,对应的大数值为16进制的0x3234323432346162
7) int BN_bn2bin(const BIGNUM *a, unsigned char *to)
将大数转换为内存形式。输入参数为大数a,to为输出缓冲区地址,缓冲区需要预先分配,返回值为缓冲区的长度。
示例:
#include <openssl/bn.h>
int main()
{
BIGNUM *ret1=NULL;
char bin[50],*buf=NULL;
int len;
ret1=BN_bin2bn("242424ab",8, NULL);
len=BN_bn2bin(ret1,bin);
len=BN_num_bytes(ret1);
buf=malloc(len);
len=BN_bn2bin(ret1,buf);
free(buf);
BN_free(ret1);
return 0;
}
本例的缓冲区分配有两种方法:静态分配和动态分配。动态分配时,先调用
BN_num_bytes函数获取大数对应的缓冲区的大小。
8) char *BN_bn2dec(const BIGNUM *a)
将大数转换成整数字符串。返回值中存放整数字符串,它由内部分配空间,用户必须在外部用OPENSSL_free函数释放该空间。
示例:
#include <openssl/bn.h>
#include <openssl/crypto.h>
int main()
{
BIGNUM *ret1=NULL;
char *p=NULL;
int len=0;
ret1=BN_bin2bn("242424ab",8, NULL);
p=BN_bn2dec(ret1);
printf("%s\n",p); /* 3617571600447332706 */
BN_free(ret1);
OPENSSL_free(p);
getchar();
return 0;
}
9) char *BN_bn2hex(const BIGNUM *a)
将大数转换为十六进制字符串。返回值为生成的十六进制字符串,外部需要用OPENSSL_free函数释放
示例:
#include <openssl/bn.h>
#include <openssl/crypto.h>
int main()
{
BIGNUM *ret1=NULL;
char *p=NULL;
int len=0;
ret1=BN_bin2bn("242424ab",8, NULL);
p=BN_bn2hex(ret1);
printf("%s\n",p);
BN_free(ret1);
OPENSSL_free(p);
getchar();
return 0;
}
输出的结果为:323432346162
10) BN_cmp
比较两个大数。
11)BIGNUM *BN_mod_inverse(BIGNUM *in, const BIGNUM *a,
const BIGNUM *n, BN_CTX *ctx)
计算ax=1(mod n)。
用户使用openssl函数编程时,一般用不着进行大数运算。BN_bin2bn、BN_hex2bn、BN_dec2bn、BN_bin2bn、BN_bn2bin、BN_bn2hex和BN_bn2dec比较常用。比如给定RSA密钥的内存形式,用户可以调用BN_bin2bn来构造RSA密钥的大数元素来进行RSA运算,或者已经生成了RSA密钥,用户调用BN_bn2bin将RSA各个元素导出到内存中再写入密钥文件。
11.4 使用示例
1)示例1
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *bn;
BIO *b;
char a[20];
int ret;
bn=BN_new();
strcpy(a,"32");
ret=BN_hex2bn(&bn,a);
b=BIO_new(BIO_s_file());
ret=BIO_set_fp(b,stdout,BIO_NOCLOSE);
BIO_write(b,"aaa",3);
BN_print(b,bn);
BN_free(bn);
return 0;
}
2)示例2
加法运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*b,*add;
BIO *out;
char c[20],d[20];
int ret;
a=BN_new();
strcpy(c,"32");
ret=BN_hex2bn(&a,c);
b=BN_new();
strcpy(d,"100");
ret=BN_hex2bn(&b,d);
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
add=BN_new();
ret=BN_add(add,a,b);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x32 + 0x100 = 0x");
BN_print(out,add);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(add);
BIO_free(out);
return 0;
}
3) 示例3
减法运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*b,*sub;
BIO *out;
char c[20],d[20];
int ret;
a=BN_new();
strcpy(c,"100");
ret=BN_hex2bn(&a,c);
b=BN_new();
strcpy(d,"32");
ret=BN_hex2bn(&b,d);
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
sub=BN_new();
ret=BN_sub(sub,a,b);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x100 - 0x32 = 0x");
BN_print(out,sub);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(sub);
BIO_free(out);
return 0;
}
4)示例4
乘法运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*b,*mul;
BN_CTX *ctx;
BIO *out;
char c[20],d[20];
int ret;
ctx=BN_CTX_new();
a=BN_new();
strcpy(c,"32");
ret=BN_hex2bn(&a,c);
b=BN_new();
strcpy(d,"100");
ret=BN_hex2bn(&b,d);
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
mul=BN_new();
ret=BN_mul(mul,a,b,ctx);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x32 * 0x100 = 0x");
BN_print(out,mul);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(mul);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
5)示例5
除法运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*b,*div,*rem;
BN_CTX *ctx;
BIO *out;
char c[20],d[20];
int ret;
ctx=BN_CTX_new();
a=BN_new();
strcpy(c,"100");
ret=BN_hex2bn(&a,c);
b=BN_new();
strcpy(d,"17");
ret=BN_hex2bn(&b,d);
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
div=BN_new();
rem=BN_new();
ret=BN_div(div,rem,a,b,ctx);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x100 / 0x17 =0x");
BN_print(out,div);
BIO_puts(out,"\n");
BIO_puts(out,"bn 0x100 % 0x17 =0x");
BN_print(out,rem);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(div);
BN_free(rem);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
6)示例6
平方运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*sqr;
BN_CTX *ctx;
BIO *out;
char c[20];
int ret;
ctx=BN_CTX_new();
a=BN_new();
strcpy(c,"100");
ret=BN_hex2bn(&a,c);
sqr=BN_new();
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
ret=BN_sqr(sqr,a,ctx);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x100 sqr =0x");
BN_print(out,sqr);
BIO_puts(out,"\n");
BN_free(a);
BN_free(sqr);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
7)示例7
次方运算
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a,*exp,*b;
BN_CTX *ctx;
BIO *out;
char c[20],d[20];
int ret;
ctx=BN_CTX_new();
a=BN_new();
strcpy(c,"100");
ret=BN_hex2bn(&a,c);
b=BN_new();
strcpy(d,"3");
ret=BN_hex2bn(&b,d);
exp=BN_new();
out=BIO_new(BIO_s_file());
ret=BIO_set_fp(out,stdout,BIO_NOCLOSE);
ret=BN_exp(exp,a,b,ctx);
if(ret!=1)
{
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x100 exp 0x3 =0x");
BN_print(out,exp);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(exp);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
第十二章 BASE64编解码
12.1 BASE64编码介绍
BASE64编码是一种常用的将十六进制数据转换为可见字符的编码。与ASCII码相比,它占用的空间较小。BASE64编码在rfc3548中定义。
12.2 BASE64编解码原理
将数据编码成BASE64编码时,以3字节数据为一组,转换为24bit的二进制数,将24bit的二进制数分成四组,每组6bit。对于每一组,得到一个数字:0-63。然后根据这个数字查表即得到结果。表如下:
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
比如有数据:0x30 0x82 0x02
编码过程如下:
1)得到16进制数据: 30 82 02
2)得到二进制数据: 00110000 10000010 00000010
3)每6bit分组: 001100 001000 001000 000010
4)得到数字: 12 8 8 2
5)根据查表得到结果 : M I I C
BASE64填充:在不够的情况下在右边加0。
有三种情况:
1) 输入数据比特数是24的整数倍(输入字节为3字节整数倍),则无填充;
2) 输入数据最后编码的是1个字节(输入数据字节数除3余1),即8比特,则需要填充2个"==",因为要补齐6比特,需要加2个00;
3)输入数据最后编码是2个字节(输入数据字节数除3余2),则需要填充1个"=",因为补齐6比特,需要加一个00。
举例如下:
对0x30编码:
1) 0x30的二进制为:00110000
2) 分组为:001100 00
3) 填充2个00:001100 000000
4) 得到数字:12 0
5) 查表得到的编码为MA,另外加上两个==
所以最终编码为:MA==
base64解码是其编码过程的逆过程。解码时,将base64编码根据表展开,根据有几个等号去掉结尾的几个00,然后每8比特恢复即可。
12.3 主要函数
Openssl中用于base64编解码的函数主要有:
1) 编码函数
- EVP_EncodeInit
编码前初始化上下文。
- EVP_EncodeUpdate
进行BASE64编码,本函数可多次调用。
- EVP_EncodeFinal
进行BASE64编码,并输出结果。
- EVP_EncodeBlock
进行BASE64编码。
2) 解码函数
- EVP_DecodeInit
解码前初始化上下文。
- EVP_DecodeUpdate
BASE64解码,本函数可多次调用。
- EVP_DecodeFinal
BASE64解码,并输出结果。
- EVP_DecodeBlock
BASE64解码,可单独调用。
12.4 编程示例
1) 示例1
#include <string.h>
#include <openssl/evp.h>
int main()
{
EVP_ENCODE_CTX ectx,dctx;
unsigned char in[500],out[800],d[500];
int inl,outl,i,total,ret,total2;
EVP_EncodeInit(&ectx);
for(i=0;i<500;i++)
memset(&in[i],i,1);
inl=500;
total=0;
EVP_EncodeUpdate(&ectx,out,&outl,in,inl);
total+=outl;
EVP_EncodeFinal(&ectx,out+total,&outl);
total+=outl;
printf("%s\n",out);
EVP_DecodeInit(&dctx);
outl=500;
total2=0;
ret=EVP_DecodeUpdate(&dctx,d,&outl,out,total);
if(ret<0)
{
printf("EVP_DecodeUpdate err!\n");
return -1;
}
total2+=outl;
ret=EVP_DecodeFinal(&dctx,d,&outl);
total2+=outl;
return 0;
}
本例中先编码再解码。
编码调用次序为EVP_EncodeInit、EVP_EncodeUpdate(可以多次)和EVP_EncodeFinal。
解码调用次序为EVP_DecodeInit、EVP_DecodeUpdate(可以多次)和EVP_DecodeFinal。
注意:采用上述函数BASE64编码的结果不在一行,解码所处理的数据也不在一行。用上述函数进行BASE64编码时,输出都是格式化输出。特别需要注意的是,BASE64解码时如果某一行字符格式超过80个,会出错。如果要BASE64编码的结果不是格式化的,可以直接调用函数:EVP_EncodeBlock。同样对于非格式化数据的BASE64解码可以调用EVP_DecodeBlock函数,不过用户需要自己去除后面填充的0。
2) 示例2
#include <string.h>
#include <openssl/evp.h>
int main()
{
unsigned char in[500],out[800],d[500],*p;
int inl,i,len,pad;
for(i=0;i<500;i++)
memset(&in[i],i,1);
printf("please input how much(<500) to base64 : \n");
scanf("%d",&inl);
len=EVP_EncodeBlock(out,in,inl);
printf("%s\n",out);
p=out+len-1;
pad=0;
for(i=0;i<4;i++)
{
if(*p=='=')
pad++;
p--;
}
len=EVP_DecodeBlock(d,out,len);
len-=pad;
if((len!=inl) || (memcmp(in,d,len)))
printf("err!\n");
printf("test ok.\n");
return 0;
}
第十三章 ASN1库
13.1 ASN1简介
ASN.1(Abstract Syntax Notation One,X.208),是一套灵活的标记语言,它允许定义多种数据类型,从integer、bit string 一类的简单类型到结构化类型,如set 和sequence,并且可以使用这些类型构建复杂类型。
DER编码是ANS.1定义的将对象描述数据编码成八位串值的编码规则,它给出了对ANS.1值(对象的类型和值)的唯一编码规则。
在ANS.1中,一个类型是一组值,对于某些类型,值的个数是已知的,而有些类型中值的个数是不固定的。ANS.1中有四种类型:
1) 简单类型
BIT STRING 任意0、1位串;
IA5String 任意IA5(ASCII)字符串;
INTEGER 任意一个整数;
NULL 空值;
OBJECT IDENTIFIER 一个对象标识号(一串整数),标识算法或属性类型等对象;
OCTET STRING 8位串;
PrintableString 任意可打印字符串;
T61String 任意T.61(8位)字符串;
UTCTime 一个“协同世界时”或“格林威治标准时(G.M.T)”。
2) 结构类型
结构类型由组件组成,ANS.1定义了四种结构类型:
SEQUENCE 一个或多个类型的有序排列;
SEQUENCE OF 一个给定类型的0个或多个有序排列;
SET 一个或多个类型的无序集合;
SET OF 一个给定类型的0个或多个无序集合。
3) 带标记类型
在一个应用内部区分类型的有效方法是使用标记,标记也同样用于区分一个结构类型内部不同的组件。例如SET或SEQUENCE类型可选项通常使用上下文标记以避免混淆。有两种标记类型的方法:隐式和显式。隐式标记类型是将其它类型的标记改变,得到新的类型。隐式标记的关键字是IMPLICIT。显式标记类型是将其它类型加上一个外部标记,得到新的类型。显式标记的关键字是EXPLICIT。
为了进行编码,隐式标记类型除了标记不同以外,可以视为与其基础类型相同。显式标记类型可以视为只有一个组件的结构类型。
4) 其它类型
类型和值用符号::=表示,符号左边的是名字,右边是类型和值。名字又可以用于定义其它的类型和值。
除了CHOICE类型、ANY类型以外,所有ANS.1类型都有一个标记,标记由一个类和一个非负的标记码组成,当且仅当标记码相同时,ANS.1类型是相同的。也就是说,影响其抽象意义的不是ANS.1类型的名字,而是其标记。
通用标记在X.208中定义,并给出相应的通用标记码。其它的标记类型分别在很多地方定义,可以通过隐式和显式标记获得。
下表列出了一些通用类型及其标记:
类型 标记码(十六进制)
INTEGER 02
BIT STRING 03
OCTET STRING 04
NULL 05
OBJECT IDENTIFIER 06
SEQUENCE and SEQUENCEOF 10
SET and SET OF 11
PrintableString 13
T61String 14
IA5String 16
UTCTime 17
13.2 DER编码
DER给出了一种将ASN.1值表示为8位串的方法。DER编码包含三个部分:
- 标识(一个或多个8位串):定义值的类和标记码,指出是原始编码还是结构化编码。
- 长度(一个或多个8位串):对于定长编码,指出内容中8位串的个数;对于不定长编码,指出长度是不定的。
- 内容(一个或多个8位串):对于原始定长编码,给出真实值;对于结构化编码,给出各组件BER编码的按位串联结果。
- 内容结束(一个或多个8位串):对于结构化不定长编码,标识内容结束;对于其它编码,无此项。
13.3 ASN1基本类型示例
1) ASN1_BOOLEAN
表明了ASN1语法中的true和flase。用户以用UltraEdit等工具编辑一个二进制文件来查看,此二进制文件的内容为:0x30 0x03 0x01 0x01 0x00,然后用asn1view工具查看此文件内容。显示如下:
其中0x01 (表示为BOOLEAN) 0x01(表示后面值的长度) 0x00(值)为本例BOOLEAN的DER编码。
2) ASN1_OBJECT
ASN1中的OBJECT表明来一个对象,每个对象有一个OID(object id)。例如:OU的OID为2.5.4.11。OBJECT对象在DER编码的时候通过计算将OID转换为另外一组数据(可用函数a2d_ASN1_OBJECTH函数)。用户编辑一个二进制文件,内容为:0x30 0x05 0x06 0x03 0x55 0x04 0x0A,用asn1view打开查看。如下:
其中0x06(表示为OBJECT类型) 0x03(值的长度) 0x55 0x04 0x0A(此三项由2.5.4.11计算而来)为此OBJECT的DER编码。
3) ASN1_INTEGER
ASN1中的INTEGER类型用于表示整数。编辑一个二进制文件,其内容为:0x30 0x03 0x02(整数) 0x01 (整数值长度)0x55 (整数值)。用an1view查看如下:
4) ASN1_ENUMERATED
ASN1枚举类型,示例如下:
5) ASN1_BIT_STRING
示例如下:
此图显示0x01 0x02的DER编码:0x03(BIT STRING 类型) 0x02(长度) 0x01 0x02(比特值)。
6) ASN1_OCTET_STRING
如下:
显示0x01 0x02的OCTET STRING编码:0x04(OCTET STRING) 0x02(长度) 0x01 0x02(值)。
7)ASN1_PRINTABLESTRING
可打印字符,如下:
显示来可打印字符“asn1“的DER编码,其编码值为0x13(PRINTABLESTRING) 0x04(值长度) 0x61 0x73 0x6E 0x31(值,即“asn1”)。
其他:
ASN1_UTCTIME:表示时间。
ASN1_GENERALIZEDTIME:表示时间。
ASN1_VISIBLESTRING:存放可见字符。
ASN1_UTF8STRING:用于存放utf8字符串,存放汉字需要将汉字转换为utf8字符串。
ASN1_TYPE:用于存放任意类型。
13.4 openssl 的ASN.1库
Openssl的ASN.1库定义了asn.1对应的基本数据结构和大量用于DER编码的宏。比如整型定义如下:
typedef struct asn1_string_st ASN1_INTEGER;
另外,还用相同的数据结构asn1_string_st定义了:
ASN1_ENUMERATED;
ASN1_BIT_STRING;
ASN1_OCTET_STRING;
ASN1_PRINTABLESTRING;
ASN1_T61STRING;
ASN1_IA5STRING;
ASN1_GENERALSTRING;
ASN1_UNIVERSALSTRING;
ASN1_BMPSTRING;
ASN1_UTCTIME;
ASN1_TIME;
ASN1_GENERALIZEDTIME;
ASN1_VISIBLESTRING;
ASN1_UTF8STRING;
ASN1_TYPE;
这些都是定义基本数据结构的必要元素。
对于每种类型,均有四种最基本的函数:new、free、i2d和d2i。其中new函数用于生成一个新的数据结构;free用于释放该结构; i2d用于将该内部数据结构转换成DER编码;d2i用于将DER编码转换成内部数据结构。另外,大部分类型都有set和get函数,用于给内部数据结构赋值和从中取值。以ASN1_INTEGER为例,它有如下基本函数:
ASN1_INTEGER ASN1_INTEGER_new(void);
void *ASN1_INTEGER_free(ASN1_INTEGER *a);
ASN1_INTEGER *d2i_ASN1_INTEGER(ASN1_INTEGER **a,
unsigned char **in,long len);
int i2d_ASN1_INTEGER(ASN1_INTEGER *a,unsigned char **out);
long ASN1_INTEGER_get(ASN1_INTEGER *a)
int ASN1_INTEGER_set(ASN1_INTEGER *a, long v);
前面的四个函数由DECLARE_ASN1_FUNCTIONS(ASN1_INTEGER)声明,并由 IMPLEMENT_ASN1_FUNCTIONS(ASN1_INTEGER)实现。
采用ASN.1定义的复杂的结构都是由基本的类型构造的,因此可以用这些基本的数据来实现对复杂结构的编码。
13.5 用openssl的ASN.1库DER编解码
当采用Openssl的ASN.1库编码一个asn.1定义的结构的时候,需要采用如下步骤:
1) 用 ASN.1语法定义内部数据结构,并声明函数;
所谓内部数据结构,指的是Openssl中用基本的数据类型按照ASN.1语法定义的其他的数据结构,这种数据结构可以方便的用于编解码。
以x509v4中的证书有效期为例,证书有效期定义如下:
AttCertValidityPeriod ::= SEQUENCE
{
notBeforeTime GeneralizedTime,
notAfterTime GeneralizedTime
}
所以我们可以定义相应的内部数据结构,如下:
typedef struct X509V4_VALID_st
{
ASN1_GENERALIZEDTIME *notBefore;
ASN1_GENERALIZEDTIME *notAfter;
}X509V4_VALID;
DECLARE_ASN1_FUNCTIONS(X509V4_VALID)
其中最后一行用于定义四个函数:
X509V4_VALID *X509V4_VALID_new(void);
void *X509V4_VALID_free(X509V4_VALID *a);
X509V4_VALID *d2i_ASN1_INTEGER(X509V4_VALID **a,unsigned char **in,long len);
int i2d_ X509V4_VALID (X509V4_VALID *a,unsigned char **out);
2) 实现内部数据结构的四个基本函数
实现内部数据结构的基本函数,是通过一系列的宏来实现的。定义的模式如下,以属性证书有效期为例,如下:
/* X509V4_VALID */
ASN1_SEQUENCE(X509V4_VALID) =
{
ASN1_SIMPLE(X509V4_VALID, notBefore, ASN1_GENERALIZEDTIME),
ASN1_SIMPLE(X509V4_VALID, notAfter, ASN1_GENERALIZEDTIME)
} ASN1_SEQUENCE_END(X509V4_VALID)
IMPLEMENT_ASN1_FUNCTIONS(X509V4_VALID)
这样通过宏就实现了一个asn.1定义结构的最基本的四个函数。
本例有五个宏,采用什么样的宏,与数据结构的asn.1定义相关。
13.6 Openssl的ASN.1宏
Openssl中的ASN.1宏用来定义某种内部数据结构以及这种结构如何编码,部分宏定义说明如下:
1) DECLARE_ASN1_FUNCTIONS
用于声明一个内部数据结构的四个基本函数,一般可以在头文件中定义。
2) IMPLEMENT_ASN1_FUNCTIONS
用于实现一个数据结构的四个基本函数。
3) ASN1_SEQUENCE
用于SEQUENCE,表明下面的编码是一个SEQUENCE。
4) ASN1_CHOICE
表明下面的编码是选择其中一项,为CHOICE类型。
5) ASN1_SIMPLE
用于简单类型或结构类型,并且是必须项。
6) ASN1_OPT
用于可选项,表明asn.1语法中,本项是可选的。
7) ASN1_EXP_OPT
用于显示标记,表明asn.1语法中,本项是显示类型,并且是可选的;
8) ASN1_EXP
用于显示标记,表明asn.1语法中,本项是显示标记。
9) ASN1_IMP_SEQUENCE_OF_OPT
用于隐示标记,表明asn.1语法中,本项是一个SEQUENCE序列,为隐 示类型,并且是可选的。
10) ASN1_IMP_OPT
用于隐示标记,表明asn.1语法中,本项是隐示类型,并且是可选的。
11) ASN1_IMP
用于隐示标记,表明asn.1语法中,本项是隐示类型。
12) ASN1_SEQUENCE_END
用于SEQUENCE结束。
13) ASN1_CHOICE_END
用于结束CHOICE类型。
13.7 ASN1常用函数
ASN1的基本的数据类型一般都有如下函数:new、free、i2d、d2i、i2a、a2i、print、set、get、cmp和dup。其中new、free、i2d、d2i函数通过宏定义实现。new函数用于分配空间,生成ASN1数据结构;free用于释放空间;i2d函数将ASN1数据结构转换为DER编码;d2i将DER编码转换为ASN1数据结构,i2a将内部结构转换为ASCII码,a2i将ASCII码转换为内部数据结构。set函数用于设置ASN1类型的值,get函数用于获取ASN1类型值;print将ASN1类型打印;cmp用于比较ASN1数据结构;dup函数进行数据结构的拷贝。
常用的函数有:
1) int a2d_ASN1_OBJECT(unsigned char *out, int olen, const char *buf, int num)
计算OID的DER编码,比如将2.99999.3形式转换为内存形式。示例:
#include <openssl/asn1.h>
void main()
{
const char oid[]={"2.99999.3"};
int i;
unsigned char *buf;
i=a2d_ASN1_OBJECT(NULL,0,oid,-1);
if (i <= 0)
return;
buf=(unsigned char *)malloc(sizeof(unsigned char)*i);
i=a2d_ASN1_OBJECT(buf,i,oid,-1);
free(buf);
return;
}
输出结果:buf内存值为:86 8D 6F 03
2) int a2i_ASN1_INTEGER(BIO *bp,ASN1_INTEGER *bs,char *buf,int size)
将bp中的ASC码转换为ASN1_INTEGER,buf存放BIO中的ASC码。示例如下:
#include <openssl/asn1.h>
int main()
{
BIO *bp;
ASN1_INTEGER *i;
unsigned char buf[50];
int size,len;
bp=BIO_new(BIO_s_mem());
len=BIO_write(bp,"0FAB08BBDDEECC",14);
size=50;
i=ASN1_INTEGER_new();
a2i_ASN1_INTEGER(bp,i,buf,size);
BIO_free(bp);
ASN1_INTEGER_free(i);
return 0;
}
3)int a2i_ASN1_STRING(BIO *bp,ASN1_STRING *bs,char *buf,int size)
将ASCII码转换为ASN1_STRING,示例:
#include <openssl/asn1.h>
int main()
{
BIO *bp;
ASN1_STRING *str;
unsigned char buf[50];
int size,len;
bp=BIO_new(BIO_s_mem());
len=BIO_write(bp,"B2E2CAD4",8);
size=50;
str=ASN1_STRING_new();
a2i_ASN1_STRING(bp,str,buf,size);
BIO_free(bp);
ASN1_STRING_free(str);
return 0;
}
转换后str->data的前四个字节即变成"测试"。
4)unsigned char *asc2uni(const char *asc, int asclen, unsigned char **uni, int *unilen)
将ASCII码转换为unicode,示例:
#include <stdio.h>
#include <openssl/crypto.h>
int main()
{
unsigned char asc[50]={"B2E2CAD4"};
unsigned char uni[50],*p,*q;
int ascLen,unlen;
ascLen=strlen(asc);
q=asc2uni(asc,ascLen,NULL,&unlen);
OPENSSL_free(q);
return 0;
}
5)int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *a, int n)
本函数根据n获取其比特位上的值,示例:
#include <openssl/asn1.h>
int main()
{
int ret,i,n;
ASN1_BIT_STRING *a;
a=ASN1_BIT_STRING_new();
ASN1_BIT_STRING_set(a,"ab",2);
for(i=0;i<2*8;i++)
{
ret=ASN1_BIT_STRING_get_bit(a,i);
printf("%d",ret);
}
ASN1_BIT_STRING_free(a);
return 0;
}
程序输出:0110000101100010
说明:a中”ab”的二进制既是0110000101100010。
6)ASN1_BIT_STRING_set
设置ASN1_BIT_STRING的值,它调用了ASN1_STRING_set函数;
7)void *ASN1_d2i_bio(void *(*xnew)(void), d2i_of_void *d2i, BIO *in, void **x)
对bio的数据DER解码,xnew无意义,d2i为DER解码函数,in为bio数据,x为数据类型,返回值为解码后的结果。如果x分配了内存,x所指向的地址与返回值一致。示例如下:
#include <stdio.h>
#include <openssl/asn1.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>
int main()
{
BIO *in;
X509 **out=NULL,*x;
in=BIO_new_file("a.cer","r");
out=(X509 **)malloc(sizeof(X509 *));
*out=NULL;
x=ASN1_d2i_bio(NULL,(d2i_of_void *)d2i_X509,in,out);
X509_free(x);
free(out);
return 0;
}
8)void *ASN1_d2i_fp(void *(*xnew)(void), d2i_of_void *d2i, FILE *in, void **x)
将in指向的文件进行DER解码,其内部调用了ASN1_d2i_bi函数,用法与ASN1_d2i_bi类似。
9)int ASN1_digest(i2d_of_void *i2d, const EVP_MD *type,
char *data,unsigned char *md, unsigned int *len)
ASN1数据类型签名。将data指针指向的ASN1数据类型用i2d函数进行DER编码,然后用type指定的摘要方法进行计算,结果存放在md中,结果的长度由len表示。
10)int ASN1_i2d_bio(i2d_of_void *i2d, BIO *out, unsigned char *x)
将ASN1数据结构DER编码,并将结果写入bio。示例如下:
#include <openssl/asn1.h>
#include <openssl/bio.h>
int main()
{
int ret;
BIO *out;
ASN1_INTEGER *a;
out=BIO_new_file("int.cer","w");
a=ASN1_INTEGER_new();
ASN1_INTEGER_set(a,(long)100);
ret=ASN1_i2d_bio(i2d_ASN1_INTEGER,out,a);
BIO_free(out);
return 0;
}
本程序将ASN1_INTEGER类型装换为DER编码并写入文件。int.cer的内容如下:
02 01 64 (十六进制)。
11) int ASN1_i2d_fp(i2d_of_void *i2d, FILE *out, void *x)
将ASN1数据结构DER编码并写入FILE,此函数调用了ASN1_i2d_bio。
12)void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, char *x)
ASN1数据复制。x为ASN1内部数据结构,本函数先将x通过i2d将它变成DER编码,然后用d2i再DER解码,并返回解码结果。
13) ASN1_ENUMERATED_set
设置ASN1_ENUMERATED的值。
14) ASN1_ENUMERATED_get
获取ASN1_ENUMERATED的值;示例如下:
clude <openssl/asn1.h>
int main()
{
long ret;
ASN1_ENUMERATED *a;
a=ASN1_ENUMERATED_new();
ASN1_ENUMERATED_set(a,(long)155);
ret=ASN1_ENUMERATED_get(a);
printf("%ld\n",ret);
return 0;
}
15)BIGNUM *ASN1_ENUMERATED_to_BN(ASN1_ENUMERATED *ai, BIGNUM *bn)
将ASN1_ENUMERATED类型转换为BN大数类型。此函数调用BN_bin2bn函数获取bn,如果ai->type表明它是负数,再调用BN_set_negative设置bn成负数。示例如下:
#include <openssl/asn1.h>
int main()
{
long ret;
ASN1_ENUMERATED *a;
BIGNUM *bn;
a=ASN1_ENUMERATED_new();
ASN1_ENUMERATED_set(a,(long)155);
ret=ASN1_ENUMERATED_get(a);
bn=BN_new();
bn=ASN1_ENUMERATED_to_BN(a,bn);
BN_free(bn);
ASN1_ENUMERATED_free(a);
return 0;
}
如果ASN1_ENUMERATED_to_BN的第二个参数为NULL,bn将在内部分配空间。
16)int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *a)
检查输入参数是不是合法的ASN1_GENERALIZEDTIME类型。
17)int ASN1_parse_dump(BIO *bp, const unsigned char *pp, long len, int indent, int dump)
本函数用于将pp和len指明的DER编码值写在BIO中,其中indent和dump用于设置打印的格式。indent用来设置打印出来当列之间空格个数,ident越小,打印内容越紧凑。dump表明当asn1单元为BIT STRING或OCTET STRING时,打印内容的字节数。示例如下:
#include <openssl/bio.h>
#include <openssl/asn1.h>
int main()
{
int ret,len,indent,dump;
BIO *bp;
char *pp,buf[5000];
FILE *fp;
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
fp=fopen("der.cer","rb");
len=fread(buf,1,5000,fp);
fclose(fp);
pp=buf;
indent=7;
dump=11;
ret=ASN1_parse_dump(bp,pp,len,indent,dump);
BIO_free(bp);
return 0;
}
其中der.cer为一个DER编码的文件,比如一个数字证书。
18)int ASN1_sign(i2d_of_void *i2d, X509_ALGOR *algor1, X509_ALGOR *algor2, ASN1_BIT_STRING *signature, char *data, EVP_PKEY *pkey, const EVP_MD *type)
对ASN1数据类型签名。i2d为ASN1数据的DER方法,signature用于存放签名结果,data为ASN1数据指针,pkey指明签名密钥,type为摘要算法,algor1和algor2无用,可全为NULL。签名时,先将ASN1数据DER编码,然后摘要,最后签名运算。
在x509.h中有很多ASN1数据类型的签名都通过此函数来定义,有X509_sign、X509_REQ_sign、X509_CRL_sign、NETSCAPE_SPKI_sign等。示例如下:
#include <openssl/asn1.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
int main()
{
int ret;
ASN1_INTEGER *a;
EVP_MD *md;
EVP_PKEY *pkey;
char *data;
ASN1_BIT_STRING *signature=NULL;
RSA *r;
int i,bits=1024;
unsigned long e=RSA_3;
BIGNUM *bne;
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;
}
pkey=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey,r);
a=ASN1_INTEGER_new();
ASN1_INTEGER_set(a,100);
md=EVP_md5();
data=(char *)a;
signature=ASN1_BIT_STRING_new();
ret=ASN1_sign(i2d_ASN1_INTEGER,NULL,NULL,signature,data,pkey,md);
printf("signature len : %d\n",ret);
EVP_PKEY_free(pkey);
ASN1_INTEGER_free(a);
free(signature);
return 0;
}本例将ASN1_INTEGER整数签名。
19)ASN1_STRING *ASN1_STRING_dup(ASN1_STRING *str)
ASN1_STRING类型拷贝。内部申请空间,需要用户调用ASN1_STRING_free释放该空间。
20)int ASN1_STRING_cmp(ASN1_STRING *a, ASN1_STRING *b)
ASN1_STRING比较。ossl_typ.h中绝大多数ASN1基本类型都定义为ASN1_STRING,所以,此函数比较通用。示例如下:
#include <openssl/asn1.h>
int main()
{
int ret;
ASN1_STRING *a,*b,*c;
a=ASN1_STRING_new();
b=ASN1_STRING_new();
ASN1_STRING_set(a,"abc",3);
ASN1_STRING_set(b,"def",3);
ret=ASN1_STRING_cmp(a,b);
printf("%d\n",ret);
c=ASN1_STRING_dup(a);
ret=ASN1_STRING_cmp(a,c);
printf("%d\n",ret);
ASN1_STRING_free(a);
ASN1_STRING_free(b);
ASN1_STRING_free(c);
return 0;
}
21)unsigned char * ASN1_STRING_data(ASN1_STRING *x)
获取ASN1_STRING数据存放地址,即ASN1_STRING数据结构中data地址。本函数由宏实现。
22)int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len)
设置ASN1字符串类型的值。str为ASN1_STRING地址,_data为设置值的首地址,len为被设置值的长度。示例如下:
ASN1_STRING *str=NULL;
str=ASN1_STRING_new();
ASN1_STRING_set(str,”abc”,3);
此示例生成的ASN1_STRING类型为OCTET_STRING。其他的ASN1_STRING类型也能用此函数设置,如下:
ASN1_PRINTABLESTRING *str=NULL;
str=ASN1_PRINTABLESTRING_new();
ASN1_STRING_set(str,”abc”,3);
23)ASN1_STRING_TABLE *ASN1_STRING_TABLE_get(int nid)
根据nid来查找ASN1_STRING_TABLE表。此函数先查找标准表tbl_standard,再查找扩展表stable。ASN1_STRING_TABLE数据结构在asn1.h中定义,它用于约束ASN1_STRING_set_by_NID函数生成的ASN1_STRING类型。
typedef struct asn1_string_table_st {
int nid;
long minsize;
long maxsize;
unsigned long mask;
unsigned long flags;
} ASN1_STRING_TABLE;
其中nid表示对象id,minsize表示此nid值的最小长度,maxsize表示此nid值的最大长度,mask为此nid可以采用的ASN1_STRING类型:B_ASN1_BMPSTRING、B_ASN1_UTF8STRING、B_ASN1_T61STRING和B_ASN1_UTF8STRING,flags用于标记是否为扩展或是否已有mask。
24)ASN1_STRING *ASN1_STRING_set_by_NID(ASN1_STRING **out, const unsigned char *in, int inlen, int inform, int nid)
根据nid和输入值获取对应的ASN1_STIRNG类型。out为输出,in为输入数据,inlen为其长度,inform为输入数据的类型,可以的值有:MBSTRING_BMP、MBSTRING_UNIV、MBSTRING_UTF8、MBSTRING_ASC,nid为数字证书中常用的nid,在a_strnid.c中由全局变量tbl_standard定义,可以的值有:NID_commonName、NID_countryName、NID_localityName、NID_stateOrProvinceName、NID_organizationName、NID_organizationalUnitName、NID_pkcs9_emailAddress、NID_pkcs9_unstructuredName、NID_pkcs9_challengePassword、NID_pkcs9_unstructuredAddress、NID_givenName、NID_surname、NID_initials、NID_serialNumber、NID_friendlyName、NID_name、NID_dnQualifier、NID_domainComponent和NID_ms_csp_name。生成的ASN1_STRING类型可以为:ASN1_T61STRING、ASN1_IA5STRING、ASN1_PRINTABLESTRING、ASN1_BMPSTRING、ASN1_UNIVERSALSTRING和ASN1_UTF8STRING。
示例1:
#include <stdio.h>
#include <openssl/asn1.h>
#include <openssl/obj_mac.h>
int main()
{
int inlen,nid,inform,len;
char in[100],out[100],*p;
ASN1_STRING *a;
FILE *fp;
/* 汉字“赵”的UTF8值,可以用UltraEdit获取*/
memset(&in[0],0xEF,1);
memset(&in[1],0xBB,1);
memset(&in[2],0xBF,1);
memset(&in[3],0xE8,1);
memset(&in[4],0xB5,1);
memset(&in[5],0xB5,1);
inlen=6;
inform=MBSTRING_UTF8;
nid=NID_commonName;
/* 如果调用下面两个函数,生成的ASN1_STRING类型将是ASN1_UTF8而不是ASN1_BMPSTRING */
ASN1_STRING_set_default_mask(B_ASN1_UTF8STRING);
ret=ASN1_STRING_set_default_mask_asc("utf8only");
if(ret!=1)
{
printf("ASN1_STRING_set_default_mask_asc err.\n");
return 0;
}
a=ASN1_STRING_set_by_NID(NULL,in,inlen,inform,nid);
p=out;
len=i2d_ASN1_BMPSTRING(a,&p);
fp=fopen("a.cer","w");
fwrite(out,1,len,fp);
fclose(fp);
ASN1_STRING_free(a);
return 0;
}
本例根据UTF8编码的汉字获取nid为NID_commonName的ASN1_STRING类型,其结果是一个ASN1_BMPSTRING类型。
示例2:
#include <stdio.h>
#include <openssl/asn1.h>
#include <openssl/obj_mac.h>
int main()
{
int inlen,nid,inform,len;
char in[100],out[100],*p;
ASN1_STRING *a;
FILE *fp;
strcpy(in,"ab");
inlen=2;
inform=MBSTRING_ASC;
nid=NID_commonName;
/* 设置生成的ASN1_STRING类型 */
ASN1_STRING_set_default_mask(B_ASN1_UTF8STRING);
a=ASN1_STRING_set_by_NID(NULL,in,inlen,inform,nid);
switch(a->type)
{
case V_ASN1_T61STRING:
printf("V_ASN1_T61STRING\n");
break;
case V_ASN1_IA5STRING:
printf("V_ASN1_IA5STRING\n");
break;
case V_ASN1_PRINTABLESTRING:
printf("V_ASN1_PRINTABLESTRING\n");
break;
case V_ASN1_BMPSTRING:
printf("V_ASN1_BMPSTRING\n");
break;
case V_ASN1_UNIVERSALSTRING:
printf("V_ASN1_UNIVERSALSTRING\n");
break;
case V_ASN1_UTF8STRING:
printf("V_ASN1_UTF8STRING\n");
break;
default:
printf("err");
break;
}
p=out;
len=i2d_ASN1_bytes(a,&p,a->type,V_ASN1_UNIVERSAL);
fp=fopen("a.cer","w");
fwrite(out,1,len,fp);
fclose(fp);
ASN1_STRING_free(a);
getchar();
return 0;
}
25)void ASN1_STRING_set_default_mask(unsigned long mask)
设置ASN1_STRING_set_by_NID函数返回的ASN1_STRING类型。mask可以取如下值:B_ASN1_BMPSTRING、B_ASN1_UTF8STRING、B_ASN1_T61STRING和B_ASN1_UTF8STRING。
26)int ASN1_STRING_set_default_mask_asc(char *p)
设置ASN1_STRING_set_by_NID函数返回的ASN1_STRING类型。字符串p可以的值有:nombstr、pkix、utf8only和default,如果设置为default,则相当于没有调用本函数。
27)int ASN1_STRING_TABLE_add(int nid, long minsize, long maxsize, unsigned long mask, unsigned long flags)
添加扩展的ASN1_STRING_TABLE项。说明:a_strnid.c中定义了基本的ASN1_STRING_TABLE项,如果用户要添加新的ASN1_STRING_TABLE项,需要调此次函数。Openssl源代码中有好几处都有这种用法,Openssl定义标准的某种表,并且提供扩展函数供用户去扩充。
示例:ASN1_STRING_TABLE_add(NID_yourNID,1,100, DIRSTRING_TYPE,0)。
28)void ASN1_STRING_TABLE_cleanup(void)
清除用户自建的扩展ASN1_STRING_TABLE表。
29)int i2a_ASN1_INTEGER(BIO *bp, ASN1_INTEGER *a)
将整数转换成为ASCII码,放在BIO中。示例如下:
#include <openssl/asn1.h>
int main()
{
ASN1_INTEGER *i;
long v;
BIO *bp;
printf("输入v的值:\n");
scanf("%ld",&v);
i=ASN1_INTEGER_new();
ASN1_INTEGER_set(i,v);
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
i2a_ASN1_INTEGER(bp,i);
BIO_free(bp);
ASN1_INTEGER_free(i);
printf("\n");
return 0;
}
输出:
输入v的值:
15
0F
30)int i2a_ASN1_STRING(BIO *bp, ASN1_STRING *a, int type)
type不起作用,将ASN1_STRING转换为ASCII码.。示例如下:
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
int main()
{
ASN1_STRING *a;
BIO *bp;
a=ASN1_STRING_new();
ASN1_STRING_set(a,"测试",4);
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
i2a_ASN1_STRING(bp,a,1);
BIO_free(bp);
ASN1_STRING_free(a);
printf("\n");
return 0;
}
输出结果:
B2E2CAD4
31)OBJ_bsearch
用于从排序好的数据结构地址数组中用二分法查找数据。示例如下:
#include <openssl/objects.h>
typedef struct Student_st
{
int age;
}Student;
int cmp_func(const void *a,const void *b)
{
Student *x,*y;
x=*(Student **)a;
y=*(Student **)b;
return x->age-y->age;
}
int main()
{
int ret,num,size;
ASN1_OBJECT *obj=NULL;
char **addr,*p;
Student a[6],**sort,**x;
a[0].age=3;
a[1].age=56;
a[2].age=5;
a[3].age=1;
a[4].age=3;
a[5].age=6;
sort=(Student **)malloc(6*sizeof(Student *));
sort[0]=&a[0];
sort[1]=&a[1];
sort[2]=&a[2];
sort[3]=&a[3];
sort[4]=&a[4];
sort[5]=&a[5];
qsort(sort,6,sizeof(Student *),cmp_func);
obj=OBJ_nid2obj(NID_rsa);
ret=OBJ_add_object(obj);
if(ret==NID_undef)
{
printf("err");
}
else
{
printf("ok\n");
}
p=&a[4];
addr=OBJ_bsearch(&p,(char *)sort,6,sizeof(Student *),cmp_func);
x=(Student **)addr;
printf("%d == %d\n",a[4].age,(*x)->age);
return 0;
}
32)OBJ_create
根据oid以及名称信息生成一个内部的object,示例:
nid=OBJ_create("1.2.3.44","testSn","testLn")。
33)OBJ_NAME_add
OBJ_NAME_cleanup
OBJ_NAME_get
OBJ_NAME_init
OBJ_NAME_remove
OBJ_NAME_new_index
OBJ_NAME_do_all
OBJ_NAME_do_all_sorted
OBJ_NAME函数用于根据名字获取对称算法或者摘要算法,主要涉及到函数有:
int EVP_add_cipher(const EVP_CIPHER *c);
int EVP_add_digest(const EVP_MD *md);
const EVP_CIPHER *EVP_get_cipherbyname(const char *name);
const EVP_MD *EVP_get_digestbyname(const char *name);
void EVP_cleanup(void);
这些函数在evp/names.c中实现,他们调用了OBJ_NAME函数。
EVP_add_cipher和EVP_add_digest函数调用OBJ_NAME_init和OBJ_NAME_add函数,将EVP_CIPHER和EVP_MD信息放入哈希表,EVP_get_cipherbyname和EVP_get_digestbyname函数调用OBJ_NAME_get函数从哈希表中查询需要的信息,EVP_cleanup函数清除存放到EVP_CIPHER和EVP_MD信息。另外,程序可以通过调用OpenSSL_add_all_ciphers和OpenSSL_add_all_digests函数将所有的对称算法和摘要算法放入哈希表。
34)int OBJ_new_nid(int num)
此函数将内部的new_nid加num,返回原nid。
35)const char *OBJ_nid2ln(int n)
根据nide得到对象的描诉。
36)OBJ_nid2obj
根据nid得到对象。
37)const char *OBJ_nid2sn(int n)
根据nid得到对象的sn(简称)。
38)int OBJ_obj2nid(const ASN1_OBJECT *a)
根据对象获取其nid;
39)OBJ_obj2txt
根据对象获取对象说明或者nid,示例:
#include <openssl/asn1.h>
int main()
{
char buf[100];
int buf_len=100;
ASN1_OBJECT *a;
a=OBJ_nid2obj(65);
OBJ_obj2txt(buf,buf_len,a,0);
printf("%s\n",buf);
OBJ_obj2txt(buf,buf_len,a,1);
printf("%s\n",buf);
return 0;
}
输出结果:
sha1WithRSAEncryption
1.2.840.113549.1.1.5
40)int OBJ_sn2nid(const char *s)
根据对象别名称获取nid
41)OBJ_txt2nid
根据sn或者ln获取对象的nid。
42)OBJ_txt2obj
根据sn或者ln得到对象。
13.8 属性证书编码
对属性证书(x509v4)编码
以下是采用Openssl的asn.1库对属性证书编/解码的源代码:
/* x509v4.h */
/* valid time */
typedef struct X509V4_VALID_st
{
ASN1_GENERALIZEDTIME *notBefore;
ASN1_GENERALIZEDTIME *notAfter;
}X509V4_VALID;
DECLARE_ASN1_FUNCTIONS(X509V4_VALID)
/* issuer */
typedef struct ISSUERSERIAL_st
{
GENERAL_NAMES *issuer;
ASN1_INTEGER *subjectSN;
ASN1_BIT_STRING *issuerUID;
}ISSUERSERIAL;
DECLARE_ASN1_FUNCTIONS(ISSUERSERIAL)
/* objdigest */
typedef struct OBJDIGEST_st
{
ASN1_ENUMERATED *digestType;
ASN1_OBJECT *otherType;
X509_ALGOR *digestAlg;
ASN1_BIT_STRING *digestBit;
}OBJDIGEST;
DECLARE_ASN1_FUNCTIONS(OBJDIGEST)
/* holder */
typedef struct ACHOLDER_st
{
ISSUERSERIAL *baseCertificateID;
GENERAL_NAMES *entityName;
OBJDIGEST *objDigest;
}ACHOLDER;
DECLARE_ASN1_FUNCTIONS(ACHOLDER)
/* version 2 form */
typedef struct V2FORM_st
{
GENERAL_NAMES *entityName;
ISSUERSERIAL *baseCertificateID;
OBJDIGEST *objDigest;
}V2FORM;
DECLARE_ASN1_FUNCTIONS(V2FORM)
typedef struct ACISSUER_st
{
int type;
union
{
V2FORM *v2Form;
}form;
} ACISSUER;
DECLARE_ASN1_FUNCTIONS(ACISSUER)
/* X509V4_CINF */
typedef struct X509V4_CINF_st
{
ASN1_INTEGER *version;
ACHOLDER *holder;
ACISSUER *issuer;
X509_ALGOR *signature;
ASN1_INTEGER *serialNumber;
X509V4_VALID *valid;
STACK_OF(X509_ATTRIBUTE) *attributes;
ASN1_BIT_STRING *issuerUID;
STACK_OF(X509_EXTENSION *extensions;
}X509V4_CINF;
DECLARE_ASN1_FUNCTIONS(X509V4_CINF)
/* x509v4 */
typedef struct X509V4_st
{
X509V4_CINF *cert_info;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
}X509V4;
DECLARE_ASN1_FUNCTIONS(X509V4)
/* x509v4.c */
/* ACISSUER */
ASN1_CHOICE(ACISSUER) = {
ASN1_IMP(ACISSUER, form.v2Form, V2FORM,0)
} ASN1_CHOICE_END(ACISSUER)
IMPLEMENT_ASN1_FUNCTIONS(ACISSUER)
/* ACHOLDER */
ASN1_SEQUENCE(ACHOLDER) = {
ASN1_IMP_OPT(ACHOLDER, baseCertificateID, ISSUERSERIAL,0),
ASN1_IMP_SEQUENCE_OF_OPT(ACHOLDER, entityName, GENERAL_NAME,1),
ASN1_IMP_OPT(ACHOLDER, objDigest, OBJDIGEST,2)
} ASN1_SEQUENCE_END(ACHOLDER)
IMPLEMENT_ASN1_FUNCTIONS(ACHOLDER)
/* V2FORM */
ASN1_SEQUENCE(V2FORM) = {
ASN1_SEQUENCE_OF_OPT(V2FORM, entityName, GENERAL_NAME),
ASN1_IMP_OPT(V2FORM, baseCertificateID, ISSUERSERIAL,0),
ASN1_IMP_OPT(V2FORM, objDigest, OBJDIGEST,1)
} ASN1_SEQUENCE_END(V2FORM)
IMPLEMENT_ASN1_FUNCTIONS(V2FORM)
/* ISSUERSERIAL */
ASN1_SEQUENCE(ISSUERSERIAL) = {
ASN1_SIMPLE(ISSUERSERIAL, issuer,GENERAL_NAMES),
ASN1_SIMPLE(ISSUERSERIAL, subjectSN, ASN1_INTEGER),
ASN1_OPT(ISSUERSERIAL, issuerUID,ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(ISSUERSERIAL)
IMPLEMENT_ASN1_FUNCTIONS(ISSUERSERIAL)
/* OBJDIGEST */
ASN1_SEQUENCE(OBJDIGEST) = {
ASN1_SIMPLE(OBJDIGEST, digestType, ASN1_ENUMERATED),
ASN1_OPT(OBJDIGEST, otherType, ASN1_OBJECT),
ASN1_SIMPLE(OBJDIGEST, digestAlg, X509_ALGOR),
ASN1_SIMPLE(OBJDIGEST, digestBit, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(OBJDIGEST)
IMPLEMENT_ASN1_FUNCTIONS(OBJDIGEST)
/* X509V4_VALID */
ASN1_SEQUENCE(X509V4_VALID) = {
ASN1_SIMPLE(X509V4_VALID, notBefore, ASN1_GENERALIZEDTIME),
ASN1_SIMPLE(X509V4_VALID, notAfter, ASN1_GENERALIZEDTIME)
} ASN1_SEQUENCE_END(X509V4_VALID)
IMPLEMENT_ASN1_FUNCTIONS(X509V4_VALID)
/* X509V4_CINF */
ASN1_SEQUENCE(X509V4_CINF) = {
ASN1_SIMPLE(X509V4_CINF,version, ASN1_INTEGER),
ASN1_SIMPLE(X509V4_CINF, holder, ACHOLDER),
ASN1_SIMPLE(X509V4_CINF, issuer, ACISSUER),
ASN1_SIMPLE(X509V4_CINF, signature, X509_ALGOR),
ASN1_SIMPLE(X509V4_CINF, serialNumber, ASN1_INTEGER),
ASN1_SIMPLE(X509V4_CINF, valid, X509V4_VALID),
ASN1_SEQUENCE_OF(X509V4_CINF, attributes, X509_ATTRIBUTE),
ASN1_OPT(X509V4_CINF, issuerUID, ASN1_BIT_STRING),
ASN1_SEQUENCE_OF_OPT(X509V4_CINF, extensions, X509_EXTENSION)
} ASN1_SEQUENCE_END(X509V4_CINF)
IMPLEMENT_ASN1_FUNCTIONS(X509V4_CINF)
ASN1_SEQUENCE(X509V4) = {
ASN1_SIMPLE(X509V4, cert_info, X509V4_CINF),
ASN1_SIMPLE(X509V4, sig_alg, X509_ALGOR),
ASN1_SIMPLE(X509V4, signature, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(X509V4)
第十四章 错误处理
14.1 概述
程序设计时,一般通过函数的返回值来判断是否调用成功。设计良好的函数以及好的错误处理能帮助调用者快速找到错误原因。错误处理应该尽可能多的包含各种信息,包括:
- 错误码;
- 出错文件以及行号;
- 错误原因;
- 出错函数;
- 出错库;
- 出错模块与类别信息;
- 错误堆栈信息等。
并且,出错信息最好能支持多种输出。可以是输出在标准输出上,也可以是文件等形式。
14.2 数据结构
openssl中,通过unsigned long类型来存放错误信息。它包含三部分内容:库代码、函数代码以及错误原因代码。其中,库代码在crypto/err.h中定义,函数代码以及错误原因代码由各个功能模块定义(同类代码不能与其他的重复,也不能超过一定的大小)。比如err.h中为BIO定义如下库代码:
/* library */
#define ERR_LIB_BIO 32
而crypto/bio.h中定义了如下函数和错误原因代号:
/* Function codes. */
#define BIO_F_ACPT_STATE 100
/* Reason codes. */
#define BIO_R_ACCEPT_ERROR 100
错误信息通过上述三部分通过计算得到,并且根据此信息能提取各个代码。计算函数在err.h中定义如下:
#define ERR_PACK(l,f,r) (((((unsigned long)l)&0xffL)*0x1000000)| \
((((unsigned long)f)&0xfffL)*0x1000)| \
((((unsigned long)r)&0xfffL)))
#define ERR_GET_LIB(l) (int)((((unsigned long)l)>>24L)&0xffL)
#define ERR_GET_FUNC(l) (int)((((unsigned long)l)>>12L)&0xfffL)
#define ERR_GET_REASON(l) (int)((l)&0xfffL)
可以看出,库的个数不能大于255(0xff),函数个数和错误原因不能大于4095(0xfff)。除非计算出来的值与已有的值没有冲突。
主要数据结构有两个,定义在crypto/err/err.h中,如下:
1)ERR_STRING_DATA
typedef struct ERR_string_data_st
{
unsigned long error;
const char *string;
} ERR_STRING_DATA;
该数据结构的内容由各个功能模块来设置。其中,error用来存放错误信息(由库代码、函数代码以及错误原因代码计算得来),string用来存放文本信息,可以是函数名也可以是错误原因。以crypto/bio_err.c为例,它定义了两个全局表,分别用来存放函数信息和错误信息:
#define ERR_FUNC(func) ERR_PACK(ERR_LIB_BIO,func,0)
#define ERR_REASON(reason) ERR_PACK(ERR_LIB_BIO,0,reason)
static ERR_STRING_DATA BIO_str_functs[]=
{
{ERR_FUNC(BIO_F_ACPT_STATE), "ACPT_STATE"},
……
}
static ERR_STRING_DATA BIO_str_reasons[]=
{
{ERR_REASON(BIO_R_ACCEPT_ERROR) ,"accept error"},
{ERR_REASON(BIO_R_BAD_FOPEN_MODE) ,"bad fopen mode"},
……
}
这两个表通过ERR_load_BIO_strings函数来添加到错误信息哈希表中去。为了便于查找,所有模块的错误信息存放在一个全局哈希表中,在crypto/err.c中实现。
2)ERR_STATE
typedef struct err_state_st
{
unsigned long pid;
int err_flags[ERR_NUM_ERRORS];
unsigned long err_buffer[ERR_NUM_ERRORS];
char *err_data[ERR_NUM_ERRORS];
int err_data_flags[ERR_NUM_ERRORS];
const char *err_file[ERR_NUM_ERRORS];
int err_line[ERR_NUM_ERRORS];
int top,bottom;
} ERR_STATE;
该结构用于存放和获取错误信息。由于可能会有多层函数调用(错误堆栈),这些信息都是一个数组。每个数组代表了一层函数的错误信息。各项意义如下:
pid:当前线程id。
err_buffer[i]:第i层错误码,包含库、函数以及错误原因信息。
err_data[i]:存放第i层操作信息。
err_data_flags[i]:存放err_data[i]相关的标记;比如为ERR_TXT_MALLOCED时,表名err_data[i]中的数据是动态分配内存的,需要释放;为ERR_TXT_STRING表名err_data[i]中的数据是一个字符串,可以用来打印。
err_file[i]:第i层错误的文件名。
err_line[i]:第i层错误的行号。
top和bottom:用于指明ERR_STATE的使用状态。top对应与最后一个错误(错误堆栈的最上层),bottom对应第一个错误(错误堆栈的最底层)。
当用户需要扩展openssl的模块时,可以仿照其他已有模块来实现自己的错误处理。
14.3 主要函数
1) ERR_add_error_data
在本层错误的err_data元素中添加说明信息。该函数一般由各个模块调用,比如可以用它说明什么操作导致了错误。
2) ERR_clear_error
清除所有的错误信息。如果不清楚所有错误信息,可能会有其他无关错误遗留在ERR_STATE表中。
3) ERR_error_string/ ERR_error_string_n
根据错误码获取具体的错误信息,包括出错的库、出错的函数以及错误原因。
4) ERR_free_strings
释放错误信息哈希表;通常在最后调用。
5) ERR_func_error_string
根据错误号,获取出错的函数信息。
6) ERR_get_err_state_table
获取存放错误的哈希表。
7) ERR_get_error
获取第一个错误号。
8) ERR_get_error_line
根据错误号,获取错误的行号。
9) ERR_get_error_line_data
根据错误号,获取出错信息。
10) ERR_get_implementation
获取错误处理函数,与哈希表操作相关。
11)ERR_get_state
获取ERR_STATE表。
12)ERR_lib_error_string
根据错误号,获取是哪个库出错。
13)ERR_load_strings
加载错误信息,由各个模块调用。
14)ERR_load_ASN1_strings
ERR_load_BIO_strings
ERR_load_BN_strings
ERR_load_BUF_strings
ERR_load_COMP_strings
ERR_load_CONF_strings
ERR_load_CRYPTO_strings
ERR_load_crypto_strings
ERR_load_DH_strings
ERR_load_DSA_strings
ERR_load_DSO_strings
ERR_load_EC_strings
ERR_load_ENGINE_strings
ERR_load_ERR_strings
ERR_load_EVP_strings
ERR_load_OBJ_strings
ERR_load_OCSP_strings
ERR_load_PEM_strings
ERR_load_PKCS12_strings
ERR_load_PKCS7_strings
ERR_load_RAND_strings
ERR_load_RSA_strings
ERR_load_UI_strings
ERR_load_X509_strings
ERR_load_X509V3_strings
各个模块实现的,加载各自错误信息。
15)ERR_peek_error
获取第一个错误号。
16)ERR_peek_error_line
获取第一个错误的出错行。
17)ERR_peek_error_line_data
获取第一个错误的行数和错误信息。
18)ERR_peek_last_error
获取最后一个错误号。
19)ERR_peek_last_error_line
获取最后一个错误的行号。
20)ERR_peek_last_error_line_data
获取最后一个错误的行号和错误信息。
21)ERR_print_errors
将错误信息输出到bio中。
22)ERR_print_errors_cb
根据用户设置的回调函数来打印错误信息。
23)ERR_print_errors_fp
将错误打印到FILE中。
24) ERR_put_error
将错误信息存放到ERR_STATE 表中top指定的错误堆栈(最后的错误)。
25) ERR_reason_error_string
根据错误号得到错误原因。
26) ERR_remove_state
删除线程相关的错误信息。
27) ERR_set_error_data
将错误信息存放到ERR_STATE 表中top指定的错误堆栈(最后的错误)。
28) ERR_unload_strings
从错误哈希表中删除相关信息。
14.4 编程示例
#include <openssl/err.h>
#include <openssl/bn.h>
int mycb(const char *a,size_t b,void *c)
{
printf("my print : %s\n",a);
return 0;
}
int main()
{
BIO *berr;
unsigned long err;
const char *file,*data,*efunc,*elib,*ereason,*p;
int line,flags;
char estring[500];
FILE *fp;
/*
ERR_load_crypto_strings();
*/
ERR_load_BIO_strings();
ERR_clear_error();
berr=BIO_new(BIO_s_file());
BIO_set_fp(berr,stdout,BIO_NOCLOSE);
BIO_new_file("no.exist","r");
err=ERR_peek_last_error();
err=ERR_peek_last_error_line(&file,&line);
printf("ERR_peek_last_error_line err : %ld,file : %s,line: %d\n",err,file,line);
err=ERR_peek_last_error_line_data(&file,&line,&data,&flags);
printf("ERR_peek_last_error_line_data err: %ld,file :%s,line :%d,data :%s\n",err,file,line,data);
err=ERR_peek_error();
printf("ERR_peek_error err: %ld\n",err);
err=ERR_peek_error_line(&file,&line);
printf("ERR_peek_error_line err : %ld,file : %s,line: %d\n",err,file,line);
err=ERR_peek_error_line_data(&file,&line,&data,&flags);
printf("ERR_peek_error_line_data err : %ld,file :%s,line :%d,data :%s\n",err,file,line,data);
err = ERR_get_error_line_data(&file,&line,&data,&flags);
printf("ERR_get_error_line_data err : %ld,file :%s,line :%d,data :%s\n",err,file,line,data);
if(err!=0)
{
p=ERR_lib_error_string(err);
printf("ERR_lib_error_string : %s\n",p);
}
err=ERR_get_error();
if(err!=0)
{
printf("ERR_get_error err : %ld\n",err);
efunc=ERR_func_error_string(err);
printf("err func : %s\n",efunc);
elib=ERR_lib_error_string(err);
printf("err lib : %s\n",efunc);
ereason=ERR_reason_error_string(err);
printf("err reason : %s\n",efunc);
efunc=ERR_func_error_string(err);
printf("err func : %s\n",efunc);
elib=ERR_lib_error_string(err);
printf("err lib : %s\n",efunc);
ereason=ERR_reason_error_string(err);
printf("err reason : %s\n",efunc);
ERR_error_string(err,estring);
printf("ERR_error_string : %s\n",estring);
ERR_error_string_n(err,estring,sizeof(estring));
printf("ERR_error_string_n : %s\n",estring);
}
err=ERR_get_error_line(&file,&line);
printf("err file :%s , err line : %d\n",file,line);
ERR_print_errors(berr);
BIO_new_file("no.exist2","r");
fp=fopen("err.log","w");
ERR_print_errors_fp(fp);
fclose(fp);
BIO_new_file("no.exist3","r");
ERR_print_errors_cb(mycb,NULL);
ERR_put_error(ERR_LIB_BN,BN_F_BNRAND,BN_R_BIGNUM_TOO_LONG,__FILE__,line);
ERR_print_errors(berr);
ERR_load_BN_strings();
ERR_put_error(ERR_LIB_BN,BN_F_BNRAND,BN_R_BIGNUM_TOO_LONG,__FILE__,line);
ERR_print_errors(berr);
ERR_put_error(ERR_LIB_BN,BN_F_BNRAND,BN_R_BIGNUM_TOO_LONG,__FILE__,line);
ERR_set_error_data("set date test!\n",ERR_TXT_STRING);
err=ERR_set_mark();
ERR_print_errors(berr);
ERR_free_strings();
BIO_free(berr);
return 0;
}
第十五章 摘要与HMAC
15.1 概述
摘要函数用于将任意数据通过计算获取唯一对应值,而这个值的长度比较短。它是一种多对一的关系。理论上,这个短的值就对应于原来的数据。这个过程是不可逆的,即不能通过摘要值来计算原始数据。摘要在信息安全中有非常重要的作用。很多网络应用都通过摘要计算来存放口令。摘要是安全协议中不可或却的要素,特别是身份认证与签名。用户需要对数据进行签名时,不可能对大的数据进行运算,这样会严重影响性能。如果只对摘要结果进行计算,则会提供运算速度。常用摘要算法有:sha、sha1、sha256以及md5等。其他还有md4、md2、mdc2以及ripemd160等。
15.2 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库(因为这个库一般比较大)。
15.3 函数说明
所有的摘要算法都有如下几个函数:
1) XXX_Init
XXX为具体的摘要算法名称,该函数初始化上下问,用于多数据摘要。
2) XXX_Update
XXX为具体的摘要算法名称,进行摘要计算,该函数可运行多次,对多个数据摘要。
3) XXX_Final
XXX为具体的摘要算法名称,进行摘要计算,该函数与1)和2)一起用。
4) XXX
对一个数据进行摘要。该函数由上述1)2)和3)实现,只是XXX_Update只调用一次。对应源码为XXX_one.c。
这些函数的测试程序,可参考各个目录下对应的测试程序源码。
15.4 编程示例
以下示例了MD2、MD4、MD5、SHA和SHA1函数的使用方法:
#include <stdio.h>
#include <string.h>
#include <openssl/md2.h>
#include <openssl/md4.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
int main()
{
unsigned char in[]="3dsferyewyrtetegvbzVEgarhaggavxcv";
unsigned char out[20];
size_t n;
int i;
n=strlen((const char*)in);
#ifdef OPENSSL_NO_MDC2
printf("默认openssl安装配置无MDC2\n");
#else
MDC2(in,n,out);
printf("MDC2 digest result :\n");
for(i=0;i<16;i++)
printf("%x ",out[i]);
#endif
RIPEMD160(in,n,out);
printf("RIPEMD160 digest result :\n");
for(i=0;i<20;i++)
printf("%x ",out[i]);
MD2(in,n,out);
printf("MD2 digest result :\n");
for(i=0;i<16;i++)
printf("%x ",out[i]);
MD4(in,n,out);
printf("\n\nMD4 digest result :\n");
for(i=0;i<16;i++)
printf("%x ",out[i]);
MD5(in,n,out);
printf("\n\nMD5 digest result :\n");
for(i=0;i<16;i++)
printf("%x ",out[i]);
SHA(in,n,out);
printf("\n\nSHA digest result :\n");
for(i=0;i<20;i++)
printf("%x ",out[i]);
SHA1(in,n,out);
printf("\n\nSHA1 digest result :\n");
for(i=0;i<20;i++)
printf("%x ",out[i]);
SHA256(in,n,out);
printf("\n\nSHA256 digest result :\n");
for(i=0;i<32;i++)
printf("%x ",out[i]);
SHA512(in,n,out);
printf("\n\nSHA512 digest result :\n");
for(i=0;i<64;i++)
printf("%x ",out[i]);
printf("\n");
return 0;
}
以上示例中演示了各种摘要计算函数的使用方法。对输入数据in进行摘要计算,结果存放在out缓冲区中。其中:
mdc2、md4和md5摘要结果为16字节,128比特;
ripemd160、sha和sha1摘要结果为20字节,160bit;
sha256摘要结果为32字节,256bit;
sha512摘要结果为64字节,512bit。
15.5 HMAC
HMAC用于保护消息的完整性,它采用摘要算法对消息、填充以及秘密密钥进行混合运算。在消息传输时,用户不仅传送消息本身,还传送HMAC值。接收方接收数据后也进行HMAC运算,再比对MAC值是否一致。由于秘密密钥只有发送方和接收方才有,其他人不可能伪造假的HMAC值,从而能够知道消息是否被篡改。
ssl协议中用HMAC来保护发送消息,并且ssl客户端和服务端的HMAC密钥是不同的,即对于双方都有一个读MAC保护密钥和写MAC保护密钥。
HMAC的实现在crypto/hmac/hmac.c中,如下:
unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len,
const unsigned char *d, size_t n, unsigned char *md,
unsigned int *md_len)
{
HMAC_CTX c;
static unsigned char m[EVP_MAX_MD_SIZE];
if (md == NULL) md=m;
HMAC_CTX_init(&c);
HMAC_Init(&c,key,key_len,evp_md);
HMAC_Update(&c,d,n);
HMAC_Final(&c,md,md_len);
HMAC_CTX_cleanup(&c);
return(md);
}
evp_md指明HMAC使用的摘要算法;
key为秘密密钥指针地址;
key_len为秘密密钥的长度;
d为需要做HMAC运算的数据指针地址;
n为d的长度;
md用于存放HMAC值;
md_len为HMAC值的长度。
第十六章 数据压缩
16.1 简介
数据压缩是将原有数据通过某种压缩算法计算得到相对数据量小的过程。这种过程是可逆的,即能通过压缩后的数据恢复出原数据。数据压缩能够节省存储空间,减轻网络负载。
在即需要加密又需要压缩的情况下,必须先压缩再加密,次序不能颠倒。因为加密后的数据是一个无序的数据,对它进行数据压缩,效果不大。
SSL协议本身支持压缩算法,Openssl实现也支持压缩算法。它实现了一个空的压缩算法(crypto/comp/c_rle.c)并支持zlib压缩算法(crypto/comp/ c_zlib.c)。openssl中用户可实现自己的压缩算法。
当openssl在有zlib库的平台下安装时,需要有zlib 或者zlib-dynamic选项。比如:
./config zlib
./config zlib-dynamic
16.2 数据结构
Openssl通过函数地址来抽象数据压缩。主要数据结构如下:
1) COMP_METHOD
该数据结构定义了具体压缩/解压函数,这些函数可由用户自己实现。
typedef struct comp_method_st
{
int type;
const char *name;
int (*init)(COMP_CTX *ctx);
void (*finish)(COMP_CTX *ctx);
int (*compress)(COMP_CTX *ctx,unsigned char *out, unsigned int olen, unsigned char *in, unsigned int ilen);
int (*expand)(COMP_CTX *ctx,unsigned char *out, unsigned int olen, unsigned char *in, unsigned int ilen);
long (*ctrl)(void);
long (*callback_ctrl)(void);
} COMP_METHOD;
各项意义如下:
type:压缩算法的nid;
name:压缩算法的名字;
init:初始化函数;
finish:结束操作;
commpress:具体的压缩算法,本函数必须实现;
expand:具体的解压算法,本函数必须实现;
ctrl和callback_ctrl:控制函数与回调控制函数,用于内部控制。
通过COMP_METHOD,Openssl能调用用户自己实现的压缩算法。只要用户实现了COMP_METHOD中的各个函数(主要是compress和expand函数)。
Openssl压缩源码位于crypto/comp目录下。它实现了一个空压缩算法和zlib压缩算法。其中空压缩算法由openssl自己实现,只是简单的拷贝数据。而zlib算法,openssl实现了基于其接口的COMP_METHOD,需要zlib库支持(/usr/lib/libz.a,/usr/lib/libz.so)。
2) comp_ctx
该结构用于存放压缩/解压中的上下文数据,主要供crypto/comp/comp_lib.c使用。
struct comp_ctx_st
{
COMP_METHOD *meth;
unsigned long compress_in;
unsigned long compress_out;
unsigned long expand_in;
unsigned long expand_out;
CRYPTO_EX_DATA ex_data;
};
各项意义如下:
meth:COMP_METHOD结构,一个comp_ctx通过它指明了一种具体的压缩算法;
compress_in:被压缩数据总字节数;
compress_out:压缩数据(结果)总字节数;
expand_in:被解压数据总字节数;
expand_out:解压数据(结果)总字节数;
ex_data:供用户使用的扩展数据,用于存放用户自定义的信息。
16.3 函数说明
1) COMP_rle
返回openssl实现的空压缩算法,返回值为一个COMP_METHOD。
2) COMP_zlib
返回基于zlib库的COMP_METHOD。
3) COMP_CTX_new
初始化上下文,输入参数为COMP_METHOD。
4) COMP_compress_block
压缩计算。
5) COMP_expand_block
解压计算。
16.4 openssl中压缩算法协商
Openssl中的压缩算法的协商与加密套件一样,都是由客户端在client hello消息中指定一个算法列表,而由服务端决定选取其中的一种,并通过server hello消息来通知客户端。
16.5 编程示例
#include <string.h>
#include <openssl/comp.h>
int main()
{
COMP_CTX *ctx;
int len,olen=100,ilen=50,i,total=0;
unsigned char in[50],out[100];
unsigned char expend[200];
#ifdef _WIN32
ctx=COMP_CTX_new(COMP_rle());
#else
/* for linux */
ctx=COMP_CTX_new(COMP_zlib());
#endif
for(i=0;i<50;i++)
memset(&in[i],i,1);
total=COMP_compress_block(ctx,out,olen,in,50);
len=COMP_expand_block(ctx,expend,200,out,total);
COMP_CTX_free(ctx);
return 0;
}
第十七章 RSA
17.1 RSA介绍
RSA算法是一个广泛使用的公钥算法。其密钥包括公钥和私钥。它能用于数字签名、身份认证以及密钥交换。RSA密钥长度一般使用1024位或者更高。RSA密钥信息主要包括[1]:
- n:模数
- e:公钥指数
- d:私钥指数
- p:最初的大素数
- q:最初的大素数
- dmp1:e*dmp1 = 1 (mod (p-1))
- dmq1:e*dmq1 = 1 (mod (q-1))
- iqmp:q*iqmp = 1 (mod p )
其中,公钥为n和e;私钥为n和d。在实际应用中,公钥加密一般用来协商密钥;私钥加密一般用来签名。
17.2 openssl的RSA实现
Openssl的RSA实现源码在crypto/rsa目录下。它实现了RSA PKCS1标准。主要源码如下:
1) rsa.h
定义RSA数据结构以及RSA_METHOD,定义了RSA的各种函数。
2) rsa_asn1.c
实现了RSA密钥的DER编码和解码,包括公钥和私钥。
3) rsa_chk.c
RSA密钥检查。
4) rsa_eay.c
Openssl实现的一种RSA_METHOD,作为其默认的一种RSA计算实现方式。此文件未实现rsa_sign、rsa_verify和rsa_keygen回调函数。
5)rsa_err.c
RSA错误处理。
6)rsa_gen.c
RSA密钥生成,如果RSA_METHOD中的rsa_keygen回调函数不为空,则调用它,否则调用其内部实现。
7)rsa_lib.c
主要实现了RSA运算的四个函数(公钥/私钥,加密/解密),它们都调用了RSA_METHOD中相应都回调函数。
8)rsa_none.c
实现了一种填充和去填充。
9)rsa_null.c
实现了一种空的RSA_METHOD。
10) rsa_oaep.c
实现了oaep填充与去填充。
11)rsa_pk1.
实现了pkcs1填充与去填充。
12)rsa_sign.c
实现了RSA的签名和验签。
13)rsa_ssl.c
实现了ssl填充。
14)rsa_x931.c
实现了一种填充和去填充。
17.3 RSA签名与验证过程
RSA签名过程如下:
1) 对用户数据进行摘要;
2) 构造X509_SIG结构并DER编码,其中包括了摘要算法以及摘要结果。
3) 对2)的结果进行填充,填满RSA密钥长度字节数。比如1024位RSA密钥必须填满128字节。具体的填充方式由用户指定。
4) 对3)的结果用RSA私钥加密。
RSA_eay_private_encrypt函数实现了3)和4)过程。
RSA验签过程是上述过程的逆过程,如下:
1) 对数据用RSA公钥解密,得到签名过程中2)的结果。
2) 去除1)结果的填充。
3) 从2)的结果中得到摘要算法,以及摘要结果。
4) 将原数据根据3)中得到摘要算法进行摘要计算。
5) 比较4)与签名过程中1)的结果。
RSA_eay_public_decrypt实现了1)和2)过程。
17.4 数据结构
RSA主要数据结构定义在crypto/rsa/rsa.h中:
17.4.1 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提供的默认方法。
17.4.2 RSA
RSA数据结构中包含了公/私钥信息(如果仅有n和e,则表明是公钥),定义如下:
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结构引用数。
17.5 主要函数
1) RSA_check_key
检查RSA密钥。
2)RSA_new
生成一个RSA密钥结构,并采用默认的rsa_pkcs1_eay_meth RSA_METHOD方法。
3)RSA_free
释放RSA结构。
4) 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。
8) RSA_padding_add_none
RSA_padding_add_PKCS1_OAEP
RSA_padding_add_PKCS1_type_1(私钥加密的填充)
RSA_padding_add_PKCS1_type_2(公钥加密的填充)
RSA_padding_add_SSLv23
各种填充方式函数。
9) RSA_padding_check_none
RSA_padding_check_PKCS1_OAEP
RSA_padding_check_PKCS1_type_1
RSA_padding_check_PKCS1_type_2
RSA_padding_check_SSLv23
RSA_PKCS1_SSLeay
各种去除填充函数。
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公钥。
17.6编程示例
17.6.1密钥生成
#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;
}
说明:
调用RSA_generate_key和RSA_generate_key_ex函数生成RSA密钥,
调用RSA_print_fp打印密钥信息。
输出:
Private-Key: (512 bit)
modulus:
00:d0:93:40:10:21:dd:c2:0b:6a:24:f1:b1:d5:b5:
77:79:ed:a9:a4:10:66:6e:88:d6:9b:0b:4c:91:7f:
23:6f:8f:0d:9e:9a:b6:7c:f9:47:fc:20:c2:12:e4:
b4:d7:ab:66:3e:73:d7:78:00:e6:5c:98:35:29:69:
c2:9b:c7:e2:c3
publicExponent: 3 (0x3)
privateExponent:
00:8b:0c:d5:60:16:93:d6:b2:46:c3:4b:cb:e3:ce:
4f:a6:9e:71:18:0a:ee:f4:5b:39:bc:b2:33:0b:aa:
17:9f:b3:7e:f0:0f:2a:24:b6:e4:73:40:ba:a0:65:
d3:19:0f:c5:b5:4f:59:51:e2:df:9c:83:47:da:8d:
84:0f:26:df:1b
prime1:
00:f7:4c:fb:ed:32:a6:74:5c:2d:6c:c1:c5:fe:3a:
59:27:6a:53:5d:3e:73:49:f9:17:df:43:79:d4:d0:
46:2f:0d
prime2:
00:d7:e9:88:0a:13:40:7c:f3:12:3d:60:85:f9:f7:
ba:96:44:29:74:3e:b9:4c:f8:bb:6a:1e:1b:a7:b4:
c7:65:0f
exponent1:
00:a4:dd:fd:48:cc:6e:f8:3d:73:9d:d6:83:fe:d1:
90:c4:f1:8c:e8:d4:4c:db:fb:65:3f:82:51:38:8a:
d9:74:b3
exponent2:
00:8f:f1:05:5c:0c:d5:a8:a2:0c:28:eb:03:fb:fa:
7c:64:2d:70:f8:29:d0:dd:fb:27:9c:14:12:6f:cd:
da:43:5f
coefficient:
00:d3:fa:ea:a0:21:7e:8a:e1:ab:c7:fd:e9:3d:cb:
5d:10:96:17:69:75:cd:71:d5:e5:07:26:93:e8:35:
ca:e3:49
17.6.2 RSA加解密运算
#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;
}
上述程序中当采用公钥RSA_SSLV23_PADDING加密,用私钥RSA_SSLV23_PADDING解密时会报错,原因是openssl源代码错误:
rsa_ssl.c函数RSA_padding_check_SSLv23有:
if (k == -1) /* err */
{
RSAerr(RSA_F_RSA_PADDING_CHECK_SSLV23,RSA_R_SSLV3_ROLLBACK_ATTACK);
return (-1);
}
修改为k!=-1即可。
各种padding对输入数据长度的要求:
私钥加密:
RSA_PKCS1_PADDING RSA_size-11
RSA_NO_PADDING RSA_size-0
RSA_X931_PADDING RSA_size-2
公钥加密
RSA_PKCS1_PADDING RSA_size-11
RSA_SSLV23_PADDING RSA_size-11
RSA_X931_PADDING RSA_size-2
RSA_NO_PADDING RSA_size-0
RSA_PKCS1_OAEP_PADDING RSA_size-2 * SHA_DIGEST_LENGTH-2
17.6.3签名与验证
签名运算:
#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)用来验证签名。
参考文献:
[1] PKCS #1 v2.1: RSA Cryptography Standard
第十八章 DSA
18.1 DSA简介
Digital Signature Algorithm (DSA)算法是一种公钥算法。其密钥由如下部分组成:
1)p
一个大素数,长度为L(64的整数倍)比特。
2)q
一个160比特素数。
3)g
g=h(p-1)/q mod p,其中h小于p-1。
4)x
小于q。
5) y
y=gx mod p。
其中x为私钥,y为公钥。p、q和g是公开信息(openssl中称为密钥参数)。
DSA签名包括两部分,如下:
r = (gk mod p) mod q
s = (k-1 (H(m) + xr)) mod q
其中,H(m)为摘要算法;
DSA验签如下:
w = s-1 mod q
u1 = (H(m) * w) mod q
u2 = (rw) mod q
v = ((gu1 * yu2) mod p) mod q
如果v=r,则验证通过。
18.2 openssl的DSA实现
Openssl的DSA实现源码在crypto/dsa目录下。主要源码如下:
1) dsa_asn1.c
DSA密钥参数(p、q和g)、DSA公钥(pub_key、p、q和g)以及DSA私钥(priv_key、pub_key、p、q和g)的DER编解码实现。
2)dsa_depr.c
生成DSA密钥参数。
3)dsa_err.c
DSA错误处理。
4)dsa_gen.c
生成DSA密钥参数。
5)dsa_key.c
根据DSA中的密钥参数产生公钥和私钥。
6)dsa_lib.c
实现了DSA通用的一些函数。
7)dsa_ossl.c
实现了一个DSA_METHOD,该DSA_METHOD为openssl默认的DSA方法,主要实现了如下三个回调函数:dsa_do_sign(签名)、dsa_sign_setup(根据密钥参数生成公私钥)和dsa_do_verify(验签)。
8)dsa_sign.c
实现了DSA签名和根据密钥参数生成公私钥。
9)dsa_vrf.c
实现了DSA验签。
18.3 DSA数据结构
DSA数据结构定义在crypto/dsa.h中,如下所示:
1) DSA_SIG
签名值数据结构
typedef struct DSA_SIG_st
{
BIGNUM *r;
BIGNUM *s;
} DSA_SIG;
签名结果包括两部分,都是大数。
2) DSA_METHOD
struct dsa_method
{
const char *name;
DSA_SIG * (*dsa_do_sign)(const unsigned char *dgst, int dlen, DSA *dsa);
int (*dsa_sign_setup)(DSA *dsa, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp);
int (*dsa_do_verify)(const unsigned char *dgst, int dgst_len, DSA_SIG *sig, DSA *dsa);
/* 其他 */
int (*dsa_paramgen)(DSA *dsa, int bits,unsigned char *seed, int seed_len, int *counter_ret, unsigned long *h_ret,BN_GENCB *cb);
int (*dsa_keygen)(DSA *dsa);
};
本结构是一个函数集合,DSA的各种计算都通过它来实现。drypto/dsa_ossl.c中实现了一个默认的DSA_METHOD。如果用户实现了自己的DSA_METHOD,通过调用DSA_set_default_method或DSA_set_method,用户可以让openssl采用自己的DSA计算函数。
主要项意义如下:
name:DSA_METHOD的名字;
dsa_do_sign:签名算法函数;
dsa_sign_setup:根据密钥参数生成公私钥的函数;
dsa_do_verify:签名验证函数;
dsa_paramgen:生成密钥参数函数;
dsa_keygen:生成公私钥函数。
18.4 主要函数
1) DSA_do_sign
数据签名。
2) DSA_do_verify
签名验证。
3) DSA_dup_DH
将DSA密钥转换为DH密钥。
4) DSA_new
生成一个DSA数据结构,一般情况下,DSA_METHOD采用默认的openssl_dsa_meth方法。
5) DSA_free
释放DSA数据结构。
6) DSA_generate_key
根据密钥参数生成公私钥。
7) DSA_generate_parameters
生成密钥参数。
8) DSA_get_default_method
获取默认的DSA_METHOD。
9) DSA_get_ex_data
获取扩展数据。
10) DSA_new_method
生成一个DSA结构。
11)DSA_OpenSSL
获取openssl_dsa_meth方法。
12)DSA_print
将DSA密钥信息输出到BIO中。
13)DSA_print_fp
将DSA密钥信息输出到FILE中。
14)DSA_set_default_method
设置默认的DSA_METHOD。
15) DSA_set_ex_data
设置扩展数据。
16)DSA_set_method
获取当前DSA的DSA_METHOD方法。
17)DSA_SIG_new
生成一个DSA_SIG签名值结构。
18)DSA_SIG_free
释放DSA_SIG结构。
19)DSA_sign
DSA签名。
20)DSA_sign_setup
根据密钥参数生成公私钥。
21)DSA_size
获取DSA密钥长度的字节数。
22)DSA_up_ref
给DSA结构添加一个引用。
23)DSA_verify
签名验证。
24)DSAparams_print
将DSA密钥参数输出到bio。
25)DSAparams_print_fp
将DSA密钥参数输出到FILE。
18.5 编程示例
18.5.1密钥生成
#include <string.h>
#include <openssl/dsa.h>
int main()
{
DSA *d;
int ret,i;
unsigned char seed[20];
int counter=2;
unsigned long h;
d=DSA_new();
for(i=0;i<20;i++)
memset(seed+i,i,1);
//ret=DSA_generate_parameters_ex(d, 512,seed, 20, &counter,&h,NULL);
/* 生成密钥参数 */
ret=DSA_generate_parameters_ex(d, 512,NULL,0,NULL,NULL,NULL);
if(ret!=1)
{
DSA_free(d);
return -1;
}
/* 生成密钥 */
ret=DSA_generate_key(d);
if(ret!=1)
{
DSA_free(d);
return -1;
}
DSA_print_fp(stdout,d,0);
DSA_free(d);
return 0;
}
输出:
priv:
35:8f:e6:50:e7:03:3b:5b:ba:ef:0a:c4:bd:92:e8:
74:9c:e5:57:6d
pub:
41:ea:ff:ac:e4:d0:e0:53:2e:cf:f0:c2:34:93:9c:
bc:b3:d2:f7:50:5e:e3:76:e7:25:b6:43:ed:ac:7b:
c0:31:7d:ea:50:92:ee:2e:34:38:fa:2d:a6:03:0c:
4f:f5:89:4b:4b:30:ab:e2:e8:4d:e4:77:f7:e9:4f:
60:88:2e:2a
P:
00:ab:8d:e8:b8:be:d1:89:e0:24:6d:4b:4e:cd:43:
9d:22:36:00:6a:d7:dd:f2:2c:cd:ce:69:9e:5f:87:
b4:6e:76:5f:e6:ef:74:7c:3b:11:5d:60:50:db:ce:
00:7e:ea:1e:a9:94:69:69:8b:e1:fc:7f:2a:ca:c2:
f0:e5:f8:63:c1
Q:
00:f8:68:d5:d5:4b:85:e6:a7:4f:98:08:bc:00:e2:
34:2e:94:cd:31:43
G:
00:8c:1a:09:06:a7:63:4b:cb:e0:c2:85:79:9f:12:
9d:ac:a7:34:3c:eb:9b:ab:4b:fe:54:c1:22:ff:49:
ec:17:d1:38:77:f5:2e:85:f7:80:d1:ac:4c:1a:96:
a1:88:a5:90:66:31:ed:6f:0b:00:f7:2e:df:79:6b:
95:97:c4:8a:95
18.5.2签名与验证
#include <string.h>
#include <openssl/objects.h>
#include <openssl/dsa.h>
int main()
{
int ret;
DSA *d;
int i,bits=1024,signlen,datalen,alg,nid;
unsigned char data[100],signret[200];
d=DSA_new();
ret=DSA_generate_parameters_ex(d,512,NULL,0,NULL,NULL,NULL);
if(ret!=1)
{
DSA_free(d);
return -1;
}
ret=DSA_generate_key(d);
if(ret!=1)
{
DSA_free(d);
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=20;
nid=NID_md5;
}
else if(alg==2)
{
datalen=20;
nid=NID_sha;
}
else if(alg==3)
{
datalen=20;
nid=NID_sha1;
}
else if(alg==4)
{
datalen=20;
nid=NID_md5_sha1;
}
ret=DSA_sign(nid,data,datalen,signret,&signlen,d);
if(ret!=1)
{
printf("DSA_sign err!\n");
DSA_free(d);
return -1;
}
ret=DSA_verify(nid,data,datalen,signret,signlen,d);
if(ret!=1)
{
printf("DSA_verify err!\n");
DSA_free(d);
return -1;
}
DSA_free(d);
printf("test ok!\n");
return 0;
}
第十九章DH
19.1 DH算法介绍
DH算法是W.Diffie和M.Hellman提出的。此算法是最早的公钥算法。它实质是一个通信双方进行密钥协商的协议:两个实体中的任何一个使用自己的私钥和另一实体的公钥,得到一个对称密钥,这一对称密钥其它实体都计算不出来。DH算法的安全性基于有限域上计算离散对数的困难性。离散对数的研究现状表明:所使用的DH密钥至少需要1024位,才能保证有足够的中、长期安全。
首先,发送方和接收方设置相同的大数数n和g,这两个数不是保密的,他们可以通过非安全通道来协商这两个素数;
接着,他们用下面的方法协商密钥:
发送方选择一个大随机整数x,计算X= g^x mod n ,发送X给接收者;
接收方选择一个大随机整数y,计算Y = g^y mod n ,发送Y给发送方;
双方计算密钥:发送方密钥为k1=Y^x mod n,接收方密钥为k2=X^y mod n。
其中k1=k2=g^(xy) mod n。
其他人可以知道n、g、X和Y,但是他们不能计算出密钥,除非他们能恢复x和y。DH算法不能抵御中间人攻击,中间人可以伪造假的X和Y分别发送给双方来获取他们的秘密密钥,所以需要保证X和Y的来源合法性。
19.2 openssl的DH实现
Openssl的DH实现在crypto/dh目录中。各个源码如下:
1) dh.h
定义了DH密钥数据结构以及各种函数。
2) dh_asn1.c
DH密钥参数的DER编解码实现。
3) dh_lib.c
实现了通用的DH函数。
4) dh_gen.c
实现了生成DH密钥参数。
5) dh_key.c
实现openssl提供的默认的DH_METHOD,实现了根据密钥参数生成DH公私钥,以及根据DH公钥(一方)以及DH私钥(另一方)来生成一个共享密钥,用于密钥交换。
6)dh_err.c
实现了DH错误处理。
7) dh_check.c
实现了DH密钥检查。
19.3数据结构
DH数据结构定义在crypto/dh/dh.h中,主要包含两项,如下:
1) DH_METHOD
struct dh_method
{
const char *name;
int (*generate_key)(DH *dh);
int (*compute_key)(unsigned char *key,const BIGNUM *pub_key,DH *dh);
int (*bn_mod_exp)(const DH *dh, BIGNUM *r, const BIGNUM *a,
const BIGNUM *p, const BIGNUM *m, BN_CTX *ctx,
BN_MONT_CTX *m_ctx);
int (*init)(DH *dh);
int (*finish)(DH *dh);
int flags;
char *app_data;
int (*generate_params)(DH *dh, int prime_len, int generator, BN_GENCB *cb);
};
DH_METHOD指明了一个DH密钥所有的计算方法函数。用户可以实现自己的DH_METHOD来替换openssl提供默认方法。各项意义如下:
name:DH_METHOD方法名称。
generate_key:生成DH公私钥的函数。
compute_key:根据对方公钥和己方DH密钥来生成共享密钥的函数。
bn_mod_exp:大数模运算函数,如果用户实现了它,生成DH密钥时,将采用用户实现的该回调函数。用于干预DH密钥生成。
init:初始化函数。
finish:结束函数。
flags:用于记录标记。
app_data:用于存放应用数据。
generate_params:生成DH密钥参数的回调函数,生成的密钥参数是可以公开的。
2) DH
struct dh_st
{
/* 其他 */
BIGNUM *p;
BIGNUM *g;
long length; /* optional */
BIGNUM *pub_key;
BIGNUM *priv_key;
int references;
CRYPTO_EX_DATA ex_data;
const DH_METHOD *meth;
ENGINE *engine;
/* 其他 */
};
p、g、length:DH密钥参数;
pub_key:DH公钥;
priv_key:DH私钥;
references:引用;
ex_data:扩展数据;
meth:DH_METHOD,本DH密钥的各种计算方法,明确指明了DH的各种运算方式;
engine:硬件引擎。
19.4 主要函数
1) DH_new
生成DH数据结构,其DH_METHOD采用openssl默认提供的。
2) DH_free
释放DH数据结构。
3) DH_generate_parameters
生成DH密钥参数。
4) DH_generate_key
生成DH公私钥。
5) DH_compute_key
计算共享密钥,用于数据交换。
6) DH_check
检查DH密钥。
7) DH_get_default_method
获取默认的DH_METHOD,该方法是可以由用户设置的。
8) DH_get_ex_data
获取DH结构中的扩展数据。
9) DH_new_method
生成DH数据结构。
10)DH_OpenSSL
获取openssl提供的DH_METHOD。
11)DH_set_default_method
设置默认的DH_METHOD方法,当用户实现了自己的DH_METHOD时,可调用本函数来设置,控制DH各种计算。
12)DH_set_ex_data
获取扩展数据。
13)DH_set_method
替换已有的DH_METHOD。
14)DH_size
获取DH密钥长度的字节数。
15) DH_up_ref
增加DH结构的一个引用。
16)DHparams_print
将DH密钥参数输出到bio中。
17) DHparams_print_fp
将DH密钥参数输出到FILE中。
19.5 编程示例
#include <openssl/dh.h>
#include <memory.h>
int main()
{
DH *d1,*d2;
BIO *b;
int ret,size,i,len1,len2;
char sharekey1[128],sharekey2[128];
/* 构造DH数据结构 */
d1=DH_new();
d2=DH_new();
/* 生成d1的密钥参数,该密钥参数是可以公开的 */
ret=DH_generate_parameters_ex(d1,64,DH_GENERATOR_2,NULL);
if(ret!=1)
{
printf("DH_generate_parameters_ex err!\n");
return -1;
}
/* 检查密钥参数 */
ret=DH_check(d1,&i);
if(ret!=1)
{
printf("DH_check err!\n");
if(i&DH_CHECK_P_NOT_PRIME)
printf("p value is not prime\n");
if(i&DH_CHECK_P_NOT_SAFE_PRIME)
printf("p value is not a safe prime\n");
if (i&DH_UNABLE_TO_CHECK_GENERATOR)
printf("unable to check the generator value\n");
if (i&DH_NOT_SUITABLE_GENERATOR)
printf("the g value is not a generator\n");
}
printf("DH parameters appear to be ok.\n");
/* 密钥大小 */
size=DH_size(d1);
printf("DH key1 size : %d\n",size);
/* 生成公私钥 */
ret=DH_generate_key(d1);
if(ret!=1)
{
printf("DH_generate_key err!\n");
return -1;
}
/* p和g为公开的密钥参数,因此可以拷贝 */
d2->p=BN_dup(d1->p);
d2->g=BN_dup(d1->g);
/* 生成公私钥,用于测试生成共享密钥 */
ret=DH_generate_key(d2);
if(ret!=1)
{
printf("DH_generate_key err!\n");
return -1;
}
/* 检查公钥 */
ret=DH_check_pub_key(d1,d1->pub_key,&i);
if(ret!=1)
{
if (i&DH_CHECK_PUBKEY_TOO_SMALL)
printf("pub key too small \n");
if (i&DH_CHECK_PUBKEY_TOO_LARGE)
printf("pub key too large \n");
}
/* 计算共享密钥 */
len1=DH_compute_key(sharekey1,d2->pub_key,d1);
len2=DH_compute_key(sharekey2,d1->pub_key,d2);
if(len1!=len2)
{
printf("生成共享密钥失败1\n");
return -1;
}
if(memcmp(sharekey1,sharekey2,len1)!=0)
{
printf("生成共享密钥失败2\n");
return -1;
}
printf("生成共享密钥成功\n");
b=BIO_new(BIO_s_file());
BIO_set_fp(b,stdout,BIO_NOCLOSE);
/* 打印密钥 */
DHparams_print(b,d1);
BIO_free(b);
DH_free(d1);
DH_free(d2);
return 0;
}
本例主要演示了生成DH密钥以及密钥交换函数。
第二十章 椭圆曲线
20.1 ECC介绍
椭圆曲线算法可以看作是定义在特殊集合下数的运算,满足一定的规则。椭圆曲线在如下两个域中定义:Fp域和F2m域。
Fp域,素数域,p为素数;
F2m域:特征为2的有限域,称之为二元域或者二进制扩展域。该域中,元素的个数为2m个。
椭圆曲线标准文档如下:
1) X9.62
Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA);
2) SEC 1
SEC 1:Elliptic Curve Cryptography;
3) SEC 2
SEC 2: Recommended Elliptic Curve Domain Parameters;
4) NIST
(U.S.) National Institute of Standards and Technology,美国国家标准。
这些标准一般都描述了Fp域和F2m域、椭圆曲线参数、数据转换、密钥生成以及推荐了多种椭圆曲线。
一些术语说明:
1) 椭圆曲线的阶(order of a curve)
椭圆曲线所有点的个数,包含无穷远点;
2) 椭圆曲线上点的阶(order of a point)
P为椭圆曲线上的点,nP=无穷远点,n取最小整数,既是P的阶;
3) 基点(base point)
椭圆曲线参数之一,用G表示,是椭圆曲线上都一个点;
4) 余因子(cofactor)
椭圆曲线的余因子,用h表示,为椭圆曲线点的个数/基点的阶
5) 椭圆曲线参数:
素数域:
(p,a,b,G,n,h)
其中,p为素数,确定Fp,a和b确定椭圆曲线方程,G为基点,n为G的阶,h为余因子。
二进制扩展域:
(m,f(x),a,b,G,n,h)
其中,m确定F2m,f(x)为不可约多项式,a和b用于确定椭圆曲线方程,G为基点,n为G的阶,h为余因子。
6) 椭圆曲线公钥和私钥
椭圆曲线的私钥是一个随机整数,小于n;
椭圆曲线的公钥是椭圆曲线上的一个点:Q=私钥*G。
20.2 openssl的ECC实现
Openssl实现了ECC算法。ECC算法系列包括三部分:ECC算法(crypto/ec)、椭圆曲线数字签名算法ECDSA (crypto/ecdsa)以及椭圆曲线密钥交换算法ECDH(crypto/ecdh)。
研究椭圆曲线需要注意的有:
1) 数据结构
- 椭圆曲线数据结构:EC_GROUP,该结构不仅包含各个参数,还包含了各种算法;
- 点的表示:EC_POINT,其中的大数X、Y和Z为雅克比投影坐标;
- EC_CURVE_DATA:用于内置椭圆曲线,包含了椭圆曲线的各个参数;
- 密钥结构
椭圆曲线密钥数据结构如下,定义在crypto/ec_lcl.h中,对用户是透明的。
struct ec_key_st
{
int version;
EC_GROUP *group;
EC_POINT *pub_key;
BIGNUM *priv_key;
/* 其他项 */
}
2) 密钥生成
对照公钥和私钥的表示方法,非对称算法不同有各自的密钥生成过程。椭圆曲线的密钥生成实现在crytpo/ec/ec_key.c中。Openssl中,内置的椭圆曲线密钥生成时,首先用户需要选取一种椭圆曲线(openssl的crypto/ec_curve.c中内置实现了67种,调用EC_get_builtin_curves获取该列表),然后根据选择的椭圆曲线计算密钥生成参数group,最后根据密钥参数group来生公私钥。
3) 签名值数据结构
非对称算法不同,签名的结果表示也不一样。与DSA签名值一样,ECDSA的签名结果表示为两项。ECDSA的签名结果数据结构定义在crypto/ecdsa/ecdsa.h中,如下:
typedef struct ECDSA_SIG_st
{
BIGNUM *r;
BIGNUM *s;
} ECDSA_SIG;
4) 签名与验签
对照签名结果,研究其是如何生成的。crypto/ecdsa/ ecs_sign.c实现了签名算法,crypto/ecdsa/ ecs_vrf.c实现了验签。
5) 密钥交换
研究其密钥交换是如何进行的;crypto/ecdh/ech_ossl.c实现了密钥交换算法。
文件说明:
EC_METHOD实现
ec2_smpl.c F2m 二进制扩展域上的EC_METHOD实现;
ecp_mont.c Fp 素数域上的EC_METHOD实现,(Montgomery 蒙哥马利)
ecp_smpl.c Fp 素数域上的EC_METHOD实现;
ecp_nist.c Fp 素数域上的EC_METHOD实现;
ec2_mult.c
F2m上的乘法;
ec2_smpt.c
F2m上的压缩算法;
ec_asn1.c
asn1编解码;
ec_check.c
椭圆曲线检测;
ec_curve.c
内置的椭圆曲线,
NID_X9_62_prime_field:X9.62的素数域;
NID_X9_62_characteristic_two_field:X9.62的二进制扩展域;
NIST:美国国家标准
ec_cvt.c
给定参数生成素数域和二进制扩展域上的椭圆曲线;
ec_err.c
错误处理;
ec_key.c
椭圆曲线密钥EC_KEY函数;
ec_lib.c
通用库实现,一般会调用底层的EC_METHOD方法;
ec_mult.c
This file implements the wNAF-based interleaving multi-exponentation method乘法;
ec_print.c
数据与椭圆曲线上点的相互转化;
ectest.c
测试源码,可以参考此源码学习椭圆曲线函数。
ec.h
对外头文件;
ec_lcl.h
内部头文件,数据结构一般在此定义。
20.3 主要函数
20.3.1参数设置
1) int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *group, EC_POINT *point,
const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx)
说明:设置二进制域椭圆曲线上点point的几何坐标;
2) int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *group, EC_POINT *point,
const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx)
说明:设置素数域椭圆曲线上点point的几何坐标;
3) int EC_POINT_set_compressed_coordinates_GF2m(const EC_GROUP *group, EC_POINT *point,const BIGNUM *x, int y_bit, BN_CTX *ctx)
说明:二进制域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;用于将Octet-String转化为椭圆曲线上的点;
4) int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *group, EC_POINT *point, const BIGNUM *x, int y_bit, BN_CTX *ctx)
说明:素数域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;用于将Octet-String转化为椭圆曲线上的点;
5) int EC_POINT_set_Jprojective_coordinates_GFp(const EC_GROUP *group, EC_POINT *point,
const BIGNUM *x, const BIGNUM *y, const BIGNUM *z, BN_CTX *ctx)
说明:素数域椭圆曲线group,设置点point的投影坐标系坐标x、y和z;
6) int EC_POINT_set_to_infinity(const EC_GROUP *group, EC_POINT *point)
说明:将点point设为无穷远点
7) int EC_GROUP_set_curve_GF2m(EC_GROUP *group, const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
说明:设置二进制域椭圆曲线参数;
8) int EC_GROUP_set_curve_GFp(EC_GROUP *group, const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
说明:设置素数域椭圆曲线参数;
9) int EC_GROUP_set_generator(EC_GROUP *group, const EC_POINT *generator, const BIGNUM *order, const BIGNUM *cofactor)
说明:设置椭圆曲线的基G;generator、order和cofactor为输入参数;
10) size_t EC_GROUP_set_seed(EC_GROUP *group, const unsigned char *p, size_t len)
说明:设置椭圆曲线随机数,用于生成a和b;
11) EC_GROUP *EC_GROUP_new_curve_GF2m(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
说明:生成二进制域上的椭圆曲线,输入参数为p,a和b;
12) EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
说明:生成素数域上的椭圆曲线。
20.3.2参数获取
1) const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *group)
说明:获取椭圆曲线的基(G);
2) unsigned char *EC_GROUP_get0_seed(const EC_GROUP *group)
说明:获取椭圆曲线参数的随机数,该随机数可选,用于生成椭圆曲线参数中的a和b;
3) int EC_GROUP_get_basis_type(const EC_GROUP *group)
说明:获取二进制域多项式的类型;
4) int EC_GROUP_get_cofactor(const EC_GROUP *group, BIGNUM *cofactor, BN_CTX *ctx)
说明:获取椭圆曲线的余因子。cofactor为X9.62中定义的h,值为椭圆曲线点的个数/基点的阶,即:cofactor = #E(Fq)/n。
5) int EC_GROUP_get_curve_GF2m(const EC_GROUP *group, BIGNUM *p, BIGNUM *a, BIGNUM *b, BN_CTX *ctx)
说明:获取二元域椭圆曲线的三个参数,其中p可表示多项式;
6) int EC_GROUP_get_curve_GFp(const EC_GROUP *group, BIGNUM *p, BIGNUM *a, BIGNUM *b, BN_CTX *ctx)
说明:获取素数域椭圆曲线的三个参数;
7) int EC_GROUP_get_curve_name(const EC_GROUP *group)
说明:获取椭圆曲线名称,返回其NID;
8) int EC_GROUP_get_degree(const EC_GROUP *group)
说明:获取椭圆曲线密钥长度。对于素数域Fp来说,是大数p的长度;对二进制域F2m来说,等于m;
9) int EC_GROUP_get_order(const EC_GROUP *group, BIGNUM *order, BN_CTX *ctx)
说明:获取椭圆曲线的阶;
10) int EC_GROUP_get_pentanomial_basis(const EC_GROUP *group, unsigned int *k1,
unsigned int *k2, unsigned int *k3)
int EC_GROUP_get_trinomial_basis(const EC_GROUP *group, unsigned int *k)
说明:获取多项式参数;
11) int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *group, const EC_POINT *point,BIGNUM *x, BIGNUM *y, BN_CTX *ctx)
说明:获取二进制域椭圆曲线上某个点的x和y的几何坐标;
12) int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group, const EC_POINT *point, BIGNUM *x, BIGNUM *y, BN_CTX *ctx)
说明:获取素数域上椭圆曲线上某个点的x和y的几何坐标;
13) int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *group, const EC_POINT *point,BIGNUM *x, BIGNUM *y, BIGNUM *z, BN_CTX *ctx)
说明:获取素数域椭圆曲线上某个点的x、y和z的投影坐标系坐标。
20.3.3转化函数
1) EC_POINT *EC_POINT_bn2point(const EC_GROUP *group, const BIGNUM *bn,EC_POINT *point,BN_CTX *ctx)
说明:将大数转化为椭圆曲线上的点;
2) EC_POINT *EC_POINT_hex2point(const EC_GROUP *group, const char *buf,EC_POINT *point,BN_CTX *ctx)
说明:将buf中表示的十六进制数据转化为椭圆曲线上的点;
3) int BN_GF2m_poly2arr(const BIGNUM *a, unsigned int p[], int max)
说明:将大数转化为多项式的各个项;
4) int BN_GF2m_arr2poly(const unsigned int p[], BIGNUM *a)
说明:将多项式的各个项转化为大数;
5) int EC_POINT_make_affine(const EC_GROUP *group, EC_POINT *point, BN_CTX *ctx)
说明:将椭圆曲线group上点的point转化为几何坐标系;
6) int EC_POINT_oct2point(const EC_GROUP *group, EC_POINT *point,
const unsigned char *buf, size_t len, BN_CTX *ctx)
说明:将buf中点数据转化为椭圆曲线上的点,len为数据长度;
7) BIGNUM *EC_POINT_point2bn(const EC_GROUP *group,const EC_POINT *point, point_conversion_form_t form,BIGNUM *ret,BN_CTX *ctx)
说明:将椭圆曲线上的点转化为大数,其中from为压缩方式,可以是POINT_CONVERSION_COMPRESSED、POINT_CONVERSION_UNCOMPRESSED或POINT_CONVERSION_HYBRID,可参考x9.62;
8) char *EC_POINT_point2hex(const EC_GROUP *group, const EC_POINT *point, point_conversion_form_t form,BN_CTX *ctx)
说明:将椭圆曲线上的点转化为十六进制,并返回该结果;
9) size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *point, point_conversion_form_t form,unsigned char *buf, size_t len, BN_CTX *ctx)
说明:将椭圆曲线上的点转化为Octet-String,可分两次调用,用法见EC_POINT_point2bn的实现。
20.3.4其他函数
1) size_t EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems)
文件:ec_curve.c
说明:获取内置的椭圆曲线。当输入参数r为NULL或者nitems为0时,返回内置椭圆曲线的个数,否则将各个椭圆曲线信息存放在r中。
示例:
#include <openssl/ec.h>
int main()
{
EC_builtin_curve *curves = NULL;
size_t crv_len = 0, n = 0;
int nid,ret;
EC_GROUP *group = NULL;
crv_len = EC_get_builtin_curves(NULL, 0);
curves = OPENSSL_malloc(sizeof(EC_builtin_curve) * crv_len);
EC_get_builtin_curves(curves, crv_len);
for (n=0;n<crv_len;n++)
{
nid = curves[n].nid;
group=NULL;
group = EC_GROUP_new_by_curve_name(nid);
ret=EC_GROUP_check(group,NULL);
}
OPENSSL_free(curves);
return 0;
}
2) const EC_METHOD *EC_GF2m_simple_method(void)
说明:返回二进制域上的方法集EC_METHOD
3) const EC_METHOD *EC_GFp_mont_method(void)
const EC_METHOD *EC_GFp_nist_method(void)
const EC_METHOD *EC_GFp_simple_method(void)
返回素数域上的方法集EC_METHOD
4) int EC_GROUP_check(const EC_GROUP *group, BN_CTX *ctx)
说明:检查椭圆曲线,成功返回1。
5) int EC_GROUP_check_discriminant(const EC_GROUP *group, BN_CTX *ctx)
说明:检查椭圆曲线表达式。对于素数域的椭圆曲线来说,该函数会调用ec_GFp_simple_group_check_discriminant函数,主要检查4*a^3 + 27*b^2 != 0 (mod p)。而对于二进制域的椭圆曲线,会调用ec_GF2m_simple_group_check_discriminant, 检查y^2 + x*y = x^3 + a*x^2 + b 是否是一个椭圆曲线并且 b !=0。
6) int EC_GROUP_cmp(const EC_GROUP *a, const EC_GROUP *b, BN_CTX *ctx)
说明:通过比较各个参数来确定两个椭圆曲线是否相等;
7) int EC_GROUP_copy(EC_GROUP *dest, const EC_GROUP *src)
EC_GROUP *EC_GROUP_dup(const EC_GROUP *a)
说明:椭圆曲线拷贝函数;
9) EC_GROUP *EC_GROUP_new_by_curve_name(int nid)
说明:根据NID获取内置的椭圆曲线;
10) int EC_KEY_check_key(const EC_KEY *eckey)
说明:检查椭圆曲线密钥;
11) int EC_KEY_generate_key(EC_KEY *eckey)
说明:生成椭圆曲线公私钥;
12) int EC_KEY_print(BIO *bp, const EC_KEY *x, int off)
说明:将椭圆曲线密钥信息输出到bio中,off为缩进量;
13) int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx)
说明:椭圆曲线上点的加法;
14) int EC_POINT_invert(const EC_GROUP *group, EC_POINT *a, BN_CTX *ctx)
说明:求椭圆曲线上某点a的逆元,a既是输入参数,也是输出参数;
15) int EC_POINT_is_at_infinity(const EC_GROUP *group, const EC_POINT *point)
说明:判断椭圆曲线上的点point是否是无穷远点;
16) int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, BN_CTX *ctx)
说明:判断一个点point是否在椭圆曲线上;
17) int ECDSA_size
说明:获取ECC密钥大小字节数。
18)ECDSA_sign
说明:签名,返回1表示成功。
19)ECDSA_verify
说明:验签,返回1表示合法。
20)EC_KEY_get0_public_key
说明:获取公钥。
21)EC_KEY_get0_private_key
说明:获取私钥。
22)ECDH_compute_key
说明:生成共享密钥
23)EC_KEY *d2i_ECPrivateKey(EC_KEY **a, const unsigned char **in, long len)
说明:DER解码将椭圆曲线密钥;
24)int i2d_ECPrivateKey(EC_KEY *a, unsigned char **out)
说明:将椭圆曲线密钥DER编码;
20.4 编程示例
下面的例子生成两对ECC密钥,并用它做签名和验签,并生成共享密钥。
#include <string.h>
#include <stdio.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/objects.h>
#include <openssl/err.h>
int main()
{
EC_KEY *key1,*key2;
EC_POINT *pubkey1,*pubkey2;
EC_GROUP *group1,*group2;
int ret,nid,size,i,sig_len;
unsigned char *signature,digest[20];
BIO *berr;
EC_builtin_curve *curves;
int crv_len;
char shareKey1[128],shareKey2[128];
int len1,len2;
/* 构造EC_KEY数据结构 */
key1=EC_KEY_new();
if(key1==NULL)
{
printf("EC_KEY_new err!\n");
return -1;
}
key2=EC_KEY_new();
if(key2==NULL)
{
printf("EC_KEY_new err!\n");
return -1;
}
/* 获取实现的椭圆曲线个数 */
crv_len = EC_get_builtin_curves(NULL, 0);
curves = (EC_builtin_curve *)malloc(sizeof(EC_builtin_curve) * crv_len);
/* 获取椭圆曲线列表 */
EC_get_builtin_curves(curves, crv_len);
/*
nid=curves[0].nid;会有错误,原因是密钥太短
*/
/* 选取一种椭圆曲线 */
nid=curves[25].nid;
/* 根据选择的椭圆曲线生成密钥参数group */
group1=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL)
{
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
group2=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL)
{
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
/* 设置密钥参数 */
ret=EC_KEY_set_group(key1,group1);
if(ret!=1)
{
printf("EC_KEY_set_group err.\n");
return -1;
}
ret=EC_KEY_set_group(key2,group2);
if(ret!=1)
{
printf("EC_KEY_set_group err.\n");
return -1;
}
/* 生成密钥 */
ret=EC_KEY_generate_key(key1);
if(ret!=1)
{
printf("EC_KEY_generate_key err.\n");
return -1;
}
ret=EC_KEY_generate_key(key2);
if(ret!=1)
{
printf("EC_KEY_generate_key err.\n");
return -1;
}
/* 检查密钥 */
ret=EC_KEY_check_key(key1);
if(ret!=1)
{
printf("check key err.\n");
return -1;
}
/* 获取密钥大小 */
size=ECDSA_size(key1);
printf("size %d \n",size);
for(i=0;i<20;i++)
memset(&digest[i],i+1,1);
signature=malloc(size);
ERR_load_crypto_strings();
berr=BIO_new(BIO_s_file());
BIO_set_fp(berr,stdout,BIO_NOCLOSE);
/* 签名数据,本例未做摘要,可将digest中的数据看作是sha1摘要结果 */
ret=ECDSA_sign(0,digest,20,signature,&sig_len,key1);
if(ret!=1)
{
ERR_print_errors(berr);
printf("sign err!\n");
return -1;
}
/* 验证签名 */
ret=ECDSA_verify(0,digest,20,signature,sig_len,key1);
if(ret!=1)
{
ERR_print_errors(berr);
printf("ECDSA_verify err!\n");
return -1;
}
/* 获取对方公钥,不能直接引用 */
pubkey2 = EC_KEY_get0_public_key(key2);
/* 生成一方的共享密钥 */
len1=ECDH_compute_key(shareKey1, 128, pubkey2, key1, NULL);
pubkey1 = EC_KEY_get0_public_key(key1);
/* 生成另一方共享密钥 */
len2=ECDH_compute_key(shareKey2, 128, pubkey1, key2, NULL);
if(len1!=len2)
{
printf("err\n");
}
else
{
ret=memcmp(shareKey1,shareKey2,len1);
if(ret==0)
printf("生成共享密钥成功\n");
else
printf("生成共享密钥失败\n");
}
printf("test ok!\n");
BIO_free(berr);
EC_KEY_free(key1);
EC_KEY_free(key2);
free(signature);
free(curves);
return 0;
}
更多底层函数的使用示例可参考ec/ectest.c,特别是用户自行定义椭圆曲线参数。
第二十一章 EVP
21.1 EVP简介
Openssl EVP(high-level cryptographic functions[1])提供了丰富的密码学中的各种函数。Openssl中实现了各种对称算法、摘要算法以及签名/验签算法。EVP函数将这些具体的算法进行了封装。
EVP主要封装了如下功能函数:
1)实现了base64编解码BIO;
2)实现了加解密BIO;
3)实现了摘要BIO;
4)实现了reliable BIO;
5)封装了摘要算法;
6)封装了对称加解密算法;
7)封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
7)基于口令的加密(PBE);
8)对称密钥处理;
9)数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密。发送给对方时,同时发送对称密钥密文和数据密文。接收方首先用自己的私钥解密密钥密文,得到对称密钥,然后用它解密数据。
10)其他辅助函数。
21.2 数据结构
EVP数据结构定义在crypto/evp.h中,如下所示:
21.2.1 EVP_PKEY
struct evp_pkey_st
{
int references;
union
{
char *ptr;
#ifndef OPENSSL_NO_RSA
struct rsa_st *rsa; /* RSA */
#endif
#ifndef OPENSSL_NO_DSA
struct dsa_st *dsa; /* DSA */
#endif
#ifndef OPENSSL_NO_DH
struct dh_st *dh; /* DH */
#endif
#ifndef OPENSSL_NO_EC
struct ec_key_st *ec; /* ECC */
#endif
} pkey;
STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */
};
该结构用来存放非对称密钥信息,可以是RSA、DSA、DH或ECC密钥。其中,ptr用来存放密钥结构地址,attributes堆栈用来存放密钥属性。
21.2.2 EVP_MD
struct env_md_st
{
int type;
int pkey_type;
int md_size;
unsigned long flags;
int (*init)(EVP_MD_CTX *ctx);
int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count);
int (*final)(EVP_MD_CTX *ctx,unsigned char *md);
int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from);
int (*cleanup)(EVP_MD_CTX *ctx);
int (*sign)(int type, const unsigned char *m, unsigned int m_length,
unsigned char *sigret, unsigned int *siglen, void *key);
int (*verify)(int type, const unsigned char *m, unsigned int m_length,
const unsigned char *sigbuf, unsigned int siglen,
void *key);
int required_pkey_type[5];
int block_size;
int ctx_size; /* how big does the ctx->md_data need to be */
} ;
该结构用来存放摘要算法信息、非对称算法类型以及各种计算函数。主要各项意义如下:
type:摘要类型,一般是摘要算法NID;
pkey_type:公钥类型,一般是签名算法NID;
md_size:摘要值大小,为字节数;
flags:用于设置标记;
init:摘要算法初始化函数;
update:多次摘要函数;
final:摘要完结函数;
copy:摘要上下文结构复制函数;
cleanup:清除摘要上下文函数;
sign:签名函数,其中key为非对称密钥结构地址;
verify:摘要函数,其中key为非对称密钥结构地址。
openssl对于各种摘要算法实现了上述结构,各个源码位于cypto/evp目录下,文件名以m_开头。Openssl通过这些结构来封装了各个摘要相关的运算。
21.2.3 EVP_CIPHER
struct evp_cipher_st
{
int nid;
int block_size;
int key_len;
int iv_len;
unsigned long flags;
int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc);
int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char *in, unsigned int inl);
int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */
int ctx_size;
int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr);
void *app_data;
} ;
该结构用来存放对称加密相关的信息以及算法。主要各项意义如下:
nid:对称算法nid;
block_size:对称算法每次加解密的字节数;
key_len:对称算法的密钥长度字节数;
iv_len:对称算法的填充长度;
flags:用于标记;
init:加密初始化函数,用来初始化ctx,key为对称密钥值,iv为初始化向量,enc用于指明是要加密还是解密,这些信息存放在ctx中;
do_cipher:对称运算函数,用于加密或解密;
cleanup:清除上下文函数;
set_asn1_parameters:设置上下文参数函数;
get_asn1_parameters:获取上下文参数函数;
ctrl:控制函数;
app_data:用于存放应用数据。
openssl对于各种对称算法实现了上述结构,各个源码位于cypto/evp目录下,文件名以e_开头。Openssl通过这些结构来封装了对称算法相关的运算。
21.2.4 EVP_CIPHER_CTX
struct evp_cipher_ctx_st
{
const EVP_CIPHER *cipher;
ENGINE *engine;
int encrypt;
int buf_len;
unsigned char oiv[EVP_MAX_IV_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
unsigned char buf[EVP_MAX_BLOCK_LENGTH];
/* 其他 */
unsigned char final[EVP_MAX_BLOCK_LENGTH];
} ;
对称算法上下文结构,此结构主要用来维护加解密状态,存放中间以及最后结果。因为加密或解密时,当数据很多时,可能会用到Update函数,并且每次加密或解密的输入数据长度任意的,并不一定是对称算法block_size的整数倍,所以需要用该结构来存放中间未加密的数据。主要项意义如下:
cipher:指明对称算法;
engine:硬件引擎;
encrypt:是加密还是解密;非0为加密,0为解密;
buf 和buf_len:指明还有多少数据未进行运算;
oiv:原始初始化向量;
iv:当前的初始化向量;
final:存放最终结果,一般与Final函数对应。
21.3 源码结构
evp源码位于crypto/evp目录,可以分为如下几类:
1) 全局函数
主要包括c_allc.c、c_alld.c、c_all.c以及names.c。他们加载openssl支持的所有的对称算法和摘要算法,放入到哈希表中。实现了OpenSSL_add_all_digests、OpenSSL_add_all_ciphers以及OpenSSL_add_all_algorithms(调用了前两个函数)函数。在进行计算时,用户也可以单独加载摘要函数(EVP_add_digest)和对称计算函数(EVP_add_cipher)。
2) BIO扩充
包括bio_b64.c、bio_enc.c、bio_md.c和bio_ok.c,各自实现了BIO_METHOD方法,分别用于base64编解码、对称加解密以及摘要。
3) 摘要算法EVP封装
由digest.c实现,实现过程中调用了对应摘要算法的回调函数。各个摘要算法提供了自己的EVP_MD静态结构,对应源码为m_xxx.c。
4) 对称算法EVP封装
由evp_enc.c实现,实现过程调用了具体对称算法函数,实现了Update操作。各种对称算法都提供了一个EVP_CIPHER静态结构,对应源码为e_xxx.c。需要注意的是,e_xxx.c中不提供完整的加解密运算,它只提供基本的对于一个block_size数据的计算,完整的计算由evp_enc.c来实现。当用户想添加一个自己的对称算法时,可以参考e_xxx.c的实现方式。一般用户至少需要实现如下功能:
- 构造一个新的静态的EVP_CIPHER结构;
- 实现EVP_CIPHER结构中的init函数,该函数用于设置iv,设置加解密标记、以及根据外送密钥生成自己的内部密钥;
- 实现do_cipher函数,该函数仅对block_size字节的数据进行对称运算;
- 实现cleanup函数,该函数主要用于清除内存中的密钥信息。
5) 非对称算法EVP封装
主要是以p_开头的文件。其中,p_enc.c封装了公钥加密;p_dec.c封装了私钥解密;p_lib.c实现一些辅助函数;p_sign.c封装了签名函数;p_verify.c封装了验签函数;p_seal.c封装了数字信封;p_open.c封装了解数字信封。
6) 基于口令的加密
包括p5_crpt2.c、p5_crpt.c和evp_pbe.c。
21.4 摘要函数
典型的摘要函数主要有:
1) EVP_md5
返回md5的EVP_MD。
2) EVP_sha1
返回sha1的EVP_MD。
3) EVP_sha256
返回sha256的EVP_MD。
4) EVP_DigestInit
摘要初使化函数,需要有EVP_MD作为输入参数。
5) EVP_DigestUpdate和EVP_DigestInit_ex
摘要Update函数,用于进行多次摘要。
6) EVP_DigestFinal和EVP_DigestFinal_ex
摘要Final函数,用户得到最终结果。
7) EVP_Digest
对一个数据进行摘要,它依次调用了上述三个函数。
21.5 对称加解密函数
典型的加解密函数主要有:
1) EVP_CIPHER_CTX_init
初始化对称计算上下文。
2) EVP_CIPHER_CTX_cleanup
清除对称算法上下文数据,它调用用户提供的销毁函数销清除存中的内部密钥以及其他数据。
3) EVP_des_ede3_ecb
返回一个EVP_CIPHER;
4) EVP_EncryptInit和EVP_EncryptInit_ex
加密初始化函数,本函数调用具体算法的init回调函数,将外送密钥key转换为内部密钥形式,将初始化向量iv拷贝到ctx结构中。
5) EVP_EncryptUpdate
加密函数,用于多次计算,它调用了具体算法的do_cipher回调函数。
6) EVP_EncryptFinal和EVP_EncryptFinal_ex
获取加密结果,函数可能涉及填充,它调用了具体算法的do_cipher回调函数。
7) EVP_DecryptInit和EVP_DecryptInit_ex
解密初始化函数。
8) EVP_DecryptUpdate
解密函数,用于多次计算,它调用了具体算法的do_cipher回调函数。
9) EVP_DecryptFinal和EVP_DecryptFinal_ex
获取解密结果,函数可能涉及去填充,它调用了具体算法的do_cipher回调函数。
10)EVP_BytesToKey
计算密钥函数,它根据算法类型、摘要算法、salt以及输入数据计算出一个对称密钥和初始化向量iv。
11)PKCS5_PBE_keyivgen和PKCS5_v2_PBE_keyivgen
实现了PKCS5基于口令生成密钥和初始化向量的算法。
12)PKCS5_PBE_add
加载所有openssl实现的基于口令生成密钥的算法。
13)EVP_PBE_alg_add
添加一个PBE算法。
21.6 非对称函数
典型的非对称函数有:
1) EVP_PKEY_encrypt
公钥加密。
2) EVP_PKEY_decrypt
私钥解密。
3) EVP_PKEY_assign
设置EVP_PKEY中具体的密钥结构,使它代表该密钥。
4) EVP_PKEY_assign_RSA/ EVP_PKEY_set1_RSA
设置EVP_PKEY中的RSA密钥结构,使它代表该RSA密钥。
5) EVP_PKEY_get1_RSA
获取EVP_PKEY的RSA密钥结构。
6) EVP_SignFinal
签名操作,输入参数必须有私钥(EVP_PKEY)。
7) EVP_VerifyFinal
验证签名,输入参数必须有公钥(EVP_PKEY)。
8) int EVP_OpenInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,const unsigned char *ek, int ekl, const unsigned char *iv,EVP_PKEY *priv)
解数字信封初始化操作,type为对称加密算法,ek为密钥密文,ekl为密钥密文长度,iv为填充值,priv为用户私钥。
9) EVP_OpenUpdate
做解密运算。
10) EVP_OpenFinal
做解密运算,解开数字信封。
11) int EVP_SealInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, unsigned char **ek,int *ekl, unsigned char *iv, EVP_PKEY **pubk, int npubk)
type为对称算法,ek数组用来存放多个公钥对密钥加密的结果,ekl用于存放ek数组中每个密钥密文的长度,iv为填充值,pubk数组用来存放多个公钥,npubk为公钥个数,本函数用多个公钥分别加密密钥,并做加密初始化。
12)EVP_SealUpdate
做加密运算。
EVP_SealFinal
做加密运算,制作数字信封。
21.7 BASE64编解码函数
1) EVP_EncodeInit
BASE64编码初始化。
2) EVP_EncodeUpdate
BASE64编码,可多次调用。
3) EVP_EncodeFinal
BASE64编码,并获取最终结果。
4) EVP_DecodeInit
BASE64解码初始化。
5) EVP_DecodeUpdate
输入数据长度不能大于80字节。BASE64解码可多次调用,注意,本函数的输入数据不能太长。
6) EVP_DecodeFinal
BASE64解码,并获取最终结果。
7)EVP_EncodeBlock
BASE64编码函数,本函数可单独调用。
8)EVP_DecodeBlock
BASE64解码,本函数可单独调用,对输入数据长度无要求。
21.8其他函数
1) EVP_add_cipher
将对称算法加入到全局变量,以供调用。
2) EVP_add_digest
将摘要算法加入到全局变量中,以供调用。
3) EVP_CIPHER_CTX_ctrl
对称算法控制函数,它调用了用户实现的ctrl回调函数。
4) EVP_CIPHER_CTX_set_key_length
当对称算法密钥长度为可变长时,设置对称算法的密钥长度。
5) EVP_CIPHER_CTX_set_padding
设置对称算法的填充,对称算法有时候会涉及填充。加密分组长度大于一时,用户输入数据不是加密分组的整数倍时,会涉及到填充。填充在最后一个分组来完成,openssl分组填充时,如果有n个填充,则将最后一个分组用n来填满。
6) EVP_CIPHER_get_asn1_iv
获取原始iv,存放在ASN1_TYPE结构中。
7) EVP_CIPHER_param_to_asn1
设置对称算法参数,参数存放在ASN1_TYPE类型中,它调用用户实现的回调函数set_asn1_parameters来实现。
8) EVP_CIPHER_type
获取对称算法的类型。
9) EVP_CipherInit/EVP_CipherInit_ex
对称算法计算(加/解密)初始化函数,_ex函数多了硬件enginge参数,EVP_EncryptInit和EVP_DecryptInit函数也调用本函数。
10) EVP_CipherUpdate
对称计算(加/解密)函数,它调用了EVP_EncryptUpdate和EVP_DecryptUpdate函数。
11) EVP_CipherFinal/EVP_CipherFinal_ex
对称计算(加/解)函数,调用了EVP_EncryptFinal(_ex)和EVP_DecryptFinal(_ex);本函数主要用来处理最后加密分组,可能会有对称计算。
12)EVP_cleanup
清除加载的各种算法,包括对称算法、摘要算法以及PBE算法,并清除这些算法相关的哈希表的内容。
13) EVP_get_cipherbyname
根据字串名字来获取一种对称算法(EVP_CIPHER),本函数查询对称算法哈希表。
14) EVP_get_digestbyname
根据字串获取摘要算法(EVP_MD),本函数查询摘要算法哈希表。
15) EVP_get_pw_prompt
获取口令提示信息字符串.
16)int EVP_PBE_CipherInit(ASN1_OBJECT *pbe_obj, const char *pass, int passlen,
ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de)
PBE初始化函数。本函数用口令生成对称算法的密钥和初始化向量,并作加/解密初始化操作。本函数再加上后续的EVP_CipherUpdate以及EVP_CipherFinal_ex构成一个完整的加密过程(可参考crypto/p12_decr.c的PKCS12_pbe_crypt函数).
17) EVP_PBE_cleanup
删除所有的PBE信息,释放全局堆栈中的信息.
18)EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *p8)
将PKCS8_PRIV_KEY_INFO(x509.h中定义)中保存的私钥转换为EVP_PKEY结构。
19) EVP_PKEY2PKCS8/EVP_PKEY2PKCS8_broken
将EVP_PKEY结构中的私钥转换为PKCS8_PRIV_KEY_INFO数据结构存储。
20) EVP_PKEY_bits
非对称密钥大小,为比特数。
21) EVP_PKEY_cmp_parameters
比较非对称密钥的密钥参数,用于DSA和ECC密钥。
22)EVP_PKEY_copy_parameters
拷贝非对称密钥的密钥参数,用于DSA和ECC密钥。
23)EVP_PKEY_free
释放非对称密钥数据结构。
24) EVP_PKEY_get1_DH/EVP_PKEY_set1_DH
获取/设置EVP_PKEY中的DH密钥。
25) EVP_PKEY_get1_DSA/EVP_PKEY_set1_DSA
获取/设置EVP_PKEY中的DSA密钥。
26)EVP_PKEY_get1_RSA/EVP_PKEY_set1_RSA
获取/设置EVP_PKEY中结构中的RSA结构密钥。
27) EVP_PKEY_missing_parameters
检查非对称密钥参数是否齐全,用于DSA和ECC密钥。
28) EVP_PKEY_new
生成一个EVP_PKEY结构。
29) EVP_PKEY_size
获取非对称密钥的字节大小。
30) EVP_PKEY_type
获取EVP_PKEY中表示的非对称密钥的类型。
31)int EVP_read_pw_string(char *buf,int length,const char *prompt,int verify)
获取用户输入的口令;buf用来存放用户输入的口令,length为buf长度,prompt为提示给用户的信息,如果为空,它采用内置的提示信息,verify为0时,不要求验证用户输入的口令,否则回要求用户输入两遍。返回0表示成功。
32) EVP_set_pw_prompt
设置内置的提示信息,用于需要用户输入口令的场合。
21.9 对称加密过程
对称加密过程如下:
1) EVP_EncryptInit:
设置buf_len为0,表明临时缓冲区buf没有数据。
2) EVP_EncryptUpdate:
ctx结构中的buf缓冲区用于存放上次EVP_EncryptUpdate遗留下来的未加密的数据,buf_len指明其长度。如果buf_len为0,加密的时候先加密输入数据的整数倍,将余下的数据拷贝到buf缓冲区。如果buf_len不为0,先加密buf里面的数据和输入数据的一部分(凑足一个分组的长度),然后用上面的方法加密,输出结果是加过密的数据。
3) EVP_ EncryptFinal
加密ctx的buf中余下的数据,如果长度不够一个分组(分组长度不为1),则填充,然后再加密,输出结果。
总之,加密大块数据(比如一个大的文件,多出调用EVP_EncryptUpdate)的结果等效于将所有的数据一次性读入内存进行加密的结果。加密和解密时每次计算的数据块的大小不影响其运算结果。
21.10 编程示例
1)示例1
#include <string.h>
#include <openssl/evp.h>
int main()
{
int ret,which=1;
EVP_CIPHER_CTX ctx;
const EVP_CIPHER *cipher;
unsigned char key[24],iv[8],in[100],out[108],de[100];
int i,len,inl,outl,total=0;
for(i=0;i<24;i++)
{
memset(&key[i],i,1);
}
for(i=0;i<8;i++)
{
memset(&iv[i],i,1);
}
for(i=0;i<100;i++)
{
memset(&in[i],i,1);
}
EVP_CIPHER_CTX_init(&ctx);
printf("please select :\n");
printf("1: EVP_des_ede3_ofb\n");
printf("2: EVP_des_ede3_cbc\n");
scanf("%d",&which);
if(which==1)
cipher=EVP_des_ede3_ofb();
else
cipher=EVP_des_ede3_cbc();
ret=EVP_EncryptInit_ex(&ctx,cipher,NULL,key,iv);
if(ret!=1)
{
printf("EVP_EncryptInit_ex err1!\n");
return -1;
}
inl=50;
len=0;
EVP_EncryptUpdate(&ctx,out+len,&outl,in,inl);
len+=outl;
EVP_EncryptUpdate(&ctx,out+len,&outl,in+50,inl);
len+=outl;
EVP_EncryptFinal_ex(&ctx,out+len,&outl);
len+=outl;
printf("加密结果长度:%d\n",len);
/* 解密 */
EVP_CIPHER_CTX_cleanup(&ctx);
EVP_CIPHER_CTX_init(&ctx);
ret=EVP_DecryptInit_ex(&ctx,cipher,NULL,key,iv);
if(ret!=1)
{
printf("EVP_DecryptInit_ex err1!\n");
return -1;
}
total=0;
EVP_DecryptUpdate(&ctx,de+total,&outl,out,44);
total+=outl;
EVP_DecryptUpdate(&ctx,de+total,&outl,out+44,len-44);
total+=outl;
ret=EVP_DecryptFinal_ex(&ctx,de+total,&outl);
total+=outl;
if(ret!=1)
{
EVP_CIPHER_CTX_cleanup(&ctx);
printf("EVP_DecryptFinal_ex err\n");
return -1;
}
if((total!=100) || (memcmp(de,in,100)))
{
printf("err!\n");
return -1;
}
EVP_CIPHER_CTX_cleanup(&ctx);
printf("test ok!\n");
return 0;
}
输出结果如下:
please select :
1: EVP_des_ede3_ofb
2: EVP_des_ede3_cbc
1
加密结果长度:100
test ok!
please select :
1: EVP_des_ede3_ofb
2: EVP_des_ede3_cbc
2
加密结果长度:104
test ok!
2)示例2
#include <string.h>
#include <openssl/evp.h>
int main()
{
int cnid,ret,i,msize,mtype;
int mpktype,cbsize,mnid,mbsize;
const EVP_CIPHER *type;
const EVP_MD *md;
int datal,count,keyl,ivl;
unsigned char salt[20],data[100],*key,*iv;
const char *cname,*mname;
type=EVP_des_ecb();
cnid=EVP_CIPHER_nid(type);
cname=EVP_CIPHER_name(type);
cbsize=EVP_CIPHER_block_size(type);
printf("encrypto nid : %d\n",cnid);
printf("encrypto name: %s\n",cname);
printf("encrypto bock size : %d\n",cbsize);
md=EVP_md5();
mtype=EVP_MD_type(md);
mnid=EVP_MD_nid(md);
mname=EVP_MD_name(md);
mpktype=EVP_MD_pkey_type(md);
msize=EVP_MD_size(md);
mbsize=EVP_MD_block_size(md);
printf("md info : \n");
printf("md type : %d\n",mtype);
printf("md nid : %d\n",mnid);
printf("md name : %s\n",mname);
printf("md pkey type : %d\n",mpktype);
printf("md size : %d\n",msize);
printf("md block size : %d\n",mbsize);
keyl=EVP_CIPHER_key_length(type);
key=(unsigned char *)malloc(keyl);
ivl=EVP_CIPHER_iv_length(type);
iv=(unsigned char *)malloc(ivl);
for(i=0;i<100;i++)
memset(&data[i],i,1);
for(i=0;i<20;i++)
memset(&salt[i],i,1);
datal=100;
count=2;
ret=EVP_BytesToKey(type,md,salt,data,datal,count,key,iv);
printf("generate key value: \n");
for(i=0;i<keyl;i++)
printf("%x ",*(key+i));
printf("\n");
printf("generate iv value: \n");
for(i=0;i<ivl;i++)
printf("%x ",*(iv+i));
printf("\n");
return 0;
}
EVP_BytesToKey函数通过salt以及data数据来生成所需要的key和iv。
输出:
encrypto nid : 29
encrypto name: DES-ECB
encrypto bock size : 8
md info :
md type : 4
md nid : 4
md name : MD5
md pkey type : 8
md size : 16
md block size : 64
generate key value:
54 0 b1 24 18 42 8d dd
generate iv value:
ba 7d c3 97 a0 c9 e0 70
3) 示例3
#include <openssl/evp.h>
#include <openssl/rsa.h>
int main()
{
int ret,inlen,outlen=0;
unsigned long e=RSA_3;
char data[100],out[500];
EVP_MD_CTX md_ctx,md_ctx2;
EVP_PKEY *pkey;
RSA *rkey;
BIGNUM *bne;
/* 待签名数据*/
strcpy(data,"openssl 编程作者:赵春平");
inlen=strlen(data);
/* 生成RSA密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
rkey=RSA_new();
ret=RSA_generate_key_ex(rkey,1024,bne,NULL);
if(ret!=1) goto err;
pkey=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey,rkey);
/* 初始化*/
EVP_MD_CTX_init(&md_ctx);
ret=EVP_SignInit_ex(&md_ctx,EVP_md5(), NULL);
if(ret!=1) goto err;
ret=EVP_SignUpdate(&md_ctx,data,inlen);
if(ret!=1) goto err;
ret=EVP_SignFinal(&md_ctx,out,&outlen,pkey);
/* 验证签名*/
EVP_MD_CTX_init(&md_ctx2);
ret=EVP_VerifyInit_ex(&md_ctx2,EVP_md5(), NULL);
if(ret!=1) goto err;
ret=EVP_VerifyUpdate(&md_ctx2,data,inlen);
if(ret!=1) goto err;
ret=EVP_VerifyFinal(&md_ctx2,out,outlen,pkey);
if(ret==1)
printf("验证成功\n");
else
printf("验证错误\n");
err:
RSA_free(rkey);
BN_free(bne);
return 0;
}
4)示例4
#include <openssl/evp.h>
#include <openssl/rsa.h>
int main()
{
int ret,ekl[2],npubk,inl,outl,total=0,total2=0;
unsigned long e=RSA_3;
char *ek[2],iv[8],in[100],out[500],de[500];
EVP_CIPHER_CTX ctx,ctx2;
EVP_CIPHER *type;
EVP_PKEY *pubkey[2];
RSA *rkey;
BIGNUM *bne;
/* 生成RSA密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
rkey=RSA_new();
ret=RSA_generate_key_ex(rkey,1024,bne,NULL);
pubkey[0]=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pubkey[0],rkey);
type=EVP_des_cbc();
npubk=1;
EVP_CIPHER_CTX_init(&ctx);
ek[0]=malloc(500);
ek[1]=malloc(500);
ret=EVP_SealInit(&ctx,type,ek,ekl,iv,pubkey,1); /* 只有一个公钥*/
if(ret!=1) goto err;
strcpy(in,"openssl 编程");
inl=strlen(in);
ret=EVP_SealUpdate(&ctx,out,&outl,in,inl);
if(ret!=1) goto err;
total+=outl;
ret=EVP_SealFinal(&ctx,out+outl,&outl);
if(ret!=1) goto err;
total+=outl;
memset(de,0,500);
EVP_CIPHER_CTX_init(&ctx2);
ret=EVP_OpenInit(&ctx2,EVP_des_cbc(),ek[0],ekl[0],iv,pubkey[0]);
if(ret!=1) goto err;
ret=EVP_OpenUpdate(&ctx2,de,&outl,out,total);
total2+=outl;
ret=EVP_OpenFinal(&ctx2,de+outl,&outl);
total2+=outl;
de[total2]=0;
printf("%s\n",de);
err:
free(ek[0]);
free(ek[1]);
EVP_PKEY_free(pubkey[0]);
BN_free(bne);
getchar();
return 0;
}
输出结果:openssl 编程
参考文献:
[1] http://www.openssl.org/docs/crypto/evp.html#NAME
第二十二章 PEM格式
22.1 PEM概述
Openssl使用PEM(Privacy Enhanced Mail)格式来存放各种信息,它是openssl默认采用的信息存放方式。Openssl中的PEM文件一般包含如下信息:
1) 内容类型
表明本文件存放的是什么信息内容,它的形式为“-------BEGIN XXXX ------”,与结尾的“------END XXXX------”对应。
2) 头信息
表明数据是如果被处理后存放,openssl中用的最多的是加密信息,比如加密算法以及初始化向量iv。
3) 信息体
为BASE64编码的数据。
举例如下:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,9CFD51EC6654FCC3
g2UP/2EvYyhHKAKafwABPrQybsxnepPXQxpP9qkaihV3k0uYJ2Q9qD/nSV2AG9Slqp0HBomnYS35NSB1bmMb+oGD5vareO7Bt+XZgFv0FINCclTBsFOmZwqs/m95Af+BBkCvNCct+ngM+UWB2N8jXYnbDMvZGyI3ma+Sfcf3vX7gyPOEXgr5D5NgwwNyu/LtQZvM4k2f7xn7VcAFGmmtvAXvqVrhEvk55XR0plkc+nOqYXbwLjYMO5LSLFNAtETm9aw0nYMD0Zx+s+8tJdtPq+Ifu3g9UZkvh2KpEg7he8Z8vaV7lpHiTjmpgkKpx9wKUCHnJq8U3cNcYdRvCWNf4T2jYLSS4kxdK2p50KjH8xcfWXVkU2CK9NQGlh18TmPueZOkSEHf76KTE9DWKAo7mNmcByTziyofe5qKhtqkYYVBbaCFC0+pKTak4EuLgznt6j87ktuXDXFc+50DnWi1FtQN3LuQH5htl7autzaxCvenfGQByIh7gxCygBVCJdWca3xE1H0SbRV6LbtjeB/NdCvwgJsRLBXXkjU2TKy/ljsG29xHP2xzlvOtATxq1zMMwMKt7kJMFpgSTIbxgUeqzgGbR7VMBmWSF4bBNnGDkOQ0WLJhVq9OMbzpBzmGJqHn3XjZ2SPXF4xhC7ZhAMxDsFs35P4lPLDH/ycLTcLtUmVZJzvPvzh2r56iTiU28f/rMnHn1xQ92Cf+62VgECI6CwTotMeM0EfGdCQCiWjeqrzH9qy8+VN3Q2xIlUZj7ibO59YO1A5zVxpKcQRamwyIy/IYTPr2c2wLfsTZPBt6mD4=
-----END RSA PRIVATE KEY-----
本例是作者生成的一个RSA密钥,以PEM格式加密存放,采用了openssl默认的对称加密算法。其中,“-----BEGIN RSA PRIVATE KEY-----”表明了本文件是一个RSA私钥;DES-EDE3-CB为对称加密算法,9CFD51EC6654FCC3为对称算法初始化向量iv。
22.2 openssl的PEM实现
Openssl的PEM模块实现位于crypto/pem目录下,并且还依赖于openssl的ASN1模块。Openssl支持的PEM类型在crypto/pem/pem.h中定义如下:
#define PEM_STRING_X509_OLD "X509 CERTIFICATE"
#define PEM_STRING_X509 "CERTIFICATE"
#define PEM_STRING_X509_PAIR "CERTIFICATE PAIR"
#define PEM_STRING_X509_TRUSTED "TRUSTED CERTIFICATE"
#define PEM_STRING_X509_REQ_OLD "NEW CERTIFICATE REQUEST"
#define PEM_STRING_X509_REQ "CERTIFICATE REQUEST"
#define PEM_STRING_X509_CRL "X509 CRL"
#define PEM_STRING_EVP_PKEY "ANY PRIVATE KEY"
#define PEM_STRING_PUBLIC "PUBLIC KEY"
#define PEM_STRING_RSA "RSA PRIVATE KEY"
#define PEM_STRING_RSA_PUBLIC "RSA PUBLIC KEY"
#define PEM_STRING_DSA "DSA PRIVATE KEY"
#define PEM_STRING_DSA_PUBLIC "DSA PUBLIC KEY"
#define PEM_STRING_PKCS7 "PKCS7"
#define PEM_STRING_PKCS8 "ENCRYPTED PRIVATE KEY"
#define PEM_STRING_PKCS8INF "PRIVATE KEY"
#define PEM_STRING_DHPARAMS "DH PARAMETERS"
#define PEM_STRING_SSL_SESSION "SSL SESSION PARAMETERS"
#define PEM_STRING_DSAPARAMS "DSA PARAMETERS"
#define PEM_STRING_ECDSA_PUBLIC "ECDSA PUBLIC KEY"
#define PEM_STRING_ECPARAMETERS "EC PARAMETERS"
#define PEM_STRING_ECPRIVATEKEY "EC PRIVATE KEY"
Openssl生成PEM格式文件的大致过程如下:
1) 将各种数据DER编码;
2) 将1)中的数据进行加密处理(如果需要);
3) 根据类型以及是否加密,构造PEM头;
4) 将2)中的数据进行BASE64编码,放入PEM文件。
Openssl各个类型的PEM处理函数主要是write和read函数。write函数用于生成PEM格式的文件,而read函数主要用于读取PEM格式的文件。各种类型的调用类似。
22.3 PEM函数
PEM函数定义在crypto/pem.h中。函数比较简单,主要的函数有:
1) PEM_write_XXXX/PEM_write_bio_XXXX
将XXXX代表的信息类型写入到文件/bio中。
2) PEM_read_XXXX/PEM_read_bio_XXXX
从文件/bio中读取PEM的XXXX代表类型的信息。
XXXX可用代表的有:SSL_SESSION、X509、X509_REQ、X509_AUX、X509_CRL、RSAPrivateKey、RSAPublicKey、DSAPrivateKey、PrivateKey、PKCS7、DHparams、NETSCAPE_CERT_SEQUENCE、PKCS8PrivateKey、DSAPrivateKey、DSA_PUBKEY、DSAparams、ECPKParameters、ECPrivateKey、EC_PUBKEY等。
3) PEM_ASN1_read/PEM_ASN1_read_bio
比较底层的PEM读取函数,2)中的函数都调用了这两个函数。
4) PEM_ASN1_write/PEM_ASN1_write_bio
比较底层的PEM读取函数,1)中的函数都调用了这两个函数。
5) PEM_read_bio
读取PEM文件的各个部分,包括文件类型、头信息以及消息体(base64解码后的结果)。
6) PEM_get_EVP_CIPHER_INFO
根据头信息获取对称算法,并加载初始化向量iv。
7) PEM_do_header
根据对称算法,解密数据。
8) PEM_bytes_read_bio
获取PEM数据,得到的结果为一个DER编码的明文数据,该函数先后调用了5)、 6)和7)函数。
22.4 编程示例
1)示例1
#include <openssl/pem.h>
#include <openssl/evp.h>
int mycb(char *buf,int num,int a,char *key)
{
if(key)
strcpy(buf,key);
else
{
if(a==1)
printf("请输入加密密码:\n");
else
printf("请输入解密密码:\n");
scanf("%s",buf);
}
return strlen(buf);
}
int main()
{
int ret;
BIO *out,*in;
RSA *r,*read;
int i,bits=512;
unsigned long e=RSA_3;
BIGNUM *bne;
const EVP_CIPHER *enc=NULL;
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;
}
enc=EVP_des_ede3_ofb();
out=BIO_new_file("pri.pem","w");
// ret=PEM_write_bio_RSAPrivateKey(out,r,enc,NULL,0,mycb,"123456");
// ret=PEM_write_bio_RSAPrivateKey(out,r,enc,NULL,0,NULL,"123456");
ret=PEM_write_bio_RSAPrivateKey(out,r,enc,NULL,0,mycb,NULL);
if(ret!=1)
{
RSA_free(r);
BIO_free(out);
return -1;
}
BIO_flush(out);
BIO_free(out);
out=BIO_new_file("pub.pem","w");
ret=PEM_write_bio_RSAPublicKey(out,r);
if(ret!=1)
{
RSA_free(r);
BIO_free(out);
return -1;
}
BIO_flush(out);
BIO_free(out);
OpenSSL_add_all_algorithms();
in=BIO_new_file("pri.pem","rb");
read=RSA_new();
// read=PEM_read_bio_RSAPublicKey(in,&read,NULL,NULL);
// read=PEM_read_bio_RSAPrivateKey(in,&read,mycb,"123456");
// read=PEM_read_bio_RSAPrivateKey(in,&read,NULL,"123456");
read=PEM_read_bio_RSAPrivateKey(in,&read,mycb,NULL);
if(read->d!=NULL)
printf("test ok!\n");
else
printf("err!\n");
RSA_free(read);
BIO_free(in);
return 0;
}
输出:
请输入加密密码:
123456
请输入解密密码:
123456
test ok!
本示例生成RSA密钥,并将私钥写入成PMI格式写入文件;然后再读取。主要需要注意的是回调函数的用法。用户可以采用默认的方式,也可以自己写。采用默认方式时,回调函数设为NULL,否则设置为用户实现调回调函数地址。另外,最后一个参数如果为空,将需要用户输入口令,否则采用参数所表示的口令。
2)示例2
#include <openssl/pem.h>
#include <openssl/bio.h>
int main()
{
BIO *bp;
char *name=NULL,*header=NULL;
unsigned char *data=NULL;
int len,ret,ret2;
EVP_CIPHER_INFO cipher;
OpenSSL_add_all_algorithms();
bp=BIO_new_file("server2.pem","r");
while(1)
{
ret2=PEM_read_bio(bp,&name,&header,&data,&len);
if(ret2==0)
break;
if(strlen(header)>0)
{
ret=PEM_get_EVP_CIPHER_INFO(header,&cipher);
ret=PEM_do_header(&cipher,data,&len,NULL,NULL);
if(ret==0)
{
printf("PEM_do_header err!\n");
return -1;
}
}
OPENSSL_free(name);
OPENSSL_free(header);
OPENSSL_free(data);
}
printf("test ok.\n");
BIO_free(bp);
return 0;
}
说明:
本例server2.pem的内容如下:
-----BEGIN CERTIFICATE-----
MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYDVQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDAxMDE2MjIzMTAzWhcNMDMwMTE0MjIzMTAzWjBjMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDEaMBgGA1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxIzAhBgNVBAMTGlNlcnZlciB0ZXN0IGNlcnQgKDUxMiBiaXQpMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ+zw4Qnlf8SMVIPFe9GEcStgOY2Ww/dgNdhjeD8ckUJNP5VZkVDTGiXav6ooKXfX3j/7tdkuD8Ey2//Kv7+ue0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCT0grFQeZaqYb5EYfk20XixZV4GmyAbXMftG1Eo7qGiMhYzRwGNWxEYojf5PZkYZXvSqZ/ZXHXa4g59jK/rJNnaVGMk+xIX8mxQvlV0n5O9PIha5BX5teZnkHKgL8aKKLKW1BK7YTngsfSzzaeame5iKfzitAE+OjGF+PFKbwX8Q==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,8FDB648C1260EDDA
CPdURB7aZqM5vgDzZoim/qtoLi5PdrrJol9LrH7CNqJfr9kZfmiexZrE4pV738Hh UBoidqT8moxzDtuBP54FaVri1IJgbuTZPiNbLn00pVcodHdZrrttrjy1eWLlFmN/QcCRQhIoRow+f1AhYGhsOhVH+m4fRb8P9KXpPbEDYVcG0R0EQq6ejdmhS0vV+YXGmghBSGH12i3OfRJXC0TXvazORsT322jiVdEmajND6+DpAtmMmn6JTYm2RKwgFr9vPWv9cRQaMP1yrrBCtMiSINS4mGieN1sE1IvZLhn+/QDNfS4NxgnMfFjSl26TiNd/m29ZNoeDDXEcc6HXhoS/PiT+zPBq7t23hmAroqTVehV9YkFsgr71okOTBwlYMbFJ9goC87HYjJo4t0q9IY53GCuoI1Mont3Wm9I8QlWh2tRq5uraDlSq7U6Z8fwvC2O+wFF+PhRJrgD+4cBETSQJhj7ZVrjJ8cxCbtGcE/QiZTmmyY3sirTlUnIwpKtlfOa9pwBaoL5hKk9ZYa8L1ZCKKMoB6pZw4N9OajVkMUtLiOv3cwIdZk4OIFSSm+pSfcfUdG45a1IQGLoqvt9svckz1sOUhuu5zDPIQUYrHFn3arqUO0zCPVWPMm9oeYOkB2WCz/OiNhTFynyX0r+Hd3XeT26lgFLfnCkZlXiW/UQXqXQFSjC5sWd5XJ1+1ZgAdXq0L5qv/vAIrfryNNZHRFxC8QDDI504OA1AHDkHuH9NO9Ur8U0z7qrsUAf5OnMRUK//QV11En5o/pWcZKD0SVGS03+FVqMhtTsWKzsil5CLAfMbOWUw+/1k1A==
-----END RSA PRIVATE KEY-----
PEM_read_bio函数可以循环读取文件中的内容。
PEM_do_header用于解密数据,之前必须调用函数OpenSSL_add_all_algorithms。
PEM_do_header解密后的数据放在data中,长度由len表示,len即是输入参数又是输出参数。
name、header和data等用OPENSSL_free释放内存。
第二十三章 Engine
23.1 Engine概述
Openssl硬件引擎(Engine)能够使用户比较容易地将自己的硬件加入到openssl中去,替换其提供的软件算法。一个Engine提供了密码计算中各种计算方法的集合,它用于控制openssl的各种密码计算。
23.2 Engine支持的原理
Openssl中的许多数据结构不仅包含数据本身,还包含各种操作,并且这些操作是可替换的。Openssl中这些结构集合一般叫做XXX_METHOD,有DSO_METHOD、DSA_METHOD、EC_METHOD、ECDH_METHOD、ECDSA_METHOD、DH_METHOD、RAND_METHOD、RSA_METHOD、EVP_CIPHER和EVP_MD等。以RSA结构为例(crypto/rsa/rsa.h),RSA结构不仅包含了大数n、e、d和p等等数据项目,还包含一个RSA_METHOD回调函数集合。该方法给出了RSA各种运算函数。
对于各种数据类型,要进行计算必须至少有一个可用的方法(XXX_METHOD)。因此,openssl对各种类型都提供了默认的计算方法(软算法)。如果用户实现了自己的XXX_METHOD,那么就能替换openssl提供的方法,各种计算由用户自己控制。硬件Engine就是这种原理。根据需要,一个硬件Engine可实现自己的RAND_METHOD、RSA_METHOD、EVP_CIPHER、DSA_METHOD、DH_METHOD、ECDH_METHOD和EVP_MD等,来替换对应软算法的METHOD。
23.3 Engine数据结构
Engine数据结构定义在crypto/engine/eng_int.h文件中,是对用户透明的数据结构,如 下:
struct engine_st
{
const char *id;
const char *name;
const RSA_METHOD *rsa_meth;
const DSA_METHOD *dsa_meth;
const DH_METHOD *dh_meth;
const ECDH_METHOD *ecdh_meth;
const ECDSA_METHOD *ecdsa_meth;
const RAND_METHOD *rand_meth;
const STORE_METHOD *store_meth;
ENGINE_CIPHERS_PTR ciphers;
ENGINE_DIGESTS_PTR digests;
ENGINE_GEN_INT_FUNC_PTR destroy;
ENGINE_GEN_INT_FUNC_PTR init;
ENGINE_GEN_INT_FUNC_PTR finish;
ENGINE_CTRL_FUNC_PTR ctrl;
ENGINE_LOAD_KEY_PTR load_privkey;
ENGINE_LOAD_KEY_PTR load_pubkey;
/* 其他项 */
CRYPTO_EX_DATA ex_data;
struct engine_st *prev;
struct engine_st *next;
};
本结构包含大量的运算集合函数(包括各种METHOD)供用户来实现。各项意义如下:
id:Engine标识;
name:Engine的名字;
rsa_meth:RSA方法集合;
dsa_meth:DSA方法集合;
dh_meth:DH方法集合;
ecdh_meth:ECDH方法结合;
ecdsa_meth:ECDSA方法集合;
rand_meth:随机数方法集合;
store_meth:存储方法集合;
ciphers:对称算法选取函数。硬件一般会支持多种对称算法,该回调函数用来从用户实现的多个对称算法中根据某种条件(一般是算法nid)来选择其中的一种;
digests:摘要算法选取函数。该回调函数用来从用户实现的多个摘要算法中根据某种条件(一般是算法nid)来选择其中的一种;
destroy:销毁引擎函数;
init:初始化引擎函数;
finish:完成回调函数;
ctrl:控制函数;
load_privkey:加载私钥函数;
load_pubkey:加载公钥函数;
ex_data:扩展数据结构,可用来存放用户数据;
prev/next:用于构建Engine链表,openssl中的硬件Engine可能不止一个。
上述这些函数,用户根据应用的需求来实现其中的一种或多种。
23.4 openssl 的Engine源码
Openssl的Engine源码分为四类:
1) 核心实现
在crypto/engine目录下,是其核心实现。当同时有多个硬件Engine时,openssl分别为cipher对称算法(tb_cipher.c)、dh算法(tb_dh.c)、digest摘要算法(tb_digest.c)、dsa算法(tb_dsa.c)、ecdh算法(tb_ecdh.c)、ecdsa算法(tb_ecdsa.c)、rand随机数算法(tb_rand.c)、rsa算法(tb_rsa.c)和存储方式(tb_store.c)维护一个哈希表。所有用户实现的硬件Engine都注册在这些全局的哈希表中。同时,用户使用的时候,能够指定各种算法默认的硬件Engine。
2) 内置硬件Engine
源码位于engines目录,实现了一些硬件Engine。
3) 范例
源码位于demos/engines目录下,供用户学习参考。
4) 分散于其他各个运算模块用于支持Engine
各个运算模块都支持Engine,当提供了Engine时,将会采用Engine中的算法。
23.5 Engine函数
主要函数如下:
1) ENGINE_add
将Engine加入全局到链表中。
2) ENGINE_by_id
根据id来获取Engine。
3) ENGINE_cleanup
清除所有Engine数据。
4) const EVP_CIPHER *ENGINE_get_cipher(ENGINE *e, int nid)
根据指定的硬件Engine以及对称算法的nid,获取Engine实现的对应的 EVP_CIPHER,用于对称计算。
5) ENGINE_get_cipher_engine
根据对称算法nid来获取Engine。
6) ENGINE_get_ciphers/ENGINE_set_ciphers
获取/设置指定Engine的对称算法选取函数地址,该函数用于从Engine中选择一种对称算法。
7) ENGINE_get_ctrl_function
获取Engine的控制函数地址。
8) const DH_METHOD *ENGINE_get_DH(const ENGINE *e)
获取Engine的DH_METHOD。
9) const EVP_MD *ENGINE_get_digest(ENGINE *e, int nid)
根据Engine和摘要算法nid来获取Engine中实现的摘要方法EVP_MD。
10) ENGINE *ENGINE_get_digest_engine(int nid)
根据摘要算法nid来获取Engine。
11)ENGINE_get_digests/ENGINE_set_digests
获取/设置指定Engine的摘要算法选取函数地址,该函数用于从Engine中选择一种摘要算法。
12) const DSA_METHOD *ENGINE_get_DSA(const ENGINE *e)
获取Engine的DSA方法。
13) int ENGINE_register_XXX(ENGINE *e)
注册函数,将某一个Engine添加到对应方法的哈希表中。
14) void ENGINE_unregister_XXX(ENGINE *e)
将某一个Engine从对应的哈希表中删除。
15) void ENGINE_register_all_XXX(void)
将所有的Engine注册到对应方法的哈希表中。
16)ENGINE_set_default_XXXX
设置某Engine为对应XXXX方法的默认Engine。
17) ENGINE_get_default_XXXX
获取XXXX方法的默认Engine。
18)ENGINE_load_XXXX
加载某种Engine。
19) ENGINE_get_RAND/ENGINE_set_RAND
获取/设置Engine的随机数方法。
20) ENGINE_get_RSA/ENGINE_set_RSA
获取/设置Engine的RSA方法。
21) ENGINE_get_first/ENGINE_get_next/ENGINE_get_prev/ENGINE_get_last
Engine链表操作函数。
22)ENGINE_set_name/ENGINE_get_name
设置/获取Engine名字。
23)ENGINE_set_id/ENGINE_get_id
设置/获取Engine的id。
24) int ENGINE_set_default(ENGINE *e, unsigned int flags)
根据flags将e设置为各种方法的默认Engine。
25) ENGINE_set_XXX_function
设置Engine中XXX对应的函数。
26) ENGINE_get_XXX_function
获取Engine中XXX对应的函数。
27) ENGINE_ctrl
Engine控制函数。
28) ENGINE_get_ex_data/ENGINE_set_ex_data
获取/设置Engine的扩展数据。
29)ENGINE_init/ENGINE_finish
Engine初始化/结束。
ENGINE_up_ref
给Engine增加一个引用。
ENGINE_new/ENGINE_free
生成/释放一个Engine数据结构。
ENGINE_register_complete
将给定的Engine,对于每个方法都注册一遍。
ENGINE_register_all_complete
将所有的Engine,对于每个方法都注册一遍。
23.6 实现Engine示例
以下的示例演示了采用Engine机制,来改变openssl的各种运算行为。实现的Engine方法有:随机数方法、对称算法、摘要算法以及RSA运算算法。其中,RSA计算中,密钥ID存放在Engine的扩展数据结构中。
#include <openssl/rsa.h>
#include <openssl/rand.h>
#include <openssl/engine.h>
static int hw_get_random_bytes(unsigned char* buf, int num)
{
int i;
printf("call hw_get_random_bytes\n");
for(i=0;i<num;i++)
memset(buf++,i,1);
return 1;
}
/* 生成RSA密钥对 */
static int genrete_rsa_key(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb)
{
printf("genrete_rsa_key \n");
return 1;
}
/* RSA公钥加密 */
int rsa_pub_enc(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding)
{
printf("call rsa_pub_enc \n");
return 1;
}
/*RSA公钥解密 */
int rsa_pub_dec(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding)
{
printf("call rsa_pub_enc \n");
return 1;
}
/* RSA私钥加密 */
int rsa_priv_enc(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding)
{
char *keyid;
/* 获取私钥id */
keyid=(char *)ENGINE_get_ex_data(rsa->engine,0);
printf("call rsa_pub_dec \n");
printf("use key id :%d \n",keyid);
return 1;
}
/* RSA私钥解密 */
int rsa_priv_dec(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding)
{
printf("call rsa_priv_dec \n");
return 1;
}
/* RSA算法 */
RSA_METHOD hw_rsa =
{
"hw cipher",
rsa_pub_enc,
rsa_pub_dec,
rsa_priv_enc,
rsa_priv_dec,
NULL,
NULL,
NULL,
NULL,
RSA_FLAG_SIGN_VER,
NULL,
NULL,
NULL,
genrete_rsa_key
};
/* 随机数方法 */
static RAND_METHOD hw_rand =
{
NULL,
hw_get_random_bytes,
NULL,
NULL,
NULL,
NULL,
};
/* Engine的id */
static const char *engine_hw_id = "ID_hw";
/* Engine的名字 */
static const char *engine_hw_name = "hwTest";
static int hw_init(ENGINE *e)
{
printf("call hw_init\n");
return 1;
}
static int hw_destroy(ENGINE *e)
{
printf("call hw_destroy\n");
return 1;
}
static int hw_finish(ENGINE *e)
{
printf("call hw_finish\n");
return 0;
}
static EVP_PKEY *hw_load_privkey(ENGINE* e, const char* key_id,
UI_METHOD *ui_method, void *callback_data)
{
/* 将密钥id放在ENGINE的扩展数据中 */
int index;
printf("call hw_load_privkey\n");
index=0;
ENGINE_set_ex_data(e, index, (char *)key_id);
return NULL;
}
#define HW_SET_RSA_PRIVATE_KEY 1
/* 实现自己的控制函数 */
static int hw_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void))
{
switch(cmd)
{
case HW_SET_RSA_PRIVATE_KEY:
hw_load_privkey(e,p,NULL,NULL);
break;
default:
printf("err.\n");
return -1;
}
return 0;
}
static EVP_PKEY *hw_load_pubkey(ENGINE* e, const char* key_id,
UI_METHOD *ui_method, void *callback_data)
{
printf("call hw_load_pubkey\n");
return NULL;
}
static const ENGINE_CMD_DEFN hw_cmd_defns[] = {
{ENGINE_CMD_BASE,
"SO_PATH",
"Specifies the path to the 'hw' shared library",
ENGINE_CMD_FLAG_STRING},
{0, NULL, NULL, 0}
};
static int hw_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc)
{
return 1;
}
static int hw_cipher_enc(EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char *in, unsigned int inl)
{
memcpy(out,in,inl);
return 1;
}
#include <openssl/objects.h>
/* 定义自己的des_ecb硬件算法*/
static const EVP_CIPHER EVP_hw_c=
{
NID_des_ecb,
1,8,0,
8,
hw_init_key,
hw_cipher_enc,
NULL,
1,
NULL,
NULL,
NULL,
NULL
};
const EVP_CIPHER *EVP_hw_cipher(void)
{
return(&EVP_hw_c);
}
/* 选择对称计算函数 */
static int cipher_nids[] =
{ NID_des_ecb, NID_des_ede3_cbc, 0 };
static int hw_ciphers(ENGINE *e, const EVP_CIPHER **cipher, const int **nids, int nid)
{
if(cipher==NULL)
{
*nids = cipher_nids;
return (sizeof(cipher_nids)-1)/sizeof(cipher_nids[0]);
}
switch (nid)
{
case NID_des_ecb:
*cipher = EVP_hw_ciphe()r;
break;
//其他对称函数
}
return 1;
}
static int init(EVP_MD_CTX *ctx)
{
printf("call md init\n");
return 1;
}
static int update(EVP_MD_CTX *ctx,const void *data,size_t count)
{
printf("call md update\n");
return 1;
}
static int final(EVP_MD_CTX *ctx,unsigned char *md)
{
int i;
printf("call md final\n");
for(i=0;i<20;i++)
memset(md++,i,1);
return 1;
}
int mySign(int type, const unsigned char *m, unsigned int m_length,
unsigned char *sigret, unsigned int *siglen, void *key)
{
RSA *k;
int keyid;
k=(RSA *)key;
/* 获取硬件中的私钥ID,进行计算 */
keyid=ENGINE_get_ex_data(k->engine,0);
printf("call mySign\n");
printf("use key id is %d\n",keyid);
return 1;
}
int myVerify(int type, const unsigned char *m, unsigned int m_length,
const unsigned char *sigbuf, unsigned int siglen,
void *key)
{
printf("call myVerify\n");
return 1;
}
static int digest_nids[] =
{ NID_sha1, NID_md5, 0 };
/* 实现的sha1摘要算法 */
static const EVP_MD hw_newmd=
{
NID_sha1,
NID_sha1WithRSAEncryption,
SHA_DIGEST_LENGTH,
0,
init,
update,
final,
NULL,
NULL,
mySign, /* sign */
myVerify, /* verify */
//sizeof(EVP_MD *)+sizeof(SHA_CTX),
6
};
static EVP_MD * EVP_hw_md()
{
return (&hw_newmd);
}
/* 选择摘要算法的函数 */
static int hw_md(ENGINE *e, const EVP_MD **digest,const int **nids, int nid)
{
if(digest==NULL)
{
*nids = digest_nids;
return (sizeof(digest_nids)-1)/sizeof(digest_nids[0]);
}
switch (nid)
{
case NID_sha1:
*digest = EVP_hw_md();
break;
//其他摘要函数
}
return 1;
}
static int bind_helper(ENGINE *e)
{
int ret;
ret=ENGINE_set_id(e, engine_hw_id);
if(ret!=1)
{
printf("ENGINE_set_id failed\n");
return 0;
}
ret=ENGINE_set_name(e, engine_hw_name);
if(ret!=1)
{
printf("ENGINE_set_name failed\n");
return 0;
}
ret=ENGINE_set_RSA(e, &hw_rsa);
if(ret!=1)
{
printf("ENGINE_set_RSA failed\n");
return 0;
}
ret=ENGINE_set_RAND(e, &hw_rand);
if(ret!=1)
{
printf("ENGINE_set_RAND failed\n");
return 0;
}
ret=ENGINE_set_destroy_function(e, hw_destroy);
if(ret!=1)
{
printf("ENGINE_set_destroy_function failed\n");
return 0;
}
ret=ENGINE_set_init_function(e, hw_init);
if(ret!=1)
{
printf("ENGINE_set_init_function failed\n");
return 0;
}
ret=ENGINE_set_finish_function(e, hw_finish);
if(ret!=1)
{
printf("ENGINE_set_finish_function failed\n");
return 0;
}
ret=ENGINE_set_ctrl_function(e, hw_ctrl);
if(ret!=1)
{
printf("ENGINE_set_ctrl_function failed\n");
return 0;
}
ret=ENGINE_set_load_privkey_function(e, hw_load_privkey);
if(ret!=1)
{
printf("ENGINE_set_load_privkey_function failed\n");
return 0;
}
ret=ENGINE_set_load_pubkey_function(e, hw_load_pubkey);
if(ret!=1)
{
printf("ENGINE_set_load_pubkey_function failed\n");
return 0;
}
ret=ENGINE_set_cmd_defns(e, hw_cmd_defns);
if(ret!=1)
{
printf("ENGINE_set_cmd_defns failed\n");
return 0;
}
ret=ENGINE_set_ciphers(e,hw_ciphers);
if(ret!=1)
{
printf("ENGINE_set_ciphers failed\n");
return 0;
}
ret=ENGINE_set_digests(e,hw_md);
if(ret!=1)
{
printf("ENGINE_set_digests failed\n");
return 0;
}
return 1;
}
static ENGINE *engine_hwcipher(void)
{
ENGINE *ret = ENGINE_new();
if(!ret)
return NULL;
if(!bind_helper(ret))
{
ENGINE_free(ret);
return NULL;
}
return ret;
}
void ENGINE_load_hwcipher()
{
ENGINE *e_hw = engine_hwcipher();
if (!e_hw) return;
ENGINE_add(e_hw);
ENGINE_free(e_hw);
ERR_clear_error();
}
#define HW_set_private_keyID(a) func(e,a,0,(void *)1,NULL)
#include <openssl/engine.h>
#include <openssl/evp.h>
int main()
{
ENGINE *e;
RSA_METHOD *meth;
int ret,num=20,i;
char buf[20],*name;
EVP_CIPHER *cipher;
EVP_MD *md;
EVP_MD_CTX mctx,md_ctx;
EVP_CIPHER_CTX ciph_ctx,dciph_ctx;
unsigned char key[8],iv[8];
unsigned char in[50],out[100],dd[60];
int inl,outl,total,dtotal;
RSA *rkey;
RSA_METHOD *rsa_m;
EVP_PKEY *ek,*pkey;
ENGINE_CTRL_FUNC_PTR func;
OpenSSL_add_all_algorithms();
ENGINE_load_hwcipher();
e=ENGINE_by_id("ID_hw");
name = (char *)ENGINE_get_name(e);
printf("engine name :%s \n",name);
/* 随机数生成 */
ret=RAND_set_rand_engine(e);
if(ret!=1)
{
printf("RAND_set_rand_engine err\n");
return -1;
}
ret=ENGINE_set_default_RAND(e);
if(ret!=1)
{
printf("ENGINE_set_default_RAND err\n");
return -1;
}
ret=RAND_bytes((unsigned char *)buf,num);
/* 对称加密 */
for(i=0;i<8;i++)
memset(&key[i],i,1);
EVP_CIPHER_CTX_init(&ciph_ctx);
/* 采用Engine对称算法 */
cipher=EVP_des_ecb();
ret=EVP_EncryptInit_ex(&ciph_ctx,cipher,e,key,iv);
if(ret!=1)
{
printf("EVP_EncryptInit_ex err\n");
return -1;
}
strcpy((char *)in,"zcpsssssssssssss");
inl=strlen((const char *)in);
total=0;
ret=EVP_EncryptUpdate(&ciph_ctx,out,&outl,in,inl);
if(ret!=1)
{
printf("EVP_EncryptUpdate err\n");
return -1;
}
total+=outl;
ret=EVP_EncryptFinal(&ciph_ctx,out+total,&outl);
if(ret!=1)
{
printf("EVP_EncryptFinal err\n");
return -1;
}
total+=outl;
/* 解密 */
dtotal=0;
EVP_CIPHER_CTX_init(&dciph_ctx);
ret=EVP_DecryptInit_ex(&dciph_ctx,cipher,e,key,iv);
if(ret!=1)
{
printf("EVP_DecryptInit_ex err\n");
return -1;
}
ret=EVP_DecryptUpdate(&dciph_ctx,dd,&outl,out,total);
if(ret!=1)
{
printf("EVP_DecryptUpdate err\n");
return -1;
}
dtotal+=outl;
ret=EVP_DecryptFinal(&dciph_ctx,dd+dtotal,&outl);
if(ret!=1)
{
printf("EVP_DecryptFinal err\n");
return -1;
}
dtotal+=outl;
/* Engine摘要 */
EVP_MD_CTX_init(&mctx);
md=EVP_sha1();
ret=EVP_DigestInit_ex(&mctx,md,e);
if(ret!=1)
{
printf("EVP_DigestInit_ex err.\n");
return -1;
}
ret=EVP_DigestUpdate(&mctx,in,inl);
if(ret!=1)
{
printf("EVP_DigestInit_ex err.\n");
return -1;
}
ret=EVP_DigestFinal(&mctx,out,(unsigned int *)&outl);
if(ret!=1)
{
printf("EVP_DigestInit_ex err.\n");
return -1;
}
func=ENGINE_get_ctrl_function(e);
/* 设置计算私钥ID */
HW_set_private_keyID(1);
rkey=RSA_new_method(e);
pkey=EVP_PKEY_new();
EVP_PKEY_set1_RSA(pkey,rkey);
EVP_MD_CTX_init(&md_ctx);
ret=EVP_SignInit_ex(&md_ctx,EVP_sha1(),e);
if(ret!=1)
{
printf("EVP_SignInit_ex err\n");
return -1;
}
ret=EVP_SignUpdate(&md_ctx,in,inl);
if(ret!=1)
{
printf("EVP_SignUpdate err\n");
return -1;
}
ret=EVP_SignFinal(&md_ctx,out,(unsigned int *)&outl,pkey);
if(ret!=1)
{
printf("EVP_SignFinal err\n");
return -1;
}
/* 私钥加密 */
RSA_private_encrypt(inl,in,out,rkey,1);
/* 公钥解密 */
/* 公钥加密 */
/* 私钥解密 */
printf("all test ok.\n");
ENGINE_free(e);
ENGINE_finish(e);
return 0;
}
读者可以跟踪调试上述示例来研究各种细节。
第二十四章 通用数据结构
24.1通用数据结构
本文中的通用数据结构主要指的是证书相关的各个数据结构。它们主要用在数字证书申请、数字证书和CRL中。主要包括如下数据结构:
- X509_ALGOR,X509算法;
- X509_VAL,有效时间;
- X509_PUBKEY,X509公钥;
- X509_SIG,X509摘要或者签名值;
- X509_NAME_ENTRY,X509中的一项名称;
- X509_NAME,X509名称集合;
- X509_EXTENSION,X509扩展项;
- X509_ATTRIBUTE,X509属性;
- GENERAL_NAME,通用名称。
通过openssl提供的ASN1库,这些数据结构都是可以进行DER编解码的。用户主要需要了解它们各项的意义、对它们的编解码以及对它们的set和get操作。
24.2 X509_ALGOR
该数据结构用来表示算法,它定义在crypto/x509/x509.h中,如下:
struct X509_algor_st
{
ASN1_OBJECT *algorithm;
ASN1_TYPE *parameter;
}
包含两项:
algorithm:ASN1_OBJECT类型,表明了是何种算法;
parameter:ASN1_TYPE类型,代表该算法需要的参数。ASN1_TYPE类型可以存放任意数据。
该结构的DER编解码接口在crypto/asn1/x_algor.c中由ASN1宏来实现,其中parameter是可选的。该结构相关的函数为:new(生成数据结构)、free(释放数据结构)、i2d(将它转换为DER编码)、d2i(由DER编码转换为该结构)和dup(拷贝)。
编程示例如下:
#include <string.h>
#include <openssl/x509.h>
int main()
{
FILE *fp;
char *buf,*p;
char data[]={"12345678"},read[1024];
int len;
X509_ALGOR *alg=NULL,*alg2=NULL,*alg3=NULL;
/* new 函数 */
alg=X509_ALGOR_new();
/* 构造内容 */
alg->algorithm=OBJ_nid2obj(NID_sha256);
alg->parameter=ASN1_TYPE_new();
ASN1_TYPE_set_octetstring(alg->parameter,data,strlen(data));
/* i2d 函数 */
len=i2d_X509_ALGOR(alg,NULL);
p=buf=malloc(len);
len=i2d_X509_ALGOR(alg,&p);
/* 写入文件 */
fp=fopen("alg.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
/* 读文件 */
fp=fopen("alg.cer","rb");
len=fread(read,1,1024,fp);
fclose(fp);
p=read;
/* d2i 函数 */
d2i_X509_ALGOR(&alg2,&p,len);
if(alg2==NULL)
{
printf("err\n");
}
/* dup 函数 */
alg3=X509_ALGOR_dup(alg);
/* free 函数 */
X509_ALGOR_free(alg);
if(alg2)
X509_ALGOR_free(alg2);
X509_ALGOR_free(alg3);
free(buf);
return 0;
}
24.3 X509_VAL
该数据结构用来表示有效时间,定义在crypto/x509/x509.h中,如下:
typedef struct X509_val_st
{
ASN1_TIME *notBefore;
ASN1_TIME *notAfter;
} X509_VAL;
包含两项:
notBefore:生效日期;
notAfter:失效日期;
该结构的DER编解码通过宏在crypto/asn1/x_val.c中。包括是个函数:new、free、i2d和d2i。
编程示例如下:
#include <string.h>
#include <openssl/x509.h>
int main()
{
FILE *fp;
char *buf,*p;
char read[1024];
int len;
X509_VAL *val=NULL,*val2=NULL;
time_t t;
/* new 函数 */
val=X509_VAL_new();
/* 构造内容 */
t=time(0);
ASN1_TIME_set(val->notBefore,t);
ASN1_TIME_set(val->notAfter,t+1000);
/* i2d 函数 */
len=i2d_X509_VAL(val,NULL);
p=buf=malloc(len);
len=i2d_X509_VAL(val,&p);
/* 写入文件 */
fp=fopen("val.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
/* 读文件 */
fp=fopen("val.cer","rb");
len=fread(read,1,1024,fp);
fclose(fp);
p=read;
/* d2i 函数 */
d2i_X509_VAL(&val2,&p,len);
if(val2==NULL)
{
printf("err\n");
}
/* free 函数 */
X509_VAL_free(val);
if(val2)
X509_VAL_free(val2);
free(buf);
return 0;
}
24.4 X509_SIG
该结构用来存放摘要或者签名值,定义在crypto/x509/x509.h中,如下:
typedef struct X509_sig_st
{
X509_ALGOR *algor;
ASN1_OCTET_STRING *digest;
} X509_SIG;
其中,algor为算法,digest用于存放摘要或者签名值。对数据进行签名时,要先对数据摘要,摘要的结果要通过本结构进行DER编码,然后才能用私钥进行计算,此时digest中存放的就是摘要值。
本结构的DER编码通过ASN1宏在crypto/asn1/x_sig.c中实现,包括new、free、i2d和d2i函数。
用于签名的摘要DER编码示例如下:
#include <string.h>
#include <openssl/x509.h>
int main()
{
X509_SIG *sig;
unsigned char data[50]={"123456789"};
unsigned char dgst[20];
int len;
unsigned char *buf,*p;
FILE *fp;
SHA1(data,strlen(data),dgst);
sig=X509_SIG_new();
/* sig->algor->algorithm 不是动态分配的,所有不需要释放
ASN1_OBJECT_free(sig->algor->algorithm); */
sig->algor->algorithm=OBJ_nid2obj(NID_sha1);
ASN1_OCTET_STRING_set(sig->digest,dgst,20);
len=i2d_X509_SIG(sig,NULL);
p=buf=malloc(len);
len=i2d_X509_SIG(sig,&p);
fp=fopen("sig.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
free(buf);
X509_SIG_free(sig);
return 0;
}
24.5 X509_NAME_ENTRY
该数据结构代表了一个名称,数据结构在crypto/x509/x509.h中定义如下:
typedef struct X509_name_entry_st
{
ASN1_OBJECT *object;
ASN1_STRING *value;
int set;
int size;
} X509_NAME_ENTRY;
每个X509_NAME_ENTRY对应于一个证书中的C、OU和O等实体名称,其中object表明了实体的类型是C还是OU等;value表明了该实体的内容,这两项用于DER编解码。该结构的DER编解码在crypto/asn1/x_name.c中由宏实现,包括new、free、i2d、d2i和dup函数。
24.6 X509_NAME
该结构是一个名称集合,在crypto/x509/x509.h中定义如下:
struct X509_name_st
{
STACK_OF(X509_NAME_ENTRY) *entries;
int modified;
#ifndef OPENSSL_NO_BUFFER
BUF_MEM *bytes;
#else
char *bytes;
#endif
unsigned long hash;
}
它主要包含了X509_NAME_ENTRY堆栈信息,bytes用于存放DER编码值,hash为该结构的摘要计算值。该结构的DER编解码在crypto/asn1/x_name.c中由宏实现。
主要函数:
1) int X509_NAME_add_entry(X509_NAME *name, X509_NAME_ENTRY *ne, int loc,
int set)
将一个X509_NAME_ENTRY放入X509_NAME的堆栈中,在堆栈中的位置由loc指定。
2) int X509_NAME_add_entry_by_NID(X509_NAME *name, int nid, int type, unsigned char *bytes, int len, int loc, int set)
根据nid在X509_NAME的X509_NAME_ENTRY堆栈中添加一项;bytes 为要添加项的值,type指明了types的ASN1类型,loc为堆栈中的位置;根据nid能够获取ASN1_OBJECT(OBJ_nid2obj函数)。
3) X509_NAME_add_entry_by_OBJ
与2)类似,只是要添加的项由ASN1_OBJECT来表示。
4) X509_NAME_add_entry_by_txt
与2)类似,只是要添加的项由字符串来表示,根据txt能获取ASN1_OBJECT(OBJ_txt2obj函数)。
5) X509_NAME_ENTRY 509_NAME_ENTRY_create_by_NID(X509_NAME_ENTRY **ne, int nid, int type, unsigned char *bytes, int len)
根据nid来生成一个X509_NAME_ENTRY,bytes 为要添加项的值,type指明了types的ASN1类型。
6) X509_NAME_ENTRY_create_by_OBJ
与5)类似,生成的项由ASN1_OBJECT来表示。
7) X509_NAME_ENTRY_create_by_txt
与5)类似,生成的项有字符串来表示。
8) int X509_NAME_get_text_by_NID(X509_NAME *name, int nid, char *buf, int len)
根据NID来获取值,结果存放在buf中。
9) X509_NAME_get_text_by_OBJ
根据ASN1_OBJECT来获取值。
10) int X509_NAME_get_index_by_OBJ(X509_NAME *name,
ASN1_OBJECT *obj, int lastpos)
根据ASN1_OBJECT获取NAME_ENTRY在堆栈中的位置。
11) X509_NAME_get_index_by_NID
根据NID获取X509_NAME_ENTRY在堆栈中的位置。
12) X509_NAME_cmp
名字比较。
13) X509_NAME_delete_entry
从堆栈中删除一个指定位置的X509_NAME_ENTRY,并将它返回。
14) X509_NAME_digest
根据指定的算法,对X509_NAME做摘要计算。
15) X509_NAME_dup
名字拷贝。
16) X509_NAME_entry_count
获取X509_NAME的X509_NAME_ENTRY堆栈中元素个数。
17) X509_NAME_ENTRY_dup
X509_NAME_ENTRY拷贝。
18) X509_NAME_ENTRY_get/set_data
获取/设置一项名称的值;set函数还需指明值的ASN1类型。
19) X509_NAME_ENTRY_get/set_object
获取/设置一项名称的ASN1_OBJECT。
20) X509_NAME_get_entry
根据指定堆栈位置获取一个X509_NAME_ENTRY。
21) X509_NAME_hash
摘要计算,该结果是对MD5的结果处理后的值。
22)char *X509_NAME_oneline(X509_NAME *a, char *buf, int len)
将a表示的名字变成:/OU=z/CN=的形式放在buf中,返回buf首地址。
23) X509_NAME_print/ X509_NAME_print_ex
打印X509_NAME到bio中。
24) X509_NAME_print_ex_fp
打印X509_NAME到FILE中。
25) int X509_NAME_set(X509_NAME **xn, X509_NAME *name)
通过dup函数,设置*xn的值为name。
编程示例:
#include <string.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
int main()
{
X509 *x;
BIO *b,*out;
int ret,len,position,count;
unsigned int mdl;
unsigned char md[20];
char buf[1024],*bufp,bytes[500];
const EVP_MD *type;
X509_NAME *xname,*xn;
unsigned long hv=0;
FILE *fp;
ASN1_OBJECT *obj;
X509_NAME_ENTRY *entry,*c=NULL,*c1;
ASN1_STRING *str;
/* cert.cer为PEM格式的数字证书 */
b=BIO_new_file("b64cert.cer","r");
if(b==NULL)
{
printf("can not open b64cert.cer!\n");
return -1;
}
x=PEM_read_bio_X509(b,NULL,NULL,NULL);
/* X509_NAME 函数 */
/* X509_get_issuer_name,返回指针地址 */
xname=X509_get_issuer_name(x);
/* X509_get_subject_name,返回指针地址 */
xname=X509_get_subject_name(x);
/* X509_NAME_hash,将X509_NAME数据结构中缓存的DER编码值(放在bytes中)MD5,其结果再做运算,注意xname->hash此时的值无意义 */
hv=X509_NAME_hash(xname);
/* X509_NAME_print */
out=BIO_new(BIO_s_file());
BIO_set_fp(out,stdout,BIO_NOCLOSE);
X509_NAME_print(out,xname,0);
printf("\n");
/* X509_NAME_print_ex_fp */
fp=stdout;
X509_NAME_print_ex_fp(fp,xname,0,XN_FLAG_SEP_MULTILINE);
printf("\n\n");
/* X509_NAME_print_ex,XN_FLAG_SEP_MULTILINE表明个值打印时占一行*/
X509_NAME_print_ex(out,xname,0,XN_FLAG_SEP_MULTILINE);
printf("\n");
/* X509_NAME_get_text_by_NID */
len=1024;
ret=X509_NAME_get_text_by_NID(xname,NID_commonName,buf,len);
printf("commonName : %s\n\n",buf);
/* X509_NAME_get_text_by_OBJ */
len=1024;
obj=OBJ_nid2obj(NID_commonName);
memset(buf,0,1024);
ret=X509_NAME_get_text_by_OBJ(xname,obj,buf,len);
printf("commonName : %s\n\n",buf);
/* X509_NAME_get_index_by_NID */
position=X509_NAME_get_index_by_NID(xname,NID_commonName,-1);
entry=X509_NAME_get_entry(xname,position);
printf("entry value : %s\n",entry->value->data);
/* X509_NAME_ENTRY_get_data */
str=X509_NAME_ENTRY_get_data(entry);
/* X509_NAME_ENTRY_get_object */
obj=X509_NAME_ENTRY_get_object(entry);
/* X509_NAME_entry_count */
count=X509_NAME_entry_count(xname);
/* X509_NAME_get_index_by_OBJ */
len=1024;
memset(buf,0,1024);
position=X509_NAME_get_index_by_OBJ(xname,obj,-1);
entry=X509_NAME_get_entry(xname,position);
printf("entry value : %s\n",entry->value->data);
/* X509_NAME_digest */
type=EVP_sha1();
ret=X509_NAME_digest(x->cert_info->subject,type,md,&mdl);
if(ret!=1)
{
printf("X509_NAME_digest err.\n");
BIO_free(b);
X509_free(x);
return -1;
}
/* X509_name_cmp */
ret=X509_name_cmp(x->cert_info->subject,x->cert_info->issuer);
if(ret==0)
{
printf("subject == issuer\n");
}
else
{
printf("subject != issuer\n");
}
/* X509_NAME_oneline */
len=1024;
bufp=X509_NAME_oneline(x->cert_info->subject,buf,len);
if(bufp==NULL)
{
printf("X509_NAME_oneline err\n");
}
else
{
printf("%s\n",buf);
}
/* 构造X509_NAME */
xn=X509_NAME_new();
strcpy(bytes,"openssl");
len=strlen(bytes);
/* X509_NAME_add_entry_by_txt */
ret=X509_NAME_add_entry_by_txt(xn,"commonName",V_ASN1_UTF8STRING,bytes,len,0,-1);
if(ret!=1)
{
printf("X509_NAME_add_entry_by_txt err.\n");
}
/* X509_NAME_add_entry_by_NID */
strcpy(bytes,"china");
len=strlen(bytes);
ret=X509_NAME_add_entry_by_txt(xn,LN_countryName,V_ASN1_UTF8STRING,bytes,len,0,-1);
if(ret!=1)
{
printf("X509_NAME_add_entry_by_txt err.\n");
}
/* X509_NAME_add_entry_by_OBJ */
strcpy(bytes,"myou");
len=strlen(bytes);
obj=OBJ_nid2obj(NID_organizationalUnitName);
ret=X509_NAME_add_entry_by_OBJ(xn,obj,V_ASN1_UTF8STRING,bytes,len,0,-1);
if(ret!=1)
{
printf("X509_NAME_add_entry_by_OBJ err.\n");
}
/* X509_NAME_ENTRY_create_by_NID */
strcpy(bytes,"myo");
len=strlen(bytes);
c=X509_NAME_ENTRY_create_by_NID(&c,NID_organizationName,V_ASN1_UTF8STRING,bytes,len);
/* X509_NAME_add_entry */
ret=X509_NAME_add_entry(xn,c,1,-1);
if(ret!=1)
{
printf("X509_NAME_add_entry_by_OBJ err.\n");
}
/* X509_NAME_ENTRY_set_object */
obj=OBJ_nid2obj(NID_localityName);
c1=X509_NAME_ENTRY_new();
ret=X509_NAME_ENTRY_set_object(c1,obj);
if(ret!=1)
{
printf("X509_NAME_ENTRY_set_object err.\n");
}
strcpy(bytes,"mylocal");
len=strlen(bytes);
/* X509_NAME_ENTRY_set_data */
ret=X509_NAME_ENTRY_set_data(c1,V_ASN1_UTF8STRING,bytes,len);
if(ret!=1)
{
printf("X509_NAME_ENTRY_set_data err.\n");
}
ret=X509_NAME_add_entry(xn,c,2,-1);
if(ret!=1)
{
printf("X509_NAME_add_entry_by_OBJ err.\n");
}
c1=X509_NAME_delete_entry(xn,2);
/* X509_NAME_set */
BIO_free(b);
X509_free(x);
return 0;
}
24.7 X509_EXTENSION
本结构用于存放各种扩展项信息。
1)结构定义
数字证书扩展项,定义在crypto/x509/x509.h中,如下:
typedef struct X509_extension_st
{
ASN1_OBJECT *object;
ASN1_BOOLEAN critical;
ASN1_OCTET_STRING *value;
} X509_EXTENSION;
其中object指明是哪种扩展项;critical指明是否为关键扩展项,为0xFF时为关键扩展项,-1为非关键扩展项;value为DER编码的具体扩展项的值。该结构的DER编解码在crypto/asn1/x_exten.c中由宏实现,包括new、free、i2d、d2i和dup函数。扩展项的DER编解码可直接采用i2d和d2i来完成,也可用采用openssl提供的其他函数。
2)通过X509V3_EXT_METHOD进行DER编解码
Openssl通过X509V3_EXT_METHOD来实现对扩展项的编解码。X509V3_EXT_METHOD定义在crypto/x509v3/x509v3.h中,如下:
struct v3_ext_method
{
int ext_nid;
int ext_flags;
ASN1_ITEM_EXP *it;
X509V3_EXT_NEW ext_new;
X509V3_EXT_FREE ext_free;
X509V3_EXT_D2I d2i;
X509V3_EXT_I2D i2d;
X509V3_EXT_I2S i2s;
X509V3_EXT_S2I s2i;
X509V3_EXT_I2V i2v;
X509V3_EXT_V2I v2i;
X509V3_EXT_I2R i2r;
X509V3_EXT_R2I r2i;
void *usr_data;
};
typedef struct v3_ext_method X509V3_EXT_METHOD;
该结构以ext_nid表示是何种扩展项,以it、d2i和i2d函数来指明来它的DER编解码函数。这样,只要知道了ext_nid,就能够对数据进行DER编解码。Openssl对于每个支持的扩展项都实现了上述数据结构,这些文件都在crypto/x509v3目录下:
- v3_akey.c:权威密钥标识,实现了AUTHORITY_KEYID的DER编解码和X509V3_EXT_METHOD;
- v3_alt.c:颁发者别名,实现了GENERAL_NAMES的509V3_EXT_METHOD;
- v3_bcons.c:基本约束,实现了BASIC_CONSTRAINTS的DER编解码和509V3_EXT_METHOD;
- v3_cpols.c:证书策略,实现了CERTIFICATEPOLICIES的DER编解码和509V3_EXT_METHOD;
- v3_crld.c:CRL发布点,实现了CRL_DIST_POINTS的DER编解码和509V3_EXT_METHOD;
- v3_enum.c:证书撤销原因,实现了其509V3_EXT_METHOD;
- v3_extku.c:扩展密钥用法,实现了EXTENDED_KEY_USAGE的DER编解码,扩展密钥和ocsp_accresp的509V3_EXT_METHOD;
- v3_info.c:权威信息获取,实现了AUTHORITY_INFO_ACCESS的DER编解码,v3_info和v3_sinfo两个509V3_EXT_METHOD;
- v3_int.c:实现了v3_crl_num、v3_delta_crl和v3_inhibit_anyp(继承任何策略)的509V3_EXT_METHOD;
- v3_ncons.c:名字约束,实现了NAME_CONSTRAINTS的DER编解码和它的509V3_EXT_METHOD;
- v3_ocsp.c:实现了OCSP相关的多个扩展项的509V3_EXT_METHOD;
- v3_pci.c:实现了代理证书扩展项的509V3_EXT_METHOD;
- v3_pcons.c:策略约束,实现了POLICY_CONSTRAINTS的DER编解码和509V3_EXT_METHOD;
- v3_pku.c:密钥有效期,实现了PKEY_USAGE_PERIOD的DER编解码和它的509V3_EXT_METHOD;
- v3_pmaps.c:策略映射,实现了POLICY_MAPPINGS的DER编解码和它的509V3_EXT_METHOD;
- v3_skey.c:主体密钥标识,实现了该扩展项的509V3_EXT_METHOD;
- v3_sxnet.c:实现了SXNET的DER编解码和它的509V3_EXT_METHOD。
openssl为509V3_EXT_METHOD维护了两个表供调用者查找和使用。一个表定义在crypto/x509v3/ext_dat.h中,如下:
static X509V3_EXT_METHOD *standard_exts[] = {
&v3_nscert,
&v3_ns_ia5_list[0],
&v3_ns_ia5_list[1],
/* 其他 */
&v3_policy_mappings,
&v3_inhibit_anyp
};
该表是一个全局表。另外一个表在crypto/x509v3/v3_lib.c中,是一个全局的X509V3_EXT_METHOD堆栈,定义如下:
static STACK_OF(X509V3_EXT_METHOD) *ext_list = NULL;
当用户其他扩展的时候,可以实现自己的X509V3_EXT_METHOD,并调用X509V3_EXT_add函数放入堆栈。
当用户根据扩展项的nid查找对应的X509V3_EXT_METHOD时,首先查找standard_exts,然后在查找ext_list。找到后,用户就能根据X509V3_EXT_METHOD中的各种方法来处理扩展项(比如,DER编解码)。
将具体的扩展项数据结构(不是指X509_EXTENSION而是一个具体扩展项,比如NAME_CONSTRAINTS)合成X509_EXTENSION时,可以采用如下函数:
X509_EXTENSION *X509V3_EXT_i2d(int ext_nid, int crit, void *ext_struc)
其中ext_nid指明了是那种扩展项,crit表明是否为关键扩展项,ext_struc为具体扩展项数据结构地址(比如NAME_CONSTRAINTS的地址),返回值为一个已经构造好的X509_EXTENSION。该函数首先根据ext_nid查表来获取具体扩展项的的X509V3_EXT_METHOD,然后根据X509V3_EXT_METHOD中的it或者i2d函数将具体扩展项(比如NAME_CONSTRAINTS)进行DER编码,最后再调用X509_EXTENSION_create_by_NID来生成一个扩展项并返回。
从X509_EXTENSION中提取出具体扩展项的数据结构可以采用如下函数:
void *X509V3_EXT_d2i(X509_EXTENSION *ext)
该函数首先根据X509_EXTENSION来获取是那种扩展项,并查找X509V3_EXT_METHOD表,然后根据对应的d2i函数解码X509_EXTENSION-> value中的DER编码数据,生成具体的扩展项数据结构并返回。
上述两个函数是具体扩展项和X509_EXTENSION相互转化最基本的函数,很多函数都基于它们。
主要函数:
- X509V3_EXT_add:在扩展X509V3_EXT_METHOD表ext_list中添加一个方法。
- X509V3_EXT_get_nid:根据nid来查找X509V3_EXT_METHOD。
- X509V3_EXT_get:根据扩展项来查找X509V3_EXT_METHOD,它调用了X509V3_EXT_get_nid
- X509V3_EXT_add_alias:添加一个X509V3_EXT_METHOD,使具有相同方法的X509V3_EXT_METHOD有不同的扩展项nid。
- X509V3_get_d2i:从扩展项堆栈中查找具体的扩展项,并返回具体扩展项数据结构地址。
- X509V3_EXT_print:打印单个扩展项。
- int X509V3_add1_i2d(STACK_OF(X509_EXTENSION) **x, int nid, void *value,int crit, unsigned long flags)。
往扩展项堆栈中添加一个具体的扩展项value,该具体的扩展项是其数据结构地址,添加扩展项时,根据输入参数flags可以处理扩展项冲突。flags可以的值定义在x509v3.h中,如下:
#define X509V3_ADD_DEFAULT 0L
#define X509V3_ADD_APPEND 1L
#define X509V3_ADD_REPLACE 2L
#define X509V3_ADD_REPLACE_EXISTING 3L
#define X509V3_ADD_KEEP_EXISTING 4L
#define X509V3_ADD_DELETE 5L
#define X509V3_ADD_SILENT 0x10
由于flags值的不同,本函数的操作可以有如下情况:
a)扩展项堆栈中没有nid对应的扩展项,此时如果flags为X509V3_ADD_REPLACE_EXISTING或X509V3_ADD_DELETE 则报错:无此扩展项;
b) 扩展项堆栈中有nid对应的扩展项,如果flags为X509V3_ADD_KEEP_EXISTING,成功返回;如果flags是X509V3_ADD_DEFAULT 报错,表明此扩展项已经存在;如果flags是X509V3_ADD_DELETE,则删除这个扩展项;如果flags是 X509V3_ADD_REPLACE_EXISTING,则替换此扩展项。
编程示例1:
调用函数X509_EXTENSION_create_by_NID和X509_EXTENSION_create_by_OBJ生成扩展项,并调用X509_EXTENSION_get_object、X509_EXTENSION_get_data和X509_EXTENSION_get_critical获取扩展项信息; 调用X509_EXTENSION_set_object、X509_EXTENSION_set_critical和X509_EXTENSION_set_data设置扩展项信息。这种构造扩展项的方法是比较烦琐的方法。
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/objects.h>
int main()
{
X509_EXTENSION *ext=NULL; /* 必须=NULL */
ASN1_OCTET_STRING *data,*data2;
time_t t;
PKEY_USAGE_PERIOD *period,*period2;
int len,ret,buflen=100;
unsigned char *p,*der,*der2;
ASN1_OBJECT *obj=NULL;
char buf[100];
BIO *b;
/* 构造内部数据结构 */
period=PKEY_USAGE_PERIOD_new();
t=1;
/* 从时间1970年1月1日0点0分0秒往后算时间,t=1表示1秒*/
period->notBefore=ASN1_GENERALIZEDTIME_set(period->notBefore,t);
t=100;
period->notAfter=ASN1_GENERALIZEDTIME_set(period->notAfter,t);
/* der编码 */
len=i2d_PKEY_USAGE_PERIOD(period,NULL);
der=(unsigned char *)malloc(len);
p=der;
len=i2d_PKEY_USAGE_PERIOD(period,&p);
data=ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(data,der,len);
#if 1
X509_EXTENSION_create_by_NID(&ext,NID_private_key_usage_period,1,data);
#else
obj=OBJ_nid2obj(NID_private_key_usage_period);
X509_EXTENSION_create_by_OBJ(&ext,obj,1,data);
#endif
/* get 函数*/
obj=X509_EXTENSION_get_object(ext);
OBJ_obj2txt(buf,buflen,obj,0);
printf("extions obj : %s\n",buf);
data=X509_EXTENSION_get_data(ext);
b=BIO_new(BIO_s_file());
BIO_set_fp(b,stdout,BIO_NOCLOSE);
ASN1_STRING_print(b,data);
ret=X509_EXTENSION_get_critical(ext);
if(ret==1)
{
printf("关键扩展项\n");
}
else
{
printf("非关键扩展项\n");
}
/* set 函数 */
ret=X509_EXTENSION_set_object(ext,obj);
if(ret!=1)
{
printf("X509_EXTENSION_set_object err\n");
}
ret=X509_EXTENSION_set_critical(ext,0); /* 设置为非关键扩展 */
if(ret!=1)
{
printf("X509_EXTENSION_set_critical err\n");
}
period2=PKEY_USAGE_PERIOD_new();
t=(2006-1970)*365*24*3600;
period2->notBefore=ASN1_GENERALIZEDTIME_set(period2->notBefore,t);
t=t+10*365*24*3600;
period2->notAfter=ASN1_GENERALIZEDTIME_set(period2->notAfter,t);
/* der编码 */
len=i2d_PKEY_USAGE_PERIOD(period2,NULL);
der2=(unsigned char *)malloc(len);
p=der2;
len=i2d_PKEY_USAGE_PERIOD(period2,&p);
data2=ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(data2,der2,len);
ret=X509_EXTENSION_set_data(ext,data2); /* 设置新的时间段 */
if(ret!=1)
{
printf("X509_EXTENSION_set_data err\n");
}
PKEY_USAGE_PERIOD_free(period);
PKEY_USAGE_PERIOD_free(period2);
free(der);
free(der2);
ASN1_OCTET_STRING_free(data);
ASN1_OCTET_STRING_free(data2);
X509_EXTENSION_free(ext);
return 0;
}
编程示例2:
通过X509V3_EXT_METHOD来构造扩展项,简单。
#include <openssl/x509v3.h>
int main()
{
X509_EXTENSION *ext=NULL;
STACK_OF(X509_EXTENSION) *exts=NULL;
time_t t;
PKEY_USAGE_PERIOD *period;
int ret;
/* 构造内部数据结构 */
period=PKEY_USAGE_PERIOD_new();
t=1;
/* 从时间1970年1月1日0点0分0秒往后算时间,t=1表示1秒*/
period->notBefore=ASN1_GENERALIZEDTIME_set(period->notBefore,t);
t=100;
period->notAfter=ASN1_GENERALIZEDTIME_set(period->notAfter,t);
/* 根据具体的扩展项构造一个X509_EXTENSION */
ext=X509V3_EXT_i2d(NID_private_key_usage_period,1, period);
/* 根据具体的扩展项构造一个X509_EXTENSION堆栈*/
ret=X509V3_add1_i2d(&exts,NID_private_key_usage_period, period,1,X509V3_ADD_DEFAULT);
X509_EXTENSION_free(ext);
sk_X509_EXTENSION_pop_free(exts,X509_EXTENSION_free);
return 0;
}
24.8 X509_ATTRIBUTE
该数据结构用来存放属性信息,定义在crypto/x509/x509.h中,如下:
typedef struct x509_attributes_st
{
ASN1_OBJECT *object;
int single;
union
{
char *ptr;
STACK_OF(ASN1_TYPE) *set;
ASN1_TYPE *single;
} value;
} X509_ATTRIBUTE;
X509_ATTRIBUTE可以存放任意类型的数据,object指明了属性的类型,single用于表示value的类型是ASN1_TYPE(为0)还是ASN1_TYPE堆栈(为1)。ASN1_TYPE可以存放任意ASN1类型数据。
该结构的DER编解码在crypto/asn1/x_attrib.c中由宏实现,实现了new、free、i2d、d2i和dup函数。
主要函数:
1) i nt X509_ATTRIBUTE_count(X509_ATTRIBUTE *attr)
获取属性中ASN1_TYPE的个数。
2)X509_ATTRIBUTE *X509_ATTRIBUTE_create(int nid, int atrtype, void *value)
生成一个属性。id用来生成ASN1_OBJECT,指明是哪种属性,atrtype为ASN1类型,value为值,用来设置set堆栈。
3)X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_OBJ(X509_ATTRIBUTE **attr,
const ASN1_OBJECT *obj, int atrtype, const void *data, int len)
生成一个属性,obj指明了属性类型,atrtype为ASN1类型,data和len指明了需要设置的值。
4)X509_ATTRIBUTE_create_by_NID
同上,属性类型由nid指定。
5)X509_ATTRIBUTE_create_by_txt
同上,属性类型由ASN1_OBJECT的名字指定。
6)X509_ATTRIBUTE_dup
拷贝函数。
7)ASN1_TYPE *X509_ATTRIBUTE_get0_type(X509_ATTRIBUTE *attr, int idx)
获取属性中由堆栈位置idx指定的ASN1_TYPE,如果属性不是set集合则返回value. single。
8)void *X509_ATTRIBUTE_get0_data(X509_ATTRIBUTE *attr, int idx,
int atrtype, void *data)
由idx和ASN1类型atrtype来获取value. ptr。
9)X509_ATTRIBUTE_get0_object
获取属性类型信息。
10) X509_ATTRIBUTE_set1_data
设置属性信息。
11) X509_ATTRIBUTE_set1_object
设置属性类别。
12)STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr(
STACK_OF(X509_ATTRIBUTE) **x, X509_ATTRIBUTE *attr)
性堆栈中添加一个属性,返回属性堆栈;如果*x为NULL,则生成一个新的堆栈。
13) STACK_OF(X509_ATTRIBUTE) 509at_add1_attr_by_NID(
STACK_OF(X509_ATTRIBUTE) **x,int nid,
int type,const unsigned char *bytes, int len)
往属性堆栈中添加一个属性,添加属性的类型由nid指定,bytes为属性值,len为其长度,type指明了bytes的ASN1类型。
14)X509at_add1_attr_by_OBJ
同上,属性类型由ASN1_OBJECT指定。
15)X509at_add1_attr_by_txt
同上,属性类型由属性名指定。
24.9 GENERAL_NAME
本结构用来表示通用名称,它定义在crypto/x509v3/x509v3.h中,如下:
typedef struct GENERAL_NAME_st
{
#define GEN_OTHERNAME 0
#define GEN_EMAIL 1
#define GEN_DNS 2
#define GEN_X400 3
#define GEN_DIRNAME 4
#define GEN_EDIPARTY 5
#define GEN_URI 6
#define GEN_IPADD 7
#define GEN_RID 8
int type;
union {
char *ptr;
OTHERNAME *otherName; /* 其他名称 */
ASN1_IA5STRING *rfc822Name; /* 符合rfc822标准的名称 */
ASN1_IA5STRING *dNSName; /* DNS名称 */
ASN1_TYPE *x400Address;
X509_NAME *directoryName; /* 目录名 */
EDIPARTYNAME *ediPartyName;
ASN1_IA5STRING *uniformResourceIdentifier; /* URI名称 */
ASN1_OCTET_STRING *iPAddress; /* IP地址名称 */
ASN1_OBJECT *registeredID;
/* Old names */
ASN1_OCTET_STRING *ip;
X509_NAME *dirn;
ASN1_IA5STRING *ia5;
ASN1_OBJECT *rid; /* registeredID */
ASN1_TYPE *other; /* x400Address */
} d;
} GENERAL_NAME;
typedef STACK_OF(GENERAL_NAME) GENERAL_NAMES;
GENERAL_NAMES为GENERAL_NAME堆栈。
GENERAL_NAME可以包含各种各样的名称。type用来表示该结构采用了什么样的名称,从0~8分别对应otherName、rfc822Name、dNSName、x400Address、directoryName、ediPartyName、uniformResourceIdentifier、iPAddress和registeredID。ptr用来存放通用的各种名称的地址。当type为某一类型时,数据存放在d中对应的项。比如,当type为GEN_DIRNAME时,数据存放在directoryName中。openssl中最常用的是X509_NAME。
上述两种结构的DER编码在crypto/x509v3/v3_genn.c中由宏实现,实现了new、free、i2d和d2i四个函数。
编程示例:
#include <openssl/x509v3.h>
int main()
{
GENERAL_NAME *gn;
GENERAL_NAMES *gns;
char *buf,*p;
int len;
FILE *fp;
gns=sk_GENERAL_NAME_new_null();
/* new 函数 */
gn=GENERAL_NAME_new();
/* 设置gn的值为一个rfc822Name */
gn->type=GEN_EMAIL;
gn->d.rfc822Name=ASN1_STRING_new();
ASN1_STRING_set(gn->d.rfc822Name,"forxy@126.com",13);
len=i2d_GENERAL_NAME(gn,NULL);
p=buf=malloc(len);
len=i2d_GENERAL_NAME(gn,&p);
/* 生成的gn1.cer并不是一个完整的DER编码文件,不能用ASN1view工具查看 */
fp=fopen("gn1.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
free(buf);
sk_GENERAL_NAME_push(gns,gn);
/* new 函数 */
gn=GENERAL_NAME_new();
/* 设置gn的值为一个GEN_DIRNAME */
gn->type=GEN_DIRNAME;
gn->d.directoryName=X509_NAME_new();
X509_NAME_add_entry_by_txt(gn->d.directoryName,LN_commonName,V_ASN1_UTF8STRING,"forxy",5,0,-1);
/* i2d 函数 */
len=i2d_GENERAL_NAME(gn,NULL);
p=buf=malloc(len);
len=i2d_GENERAL_NAME(gn,&p);
/* 生成的gn2.cer并不是一个完整的DER编码文件,不能用ASN1view工具查看 */
fp=fopen("gn2.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
free(buf);
sk_GENERAL_NAME_push(gns,gn);
/* GENERAL_NAMES 的i2d函数 */
len=i2d_GENERAL_NAMES(gns,NULL);
p=buf=malloc(len);
len=i2d_GENERAL_NAMES(gns,&p);
/* 生成完整的DER编码文件 */
fp=fopen("gns.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
free(buf);
sk_GENERAL_NAME_pop_free(gns,GENERAL_NAME_free);
return 0;
}
第二十五章 证书申请
25.1 证书申请介绍
生成X509数字证书前,一般先由用户提交证书申请文件,然后由CA来签发证书。大致过程如下:
1) 用户生成自己的公私钥对;
2) 构造自己的证书申请文件,符合PKCS#10标准。该文件主要包括了用户信息、公钥以及一些可选的属性信息,并用自己的私钥给该内容签名;
3) 用户将证书申请文件提交给CA;
4) CA验证签名,提取用户信息,并加上其他信息(比如颁发者等信息),用CA的私钥签发数字证书;
X509证书申请的格式标准为pkcs#10和rfc2314。
25.2 数据结构
根据PKCS#10,openssl的X509数字证书申请结构定义在crypto/x509.h中,如下所示,主要由两部分组成:
1)X509_REQ_INFO
typedef struct X509_req_info_st
{
ASN1_ENCODING enc;
ASN1_INTEGER *version;
X509_NAME *subject;
X509_PUBKEY *pubkey;
STACK_OF(X509_ATTRIBUTE) *attributes;
} X509_REQ_INFO;
该结构为证书申请信息主体,其中version表示版本,subject为申请者信息,pubkey为申请者公钥信息,attributes为可选的属性信息。该结构的DER编码接口在crytpo/asn1/x_req.c中由宏实现,实现了new、free、i2d和d2i函数。
2)X509_REQ
typedef struct X509_req_st
{
X509_REQ_INFO *req_info;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
int references;
} X509_REQ;
该结构为证书申请信息,req_info为信息主体,sig_alg为签名算法,signature为签名值(申请者对req_info的DER编码值用自己的私钥签名)。该结构的DER编码接口在crytpo/asn1/x_req.c中由宏实现,实现了new、free、i2d和d2i函数。
25.3 主要函数
1) int X509_REQ_add1_attr(X509_REQ *req, X509_ATTRIBUTE *attr)
添加一个属性到req的属性堆栈中。
2) int X509_REQ_add1_attr_by_NID(X509_REQ *req,int nid,
int type,const unsigned char *bytes, int len)
添加一个属性到req的属性堆栈中,nid指明了属性类型,bytes为属性值,len为其长度,type为属性值的ASN1类型。
3) X509_REQ_add1_attr_by_OBJ
同上,属性类型由ASN1_OBJECT指定。
4) X509_REQ_add1_attr_by_txt
同上,属性类型由属性名指定。
5) int X509_REQ_add_extensions_nid(
X509_REQ *req, STACK_OF(X509_EXTENSION) *exts,int nid)
添加一个属性到req的属性堆栈中,将exts扩展项集合作为一个属性加入,nid指明了加入的是哪种属性;该函数将X509_EXTENSION堆栈DER编码,编码后的值作为属性值。
6) X509_REQ_add_extensions
调用了5),只是nid指定为NID_ext_req。
7) X509_REQ_delete_attr
从属性堆栈中删除指定位置的属性。
8) X509_REQ_digest
根据指定的摘要算法,对X509_REQ结构做摘要计算。
9) X509_REQ_dup
拷贝函数,返回一个X509_REQ,返回的X509_REQ需要调用X509_REQ_free释放空间。
10)int X509_REQ_extension_nid(int req_nid)
判断req_nid是否为NID_ext_req、NID_ms_ext_req或其他由用户设置的NID,如果是返回1,否则返回0。
11)STACK_OF(X509_EXTENSION) *X509_REQ_get_extensions(X509_REQ *req)
获取X509_REQ中的属性信息,并将属性信息转换为X509_EXTENSION堆栈。该函数从X509_REQ的属性堆栈中查找包含合法的nid类型的属性(见X509_REQ_get_extension_nids函数说明),如果找到一个,则将属性值通过DER解码转换为扩展项堆栈。
12)X509_REQ_get1_email
获取证书申请中申请者的邮件地址信息,信息来自X509_NAME *subject和STACK_OF(X509_ATTRIBUTE) *attributes,返回一个堆栈。
13)X509_REQ_get_attr
根据指定位置,获取属性堆栈中的一个属性。
14)int X509_REQ_get_attr_by_NID(const X509_REQ *req, int nid, int lastpos)
根据属性nid,从req的属性堆栈中查找对应属性,并返回。查找堆栈时,从lastpos位置开始查找。
15)X509_REQ_get_attr_by_OBJ
同上,根据ASN1_OBJECT来查找属性。
16)X509_REQ_get_attr_count
属性堆栈中属性的个数。
17)X509_REQ_get_extension_nids/ X509_REQ_set_extension_nids
获取证书申请合法扩展项列表,默认情况下,该列表在x509/x509_req.c中定义如下:
static int ext_nid_list[] = { NID_ext_req, NID_ms_ext_req, NID_undef};
static int *ext_nids = ext_nid_list;
本函数返回ext_nids;
通过X509_REQ_set_extension_nids函数,用户可用定义自己的证书申请扩展项列,表,该函数的输入参数是一个nid列表。调用X509_REQ_set_extension_nids时,将ext_nids修改为用户输入参数,不再是默认的ext_nid_list。
18)X509_REQ_get_pubkey
获取公钥。
19)X509_REQ_print
将证书申请信息输出到BIO中。
20)int X509_REQ_print_ex(BIO *bp, X509_REQ *x,
unsigned long nmflags, unsigned long cflag)
将证书申请信息输出到BIO中,输出的内容通过cflag进行过滤,其值定义在x509.h中,如下:
#define X509_FLAG_NO_HEADER 1L
#define X509_FLAG_NO_VERSION (1L << 1)
#define X509_FLAG_NO_SERIAL (1L << 2)
#define X509_FLAG_NO_SIGNAME (1L << 3)
#define X509_FLAG_NO_ISSUER (1L << 4)
#define X509_FLAG_NO_VALIDITY (1L << 5)
#define X509_FLAG_NO_SUBJECT (1L << 6)
#define X509_FLAG_NO_PUBKEY (1L << 7)
#define X509_FLAG_NO_EXTENSIONS (1L << 8)
#define X509_FLAG_NO_SIGDUMP (1L << 9)
#define X509_FLAG_NO_AUX (1L << 10)
#define X509_FLAG_NO_ATTRIBUTES (1L << 11)
21)X509_REQ_print_fp
将证书申请消息输出到FILE中。
22)X509_REQ *X509_to_X509_REQ(X509 *x, EVP_PKEY *pkey, const EVP_MD *md)
根据证书信息,申请者私钥以及摘要算法生成证书请求。x为数字证书,pkey为申请人的私钥信息,md为摘要算法,pkey和md用于给证书申请签名。
23)X509 *X509_REQ_to_X509(X509_REQ *r, int days, EVP_PKEY *pkey)
根据X509_REQ生成一个数字证书并返回,days指明其失效期,pkey为外送私钥,用于签名,返回数字证书。此函数无多大用处,由于没有指明颁发者,生成的数字证书颁发者就是X509_REQ中的申请人,并且证书的摘要固定用的是md5算法,另外,没有处理证书扩展项。
24)int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey)
设置证书请求的公钥。
25)int X509_REQ_set_subject_name(X509_REQ *x, X509_NAME *name)
设置证书请求的者的名称,此函数调用X509_NAME_set函数来实现。
26)int X509_REQ_set_version(X509_REQ *x, long version)
设置证书请求信息的版本,此函数调用ASN1_INTEGER_set函数来完成。
25.4 编程示例
25.4.1生成证书请求文件
#include <string.h>
#include <openssl/x509.h>
#include <openssl/rsa.h>
int main()
{
X509_REQ *req;
int ret;
long version;
X509_NAME *name;
EVP_PKEY *pkey;
RSA *rsa;
X509_NAME_ENTRY *entry=NULL;
char bytes[100],mdout[20];
int len,mdlen;
int bits=512;
unsigned long e=RSA_3;
unsigned char *der,*p;
FILE *fp;
const EVP_MD *md;
X509 *x509;
BIO *b;
STACK_OF(X509_EXTENSION) *exts;
req=X509_REQ_new();
version=1;
ret=X509_REQ_set_version(req,version);
name=X509_NAME_new();
strcpy(bytes,"openssl");
len=strlen(bytes);
entry=X509_NAME_ENTRY_create_by_txt(&entry,"commonName",V_ASN1_UTF8STRING,(unsigned char *)bytes,len);
X509_NAME_add_entry(name,entry,0,-1);
strcpy(bytes,"bj");
len=strlen(bytes);
entry=X509_NAME_ENTRY_create_by_txt(&entry,"countryName",V_ASN1_UTF8STRING,bytes,len);
X509_NAME_add_entry(name,entry,1,-1);
/* subject name */
ret=X509_REQ_set_subject_name(req,name);
/* pub key */
pkey=EVP_PKEY_new();
rsa=RSA_generate_key(bits,e,NULL,NULL);
EVP_PKEY_assign_RSA(pkey,rsa);
ret=X509_REQ_set_pubkey(req,pkey);
/* attribute */
strcpy(bytes,"test");
len=strlen(bytes);
ret=X509_REQ_add1_attr_by_txt(req,"organizationName",V_ASN1_UTF8STRING,bytes,len);
strcpy(bytes,"ttt");
len=strlen(bytes);
ret=X509_REQ_add1_attr_by_txt(req,"organizationalUnitName",V_ASN1_UTF8STRING,bytes,len);
md=EVP_sha1();
ret=X509_REQ_digest(req,md,mdout,&mdlen);
ret=X509_REQ_sign(req,pkey,md);
if(!ret)
{
printf("sign err!\n");
X509_REQ_free(req);
return -1;
}
/* 写入文件PEM格式 */
b=BIO_new_file("certreq.txt","w");
PEM_write_bio_X509_REQ(b,req,NULL,NULL);
BIO_free(b);
/* DER编码 */
len=i2d_X509_REQ(req,NULL);
der=malloc(len);
p=der;
len=i2d_X509_REQ(req,&p);
OpenSSL_add_all_algorithms();
ret=X509_REQ_verify(req,pkey);
if(ret<0)
{
printf("verify err.\n");
}
fp=fopen("certreq2.txt","wb");
fwrite(der,1,len,fp);
fclose(fp);
free(der);
X509_REQ_free(req);
return 0;
}
本例用于生成一个证书请求文件,并测试了X509_REQ_verify和X509_REQ_digest等函数。
25.4.2 解码证书请求文件
#include <openssl/pem.h>
int main()
{
BIO *in;
X509_REQ *req=NULL,**req2=NULL;
FILE *fp;
unsigned char buf[1024],*p;
int len;
in=BIO_new_file("certreq.txt","r");
req=PEM_read_bio_X509_REQ(in,NULL,NULL,NULL);
if(req==NULL)
{
printf("DER解码错误!\n");
}
else
{
printf("DER解码成功!\n");
}
fp=fopen("certreq2.txt","r");
len=fread(buf,1,1024,fp);
fclose(fp);
p=buf;
req2=(X509_REQ **)malloc(sizeof(X509_REQ *));
d2i_X509_REQ(req2,&p,len);
if(*req2==NULL)
{
printf("DER解码错误!\n");
}
else
{
printf("DER解码成功!\n");
}
X509_REQ_free(*req2);
free(req2);
return 0;
}
其中certreq.txt是PEM格式的证书请求文件,certreq2.txt为DER编码格式。
第二十六章 X509数字证书
26.1 X509数字证书
数字证书是将用户(或其他实体)身份与公钥绑定的信息载体。一个合法的数字证书不仅要符合X509格式规范,还必须有CA的签名。用户不仅有自己的数字证书,还必须有对应的私钥。
X509v3数字证书主要包含的内容有[1]:证书版本、证书序列号、签名算法、颁发者信息、有效时间、持有者信息、公钥信息、颁发者ID、持有者ID和扩展项。
26.2 opessl实现
openssl实现了标准的x509v3数字证书,其源码在crypto/x509和crypto/x509v3中。其中x509目录实现了数字证书以及证书申请相关的各种函数,包括了X509和X509_REQ结构的设置、读取、打印和比较;数字证书的验证、摘要;各种公钥的导入导出等功能。x509v3目录主要实现了数字证书扩展项相关的函数。
26.3 X509数据结构
该结构定义在crypto/x509.h中,如下:
typedef struct x509_cinf_st
{
ASN1_INTEGER *version; /* 版本 */
ASN1_INTEGER *serialNumber; /* 序列号 */
X509_ALGOR *signature; /* 签名算法 */
X509_NAME *issuer; /* 颁发者 */
X509_VAL *validity; /* 有效时间 */
X509_NAME *subject; /* 持有者 */
X509_PUBKEY *key; /* 公钥 */
ASN1_BIT_STRING *issuerUID; /* 颁发者唯一标识 */
ASN1_BIT_STRING *subjectUID; /* 持有者唯一标识 */
STACK_OF(X509_EXTENSION) *extensions; /* 扩展项 */
} X509_CINF;
本结构是数字证书的信息主体;
struct x509_st
{
X509_CINF *cert_info;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
int valid;
int references;
char *name;
CRYPTO_EX_DATA ex_data;
long ex_pathlen;
long ex_pcpathlen;
unsigned long ex_flags;
unsigned long ex_kusage;
unsigned long ex_xkusage;
unsigned long ex_nscert;
ASN1_OCTET_STRING *skid;
struct AUTHORITY_KEYID_st *akid;
X509_POLICY_CACHE *policy_cache;
#ifndef OPENSSL_NO_SHA
unsigned char sha1_hash[SHA_DIGEST_LENGTH];
#endif
X509_CERT_AUX *aux;
};
该结构表示了一个完整的数字证书。各项意义如下:
cert_info:证书主体信息;
sig_alg:签名算法;
signature:签名值,存放CA对该证书采用sig_alg算法签名的结果;
valid:是否是合法证书,1为合法,0为未知;
references:引用次数,被引用一次则加一;
name:证书持有者信息,内容形式为/C=CN/O=ourinfo……,该内容在调用d2i_X509的过程中,通过回调函数x509_cb(crypto/asn1/x_x509.c)调用X509_NAME_oneline来设置;
ex_data:扩展数据结构,用于存放用户自定义的信息;
扩展项信息,用于证书验证。下面的扩展项信息由crypto/x509v3/v3_purp.c中的x509v3_cache_extensions函数设置:
ex_pathlen:证书路径长度,对应扩展项为NID_basic_constraints;
ex_flags:通过“与”计算存放各种标记;
ex_kusage:密钥用法,对应扩展项为NID_key_usage;
ex_xkusage:扩展密钥用法,对应扩展项为NID_ext_key_usage;
ex_nscert:Netscape证书类型,对应扩展项为NID_netscape_cert_type;
skid:主体密钥标识,对应扩展项为NID_subject_key_identifier;
akid:颁发者密钥标识,对应扩展项为NID_authority_key_identifier;
policy_cache:各种策略缓存,crypto/x509v3/pcy_cache.c中由函数policy_cache_create设置,对应的策略为NID_policy_constraints、NID_certificate_policies、NID_policy_mappings和NID_inhibit_any_policy(见policy_cache_new和policy_cache_set函数);
sha1_hash:存放证书的sha1摘要值;
aux:辅助信息;
上述两个结构的DER编解码接口由宏在crypto/asn1/x_x509.c中实现,包括各自的new、free、i2d和d2i函数。
DER解码编程示例如下:
#include <openssl/x509.h>
int main()
{
X509 *x;
FILE *fp;
unsigned char buf[5000],*p;
int len,ret;
BIO *b;
/* cert.cer为DER编码的数字证书
用户如果是windows系统,可以从IE中导出一个x509v3的数字证书作为解析目标
*/
fp=fopen("cert.cer","rb");
if(!fp) return -1;
len=fread(buf,1,5000,fp);
fclose(fp);
p=buf;
x=X509_new();
d2i_X509(&x,(const unsigned char **)&p,len);
b=BIO_new(BIO_s_file());
BIO_set_fp(b,stdout,BIO_NOCLOSE);
ret=X509_print(b,x);
BIO_free(b);
X509_free(x);
return 0;
}
程序输出:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
06:37:6c:00:aa:00:64:8a:11:cf:b8:d4:aa:5c:35:f4
Signature Algorithm: md5WithRSAEncryption
Issuer: CN=Root Agency
Validity
Not Before: May 28 22:02:59 1996 GMT
Not After : Dec 31 23:59:59 2039 GMT
Subject: CN=Root Agency
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (512 bit)
Modulus (512 bit):
00:81:55:22:b9:8a:a4:6f:ed:d6:e7:d9:66:0f:55:
bc:d7:cd:d5:bc:4e:40:02:21:a2:b1:f7:87:30:85:
5e:d2:f2:44:b9:dc:9b:75:b6:fb:46:5f:42:b6:9d:
23:36:0b:de:54:0f:cd:bd:1f:99:2a:10:58:11:cb:
40:cb:b5:a7:41
Exponent: 65537 (0x10001)
X509v3 extensions:
commonName:
.GFor Testing Purposes Only Sample Software Publishing Credentials Agency
2.5.29.1:
0>.....-...O..a!..dc..0.1.0...U....Root Agency...7l...d......\5.
Signature Algorithm: md5WithRSAEncryption
2d:2e:3e:7b:89:42:89:3f:a8:21:17:fa:f0:f5:c3:95:db:62:
69:5b:c9:dc:c1:b3:fa:f0:c4:6f:6f:64:9a:bd:e7:1b:25:68:
72:83:67:bd:56:b0:8d:01:bd:2a:f7:cc:4b:bd:87:a5:ba:87:
20:4c:42:11:41:ad:10:17:3b:8c
上述示例解码DER编码的数字证书,X509_print用于打印数字证书信息。
如果需要解码PEM格式的证书,如下例:
#include <openssl/x509.h>
#include <openssl/pem.h>
int main()
{
X509 *x;
BIO *b;
/* cert.cer为PEM格式的数字证书 */
b=BIO_new_file("b64cert.cer","r");
x=PEM_read_bio_X509(b,NULL,NULL,NULL);
BIO_free(b);
X509_free(x);
return 0;
}
上例得到X509数据结构。
26.4 X509_TRUST与X509_CERT_AUX
1) X509_TRUST
该结构定义在crypto/x509v3/x509v3.h中,如下:
typedef struct x509_trust_st
{
int trust;
int flags;
int (*check_trust)(struct x509_trust_st *, X509 *, int);
char *name;
int arg1;
void *arg2;
} X509_TRUST;
信任检查数据结构,本结构用来检查数字证书是否是受信任的,其主要的函数实现在x509/x509_trs.c中。其主要项为回调函数check_trust,该函数用于判断证书是受信任的。
Openssl在x509_trs.c中维护了两个表,标准表和扩展表,用于判断特定NID的信任情况。如下:
标准表:
static X509_TRUST trstandard[] = {
{X509_TRUST_COMPAT, 0, trust_compat, "compatible", 0, NULL},
{X509_TRUST_SSL_CLIENT, 0, trust_1oidany, "SSL Client", NID_client_auth, NULL},
{X509_TRUST_SSL_SERVER, 0, trust_1oidany, "SSL Server", NID_server_auth, NULL},
{X509_TRUST_EMAIL, 0, trust_1oidany, "S/MIME email", NID_email_protect, NULL},
{X509_TRUST_OBJECT_SIGN, 0, trust_1oidany, "Object Signer", NID_code_sign, NULL},
{X509_TRUST_OCSP_SIGN, 0, trust_1oid, "OCSP responder", NID_OCSP_sign, NULL},
{X509_TRUST_OCSP_REQUEST, 0, trust_1oid, "OCSP request", NID_ad_OCSP, NULL}
};
扩展表:
static STACK_OF(X509_TRUST) *trtable = NULL;
扩展表通过X509_TRUST_add函数来添加。当用户需要对某个NID做判断时,查找这两个表,然后通过check_trust得到结果。
2)X509_CERT_AUX
该结构定义在x509.h中,如下:
typedef struct x509_cert_aux_st
{
STACK_OF(ASN1_OBJECT) *trust;
STACK_OF(ASN1_OBJECT) *reject;
ASN1_UTF8STRING *alias;
ASN1_OCTET_STRING *keyid;
STACK_OF(X509_ALGOR) *other;
} X509_CERT_AUX;
该结构是X509的一项,用于决定一个证书是否受信任。trust堆栈中存放了受信任的ASN1_OBJECT,reject堆栈中存放了应该拒绝的ASN1_OBJECT。trust堆栈通过X509_add1_trust_object函数来存放一个可信的ASN1_OBJECT,reject堆栈通过X509_add1_reject_object来存放一个应该拒绝的ASN1_OBJECT。这两个堆栈在x509/x509_trs.c的obj_trust函数中使用。obj_trust函数是默认的check_trust函数。
上述两个结构在证书验证中的作用如下:
- 在X509结构中构造X509_CERT_AUX;
- 调用X509_add1_trust_object和X509_add1_reject_object,将受信任和要拒绝的ASN1_OBJECT添加到X509_CERT_AUX的两个堆栈中;
- 验证证书时,如果要验证某个ASN1_OBJECT是否受信任,查表找到相应的check_trust,进行计算。如果对应的项在标准表trstandard中,除了X509_TRUST_COMPAT(检查证书用途)都会调用obj_trust函数。
26.5 X509_PURPOSE
该结构用于检查证书用途,它定义在x509v3.h中,如下:
typedef struct x509_purpose_st
{
int purpose;
int trust;
int flags;
int (*check_purpose)(const struct x509_purpose_st *,const X509 *, int);
char *name;
char *sname;
void *usr_data;
} X509_PURPOSE;
purpose为证书用途ID,check_purpose为检查证书用途函数。基本的用途ID在x509v3.h中定义,如下:
#define X509_PURPOSE_SSL_CLIENT 1
#define X509_PURPOSE_SSL_SERVER 2
#define X509_PURPOSE_NS_SSL_SERVER 3
#define X509_PURPOSE_SMIME_SIGN 4
#define X509_PURPOSE_SMIME_ENCRYPT 5
#define X509_PURPOSE_CRL_SIGN 6
#define X509_PURPOSE_ANY 7
#define X509_PURPOSE_OCSP_HELPER 8
Openssl在x509v3/v3_purp.c中维护了两个表,用来检查各种证书用途。如下:
标准表:
static X509_PURPOSE xstandard[] = {
{X509_PURPOSE_SSL_CLIENT, X509_TRUST_SSL_CLIENT, 0, check_purpose_ssl_client, "SSL client", "sslclient", NULL},
{X509_PURPOSE_SSL_SERVER, X509_TRUST_SSL_SERVER, 0, check_purpose_ssl_server, "SSL server", "sslserver", NULL},
{X509_PURPOSE_NS_SSL_SERVER, X509_TRUST_SSL_SERVER, 0, check_purpose_ns_ssl_server, "Netscape SSL server", "nssslserver", NULL},
{X509_PURPOSE_SMIME_SIGN, X509_TRUST_EMAIL, 0, check_purpose_smime_sign, "S/MIME signing", "smimesign", NULL},
{X509_PURPOSE_SMIME_ENCRYPT, X509_TRUST_EMAIL, 0, check_purpose_smime_encrypt, "S/MIME encryption", "smimeencrypt", NULL},
{X509_PURPOSE_CRL_SIGN, X509_TRUST_COMPAT, 0, check_purpose_crl_sign, "CRL signing", "crlsign", NULL},
{X509_PURPOSE_ANY, X509_TRUST_DEFAULT, 0, no_check, "Any Purpose", "any", NULL},
{X509_PURPOSE_OCSP_HELPER, X509_TRUST_COMPAT, 0, ocsp_helper, "OCSP helper", "ocsphelper", NULL},
};
扩展表:
static STACK_OF(X509_PURPOSE) *xptable = NULL;
扩展表由用户通过X509_PURPOSE_add函数来添加。
当用户需要检查某个证书用途时,先查表,找到对应的X509_PURPOSE,然后调用其check_purpose函数来判断证书用途是否合法。
检查证书用途的函数为int X509_check_purpose(X509 *x, int id, int ca),该函数用于检查证书的用途。x为待检查待证书,id为证书用途NID,ca表明x是否是ca证书。
基本用法如下:
#include <openssl/x509.h>
#include <openssl/x509v3.h>
int main()
{
X509 *x=0;
int id,len,ret;
FILE *fp;
unsigned char buf[5000],*p;
fp=fopen("root.cer","rb");
len=fread(buf,1,5000,fp);
fclose(fp);
p=buf;
d2i_X509(&x,&p,len);
id=X509_PURPOSE_OCSP_HELPER;
ret=X509_check_purpose(x,id,0);
if(ret==1)
{
printf("purpose check ok!\n");
}
else
{
printf("purpose check failed!\n");
}
X509_free(x);
return 0;
}
如果输入的id小于0,不做任何检查,只是证书的各个扩展项信息写入到将X509数据结构中。
另外本函数支持其他用途的验证,示例如下:
#include <openssl/x509v3.h>
int cb_check_tsa(X509_PURPOSE *purpose,const X509 *x,int isCA)
{
int flag;
printf("------------my check!-----------------\n");
/* 针对x添加判断函数 */
flag=*((int *)(purpose->usr_data));
if(flag)
return 1; /* 由此功能 */
else
return 0; /* 无此功能 */
}
int main()
{
X509 *x=0;
int id,len,ret;
FILE *fp;
unsigned char buf[5000],*p;
int tsaFlag;
tsaFlag=1;
ret=X509_PURPOSE_add(1000,1000,0,cb_check_tsa,"tsa","checkTsa",&tsaFlag);
fp=fopen("root.cer","rb");
len=fread(buf,1,5000,fp);
fclose(fp);
p=buf;
d2i_X509(&x,&p,len);
id=1000;
ret=X509_check_purpose(x,id,0);
if(ret==1)
{
printf("purpose check ok!\n");
}
else
{
printf("purpose check failed!\n");
}
X509_free(x);
return 0;
}
本程序通过调用函数X509_PURPOSE_add添加一个X509_PURPOSE内部数据结构,然后再验证证书是否有此用途。用户所要实现的为X509_PURPOSE中的回调函数,在此回调函数中,用户根据证书信息来判断证书是否有此用途。如果用户还需要其他的信息才能作出判断,可以另外获取X509_PURPOSE数据结构中的usr_data。usr_data为一个void指针类型。用户可在调用X509_PURPOSE_add函数时将它写入对应的X509_PURPOSE数据结构(上例中的tsaFlag)。
26.6 主要函数
1) X509_STORE_add_cert
将证书添加到X509_STORE中。
2) X509_STORE_add_crl
将crl添加到X509_STORE中。
3) void X509_STORE_set_flags(X509_STORE *ctx, long flags)
将flags赋值给ctx里面的flags,表明了验证证书时需要验证哪些项。
4) X509_TRUST_set_default
设置默认的X509_TRUST检查函数。
5) int X509_verify(X509 *a, EVP_PKEY *r)
验证证书的签名。
6) X509_verify_cert
验证证书,用法可参考apps/verify.c。
7) X509_verify_cert_error_string
根据错误号,获取错误信息。
8) X509_add1_ext_i2d
根据具体的扩展项数据结构添加一个扩展项。
9) X509_add_ext
X509_EXTENSION堆栈中,在指定位置添加一项。
10)X509_ALGOR_dup
算法拷贝。
11)X509_alias_get0/X509_alias_set1
获取/设置别名。
12)X509_asn1_meth
获取X509的ASN1_METHOD,包括new、free、i2d和d2i函数。
13)X509_certificate_type
获取证书和公钥类型。
14)int X509_check_issued(X509 *issuer, X509 *subject);
检查subject证书是否由issuer颁发,如果是则返回X509_V_OK,即0。
15)X509_check_private_key
检查私钥与证书中的公钥是否匹配,匹配返回1。
16)X509_cmp
证书比较。
17) int X509_cmp_current_time(ASN1_TIME *s)
将s与当前时间进行比较,返回值小于0则s早于当前时间,大于0则s晚与当前时间。
18)int X509_cmp_time(ASN1_TIME *ctm, time_t *cmp_time)
如果ctm时间在cmp_time之后,则返回值大于0。
19) X509_delete_ext
删除扩展项堆栈中指定位置的扩展项。
20)X509_digest
根据指定的摘要算法对X509结构做摘要。
20) X509_dup
拷贝函数。
21)X509_find_by_issuer_and_serial
根据颁发者的X509_NAME名称和证书序列号,在X509堆栈中查找对应的证书并返回。
22) X509_find_by_subject
从证书堆栈中根据持有者名字查询证书,并返回。
23)X509_get0_pubkey_bitstr
获取X509结构中的DER编码的公钥信息。
24)X509_load_cert_crl_file
加载证书和crl,用于验证证书。
25)X509_PURPOSE_get0
根据X509_PURPOSE的位置获取对应的X509_PURPOSE。
26)X509_PURPOSE_get0_name
获取X509_PURPOSE的名字。
27)X509_PURPOSE_get0_sname
获取X509_PURPOSE的别名。
28)X509_PURPOSE_get_by_id
根据证书用途ID获取X509_PURPOSE在当前数组(xstandard)或堆栈(xptable)中的位置,如果没有返回-1。
29)X509_PURPOSE_get_by_sname
根据别名获取对应的X509_PURPOSE在数组或堆栈中的位置。
30)X509_PURPOSE_get_count
获取所有的X509_PURPOSE个数,包括标准的和用户动态添加的。
31)X509_PURPOSE_get_id
获取X509_PURPOSE的ID。
32) int X509_PURPOSE_set(int *p, int purpose)
检查是否有purpose标识的X509_PURPOSE,并将purpose值写入p。
33) STACK_OF(X509_EXTENSION) X509v3_add_ext
(STACK_OF(X509_EXTENSION) **x, X509_EXTENSION *ex, int loc)
添加扩展项,堆栈操作,将ex表示的扩展项根据loc指定的位置插入到X509_EXTENSION堆栈中。
34) X509v3_delete_ext
堆栈操作,去除指定位置的扩展项。
35)int X509V3_EXT_print(BIO *out, X509_EXTENSION *ext,
unsigned long flag, int indent)
本函数用于打印单个扩展项,out为BIO类型的输出对象,ext为扩展项,flag表明不支持扩展项的处理方式,indent表明输出时第一列的位置。
flag的值在x509v3.h中定义,可以有:
- #define X509V3_EXT_DEFAULT 0
打印DER编码内容,调用M_ASN1_OCTET_STRING_print。
- #define X509V3_EXT_ERROR_UNKNOWN (1L << 16)
打印一行语句。
- #define X509V3_EXT_PARSE_UNKNOWN (2L << 16)
分析扩展项的DER编码,并打印。
- #define X509V3_EXT_DUMP_UNKNOWN (3L << 16)
打印出DER编码的内容,调用BIO_dump_indent。
36)int X509V3_extensions_print(BIO *bp, char *title,
STACK_OF(X509_EXTENSION) *exts, unsigned long flag, int indent)
本函数将堆栈中的所有扩展项打印,参数意义同上。
37) int X509v3_get_ext_by_critical(const STACK_OF(X509_EXTENSION) *sk, int crit, int lastpos)
获取扩展项在堆栈中的位置,crit表面扩展项是否关键,lastpos为指定堆栈搜索起始位置。此函数从给定的lastpos开始搜索扩展项堆栈,找到与crit匹配的扩展项后,返回其位置,如果找不到扩展项,返回-1。
38)int X509v3_get_ext_by_NID(const STACK_OF(X509_EXTENSION) *x, int nid,
int lastpos)
获取扩展项在其堆栈中的位置,此函数根据扩展项标识nid以及堆栈搜索的起始进行搜索,如果找到,返回它在堆栈中的位置,如果没找到,返回-1。
39) int X509v3_get_ext_by_OBJ(const STACK_OF(X509_EXTENSION) *sk, ASN1_OBJECT *obj, int lastpos)
功能同上。
40)X509_EXTENSION *X509v3_get_ext(const STACK_OF(X509_EXTENSION) *x,
int loc)
获取扩展项,loc为扩展项在堆栈x中的位置,如果不成功,返回NULL。
41)int X509v3_get_ext_count(const STACK_OF(X509_EXTENSION) *x)
获取扩展项的个数,此函数调用堆栈操作sk_X509_EXTENSION_num(x)来获取扩展项的个数。
42)STACK_OF(CONF_VALUE) * X509V3_get_section(X509V3_CTX *ctx, char *section)
获取配置信息,section为配置信息中的“段”信息。比如有配置信息如下:
[CA]
Name1=A
Name2=B
则section应是”CA”,返回的信息为它包含的内容信息。
43)char * X509V3_get_string(X509V3_CTX *ctx, char *name, char *section)
根据段和属性获取值,比如有如下配置信息:
[CA]
Name1=A
Name2=B
调用此函数时name为”Name1”,sectionwei “CA”,则返回值为”A”。
44)int X509V3_get_value_bool(CONF_VALUE *value, int *asn1_bool)
判断配置信息的布尔值,如果value表示的值为true、TRUE、y、Y、yes、YES,*asn1_bool 的值设为xff,并返回1,如果为false、FALSE、n、N、NO、no, *asn1_bool设置为 0,并返回1。此函数调用不成功时返回0。
45) int X509V3_get_value_int(CONF_VALUE *value, ASN1_INTEGER **aint)
将value中的值转换为ASN1_INTEGER类型,结果存放在**aint中,函数调用成功返回1,否则返回0。
46)STACK_OF(CONF_VALUE) *X509V3_parse_list(const char *line)
分析配置信息的一行数据,返回结果。
26.7 证书验证
26.7.1证书验证项
数字证书验证中,主要考察的项有:
- 有效期,看证书是否已经失效;
- 签名,用颁发者的公钥来验证签名;
- 证书用途;
- 名字比较,证书中的颁发者信息应与颁发者证书的持有者信息一致;
- 扩展项约束;
26.7.2 Openssl中的证书验证
Openssl中的证书验证比较复杂,实现源码在x509/x509_vfy.c中,主要有两个函数:X509_verify_cert和internal_verify。X509_verify_cert主要将所有的证书信息进行排序,构造出一个有序的证书链,然后调用internal_verify函数来验证证书。internal_verify是openssl提供的一个内置的验证证书链的函数。如果用户通过X509_STORE_set_verify_func函数设置了X509_STORE_CTX的verify函数,将调用用户实现的verify函数而不会调用internal_verify。
如何用openssl函数验证证书,用户可以参考apps/verify.c。
参考文献
[1] rfc2459,Internet X.509 Public Key Infrastructure Certificate and CRL Profile
第二十七章 OCSP
27.1 概述
在线证书状态协议(OCSP,Online Certificate Status Protocol,rfc2560)用于实时表明证书状态。OCSP客户端通过查询OCSP服务来确定一个证书的状态。OCSP可以通过HTTP协议来实现。rfc2560定义了OCSP客户端和服务端的消息格式。
27.2 openssl实现
openssl在crypto/ocsp目录实现了ocsp模块,包括客户端和服务端各种函数。主要源码如下:
- ocsp_asn.c:ocsp消息的DER编解码实现,包括基本的new、free、i2d和d2i函数;
- ocsp_cl.c:ocsp客户端函数实现,主要用于生成ocsp请求;
- ocsp_srv.c:ocsp服务端思想,主要用于生成ocsp响应;
- ocsp_err.c:ocsp错误处理;
- ocsp_ext.c:ocsp扩展项处理;
- ocsp_ht.c:基于HTTP协议通信的OCSP实现;
- ocsp_lib.c:通用库实现;
- ocsp_prn:打印OCSP信息;
- ocsp_vfy:验证ocsp请求和响应;
- ocsp.h:定义了ocsp请求和响应的各种数据结构和用户接口。
27.3 主要函数
1) d2i_OCSP_REQUEST_bio
将bio中的DER编码的数据转换为OCSP_REQUEST数据结构。
2) d2i_OCSP_RESPONSE_bio
将bio中的DER编码的数据转换为OCSP_RESPONSE数据结构。
3) i2d_OCSP_RESPONSE_bio
将OCSP_RESPONSE数据结构DER编码,并输出到BIO中。
4) i2d_OCSP_REQUEST_bio
将OCSP_REQUEST数据结构DER编码,并输出到BIO中。
5) PEM_read_bio_OCSP_REQUEST
读取PEM格式的OCSP_REQUEST信息,返回其数据结构。
6) PEM_read_bio_OCSP_RESPONSE
读取PEM格式的OCSP_RESPONSE信息,返回其数据结构。
7) PEM_write_bio_OCSP_REQUEST
将OCSP_REQUEST结构写成PEM格式。
8) PEM_write_bio_OCSP_RESPONSE
将OCSP_RESPONSE结构写成PEM格式。
9) OCSP_REQUEST_sign
本函数由宏来定义,它用于给OCSP_REQUEST数据结构签名。签名的对象为DER编码的OCSP_REQINFO信息,签名算法为OCSP_SIGNATURE指定的的算法,签名私钥以及摘要算法由输入参数指定。
10)int OCSP_request_sign(OCSP_REQUEST *req,
X509 *signer,
EVP_PKEY *key,
const EVP_MD *dgst,
STACK_OF(X509) *certs,
unsigned long flags)
本函数用于给OCSP请求消息签名,通过OCSP_REQUEST_sign函数进行签名,将signer持有者信息写入req,如果flags不为OCSP_NOCERTS,将certs信息写入req。
11) OCSP_BASICRESP_sign
对OCSP_BASICRESP结构进行签名,签名结果放在OCSP_BASICRESP的signature中,摘要算法由输入参数指定。
12)OCSP_REQUEST_verify
验证ocsp请求签名,公钥由输入参数指定。
13)OCSP_BASICRESP_verify
验证ocsp响应签名,公钥由输入参数指定。
14)OCSP_request_verify
验证ocsp响应,该函数做全面的验证,包括签名、证书目的以及证书链等。
15)int OCSP_basic_sign(OCSP_BASICRESP *brsp,X509 *signer, EVP_PKEY *key,
const EVP_MD *dgst,STACK_OF(X509) *certs, unsigned long flags)
本函数用输入参数signer、key、dgst、certs和flags来填充brsp数据结构,并对brsp结构签名,成功返回1,否则返回0。
16) int OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec)
时间检查计算,合法返回1,thisupd为本次更新时间,nextupd为下次更新时间。thisupd和nextupd由响应服务生成,他们被传给请求者。请求者收到响应之后需要验证ocsp消息的时间有效性。要求如下:
- 本次更新时间不能比当前时间提前太多,提前时间不能大于nsec,比如ocsp服务器多时间比请求者系统时间快很多,导致thisupd错误非法;
- 本次更新时间不能晚于当前时间太多,否则ocsp消息失效,晚的时间不能大于maxsec;
- 下次更新时间不能晚于当前时间太多,晚多时间不大于nsec(由于下一条规则限制,也不能大于maxsec);
- 下次更新时间必须大于本次更新时间。
总之,本次更新时间和下次更新时间必须在以当前时间为中心的一个窗口内。
17)OCSP_CERTID_dup
复制函数。
18)OCSP_CERTSTATUS_dup
复制函数。
19)OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *req,
OCSP_CERTID *cid)
本函数用于往请求消息中添加一个证书ID;它将一个OCSP_CERTID信息存入OCSP_REQUEST结构,返回内部生成的OCSP_ONEREQ指针。根据cid构造一个OCSP_ONEREQ信息,并将此信息放入req请求消息的堆栈。
20)int OCSP_request_set1_name(OCSP_REQUEST *req, X509_NAME *nm)
本函数用于设置消息请求者的名字。
21)int OCSP_request_add1_cert(OCSP_REQUEST *req, X509 *cert)
本函数往消息请求中添加一个证书。此证书信息放在OCSP_REQUEST结构的一个堆栈中,并将此证书结构的引用加1。
22)int OCSP_response_status(OCSP_RESPONSE *resp)
本函数获取OCSP响应状态。
23)OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *resp)
本函数从响应数据结构中获取OCSP_BASICRESP信息。
24)int OCSP_resp_count(OCSP_BASICRESP *bs)
本函数获取响应消息中包含的证书状态的个数。
25)OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *bs, int idx);
给定单个响应的序号,从堆栈中取出。
26)int OCSP_resp_find(OCSP_BASICRESP *bs, OCSP_CERTID *id, int last)
根据ocsp证书ID查询对应的响应在堆栈中的位置,last为搜索堆栈时的起始位置,如果小于0,从0开始。
27)int OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason,
ASN1_GENERALIZEDTIME **revtime,
ASN1_GENERALIZEDTIME **thisupd,
ASN1_GENERALIZEDTIME **nextupd)
获取单个证书的状态,返回值为其状态,ocsp.h中定义如下:
#define V_OCSP_CERTSTATUS_GOOD 0
#define V_OCSP_CERTSTATUS_REVOKED 1
#define V_OCSP_CERTSTATUS_UNKNOWN 2
如果证书被撤销,并且reason和revtime参数不为空,将撤销原因以及撤销时间返回。并且对于这个证书给出thisUpdate和nextUpdate。
28)int OCSP_resp_find_status(OCSP_BASICRESP *bs, OCSP_CERTID *id, int *status,
int *reason,
ASN1_GENERALIZEDTIME **revtime,
ASN1_GENERALIZEDTIME **thisupd,
ASN1_GENERALIZEDTIME **nextupd);
功能同OCSP_single_get0_status函数,id为OCSP证书ID,它依次调用OCSP_resp_find、OCSP_resp_get0和 OCSP_single_get0_status函数,其中status为返回的证书状态。
29)int OCSP_request_add1_nonce(OCSP_REQUEST *req, unsigned char *val, int len)
添加nonce扩展项,val和len表明了nonce值,如果val为空,则内部生成长度为len的随机数作为nonce。
30)int OCSP_basic_add1_nonce(OCSP_BASICRESP *resp, unsigned char *val, int len)
功能同上。
31)int OCSP_check_nonce(OCSP_REQUEST *req, OCSP_BASICRESP *bs)
检测nonce,用于防止重放攻击;检查请求和响应的nonce扩展项,看他们是否相等,OCSP服务端应当将请求中的nonce拷贝到响应中。如果请求和响应中的nonce扩展项都存在,比较nonce值,如果不相等,返回错误,或者,请求中有nonce,而响应中没有nonce,也返回错误。验证正确时返回值大于0。
32)int OCSP_copy_nonce(OCSP_BASICRESP *resp, OCSP_REQUEST *req)
将请求中都nonce拷贝到响应中。
33)X509_EXTENSION *OCSP_crlID_new(char *url, long *n, char *tim)
根据crl的url,crl个数以及生成crl的时间生成X509_EXTENSION扩展项。
34)X509_EXTENSION *OCSP_accept_responses_new(char **oids)
根据多个oid的名字生成扩展项,其中oids指针数组,以NULL结尾。本函数由客户端调用,告诉服务端它所要的端响应的类型,参考rfc2560对于AcceptableResponses扩展项的说明。
35)X509_EXTENSION *OCSP_archive_cutoff_new(char* tim)
生成单个证书的Archive Cutoff扩展项,某已被撤销的证书的Archive Cutoff时间为本次OCSP生效时间(producedAt)减去被撤销时的时间。可以将它看作已撤销了多长时间。
36)X509_EXTENSION *OCSP_url_svcloc_new(X509_NAME* issuer, char **urls);
根据颁发者名字和一个或多个url生成扩展项。扩展项内容为AuthorityInfoAccess。urls为指针数组,以NULL结束。
37)OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *dgst, X509 *subject, X509 *issuer)
根据摘要算法、持有者证书和颁发者证书生成OCSP_CERTID数据结构。
38)OCSP_CERTID *OCSP_cert_id_new(const EVP_MD *dgst,
X509_NAME *issuerName,
ASN1_BIT_STRING* issuerKey,
ASN1_INTEGER *serialNumber);
本函数根据摘要算法、颁发者名字、颁发者公钥DER编码以及证书持有者的证书序列号生成OCSP_CERTID;奇怪的是serialNumber可以为空,无法标识需要查询状态证书。
39)int OCSP_id_issuer_cmp(OCSP_CERTID *a, OCSP_CERTID *b)
比较OCSP_CERTID,如果相等返回0,不相等返回非0。本函数不比较证书序列号。
40)int OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b)
比较OCSP_CERTID,如果相等返回0,不相等返回非0。本函数比较所有项,包括证书序列号。
41) int OCSP_parse_url(char *url, char **phost, char **pport, char **ppath, int *pssl);
分析url,获取主机、端口、路径和协议(http还是https)等信息。
42) char *OCSP_response_status_str(long s)
根据OCSP响应码获取响应状态信息。
43)char *OCSP_cert_status_str(long s)
根据证书状态码获取证书状态信息。
44) char *OCSP_crl_reason_str(long s)
根据状态码获取证书撤销原因。
45)int OCSP_REQUEST_print(BIO *bp, OCSP_REQUEST* o, unsigned long flags)
将OCSP请求OCSP_REQUEST的信息输出到bp中,flags表明不支持到扩展项 的处理方式,参考X509V3_extensions_print以及X509V3_EXT_print函数。
46)int OCSP_RESPONSE_print(BIO *bp, OCSP_RESPONSE* o, unsigned long flags)
将OCSP请求OCSP_RESPONSE的信息输出到bp中,flags表明不支持到扩展项到处理方式,参考X509V3_extensions_print以及X509V3_EXT_print 函数。
47)int OCSP_request_onereq_count(OCSP_REQUEST *req)
获取OCSP请求中请求列表的个数,即多少个证书状态需要查询。
48)OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i)
根据在堆栈中到位置获取OCSP_ONEREQ,OCSP_ONEREQ包含了单个证书的信息。
49)OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *one)
获取OCSP_ONEREQ中到证书ID信息。
50)int OCSP_id_get0_info(ASN1_OCTET_STRING **piNameHash,
ASN1_OBJECT **pmd,ASN1_OCTET_STRING **pikeyHash,
ASN1_INTEGER **pserial, OCSP_CERTID *cid)
从cid中获取颁发者名字摘要值、摘要算法、颁发者公钥摘要值以及持有者证书序列号,成功返回1,否则为0。
51) int OCSP_request_is_signed(OCSP_REQUEST *req)
判断请求是否已签名,如果已签名返回1,否则返回0。
52)OCSP_RESPONSE *OCSP_response_create(int status, OCSP_BASICRESP *bs)
生成OCSP响应数据,status为响应状态,bs为响应的具体内容。
53)OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *rsp,
OCSP_CERTID *cid,
int status, int reason,
ASN1_TIME *revtime,
ASN1_TIME *thisupd, ASN1_TIME *nextupd);
根据输入参数证书ID、证书状态、撤销原因、撤销时间、本次OCSP时间以及下次OCSP时间生成一个单一证书的状态信息,将此状态信息放入rsp的堆栈中,并返回此状态信息。
54)int OCSP_basic_add1_cert(OCSP_BASICRESP *resp, X509 *cert)
添加一个证书到响应信息中。
55)ASN1_STRING *ASN1_STRING_encode(ASN1_STRING *s, i2d_of_void *i2d,
void *data, STACK_OF(ASN1_OBJECT) *sk)
本函数将数据进行DER编码,编码后的结果放在ASN1_STRING中,并返回此ASN1_STRING。其中,s为要设置的ASN1_STRING,i2d为输入数据的i2d方法,data为输入数据结构,sk为输入对象堆栈。如果data不为空,则DER编码data指向的数据结构;如果data为空,sk不为空,则DER编码sk堆栈表示的内容。
56)int OCSP_REQUEST_get_ext_count(OCSP_REQUEST *x)
获取OCSP_REQUEST结构中tbsRequest成员的扩展项的个数。
57)int OCSP_REQUEST_get_ext_by_NID(OCSP_REQUEST *x, int nid, int lastpos)
根据对象nid获取扩展项在x->tbsRequest->requestExtensions中的位置。
58)int OCSP_REQUEST_get_ext_by_OBJ(OCSP_REQUEST *x,
ASN1_OBJECT *obj, int lastpos)
获取对象在x->tbsRequest->requestExtensions中的位置。
59)int OCSP_REQUEST_get_ext_by_critical(OCSP_REQUEST *x, int crit, int lastpos)
根据是否关键crit以及堆栈搜索基准lastpos获取x->tbsRequest->requestExtensions中扩展项的位置。
60)X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *x, int loc)
根据扩展项在堆栈中的位置获取扩展项。
61)X509_EXTENSION *OCSP_REQUEST_delete_ext(OCSP_REQUEST *x, int loc)
根据扩展项在堆栈中的位置删除扩展项。
62)void *OCSP_REQUEST_get1_ext_d2i(OCSP_REQUEST *x, int nid, int *crit, int *idx)
根据扩展项nid获取扩展项信息,其中返回值为扩展项数据结构的指针地址,crit返回是否时关键扩展,idx表明它在堆栈中的位置。
63)int OCSP_REQUEST_add1_ext_i2d(OCSP_REQUEST *x, int nid, void *value, int crit,
unsigned long flags)
将具体的扩展项添加到x中,成功则返回1。其中,nid表明是什么扩展项,crit表明是否是关键扩展,value是具体扩展项数据结构的地址,flags表明了何种操作,参考函数X509V3_add1_i2d。
64)int OCSP_REQUEST_add_ext(OCSP_REQUEST *x, X509_EXTENSION *ex, int loc)
将扩展项添加到x->tbsRequest->requestExtensions堆栈中,loc表示堆栈位置。
65)int OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs,
X509_STORE *st, unsigned long flags)
验证OCSP响应消息,成功返回1。验证内容有:验证OCSP签名、验证签名者证书、检查每个证书状态信息的颁发者是否是相同、检查颁发者证书的扩展密钥用法中是否支持OCSP签名。
27.4编程示例
ocsp的编程主要是生成ocsp请求、解析ocsp请求、生成ocsp响应、解析ocsp响应得到结果以及消息的签名和验证。客户端可用ocsp_cl.c中提供的函数,服务端可用ocsp_srv.c中提供的函数。典型的应用程序请参考apps/ocsp.c。
第二十八章 CRL
28.1 CRL介绍
证书撤销列表(Certificate Revocation List,简称CRL),是一种包含撤销的证书列表的签名数据结构。CRL是证书撤销状态的公布形式,CRL就像信用卡的黑名单,用于公布某些数字证书不再有效。
CRL是一种离线的证书状态信息。它以一定的周期进行更新。CRL可以分为完全CRL和增量CRL。在完全CRL中包含了所有的被撤销证书信息,增量CRL由一系列的CRL来表明被撤销的证书信息,它每次发布的CRL是对前面发布CRL的增量扩充。
基本的CRL信息有:被撤销证书序列号、撤销时间、撤销原因、签名者以及CRL签名等信息。
基于CRL的验证是一种不严格的证书认证。CRL能证明在CRL中被撤销的证书是无效的。但是,它不能给出不在CRL中的证书的状态。如果执行严格的认证,需要采用在线方式进行认证,即OCSP认证。
28.2 数据结构
Openssl中的crl数据结构定义在crypto/x509/x509.h中。
1) X509_REVOKED
typedef struct X509_revoked_st
{
ASN1_INTEGER *serialNumber;
ASN1_TIME *revocationDate;
STACK_OF(X509_EXTENSION) *extensions;
int sequence;
} X509_REVOKED;
本结构用于存放一个被撤销证书的信息,各项意义如下:
serialNumber:被撤销证书的序列号;
revocationDate:撤销时间;
extensions:扩展项,可选;
sequence:顺序号,用于排序,表示当前被撤销证书信息在crl中的顺序。
2)X509_CRL_INFO
typedef struct X509_crl_info_st
{
ASN1_INTEGER *version;
X509_ALGOR *sig_alg;
X509_NAME *issuer;
ASN1_TIME *lastUpdate;
ASN1_TIME *nextUpdate;
STACK_OF(X509_REVOKED) *revoked;
STACK_OF(X509_EXTENSION) *extensions;
ASN1_ENCODING enc;
} X509_CRL_INFO;
crl信息主体,各项意义如下:
version:crl版本;
sig_alg:crl签名法;
issuer:签发者信息;
lastUpdate:上次更新时间;
nextUpdate:下次更新时间;
revoked:被撤销证书信息;
extensions:扩展项,可选。
3) X509_CRL
struct X509_crl_st
{
X509_CRL_INFO *crl;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
int references;
} ;
完整crl数据结构,各项意义如下:
crl:crl信息主体;
sig_alg:签名算法,与X509_CRL_INFO中的一致;
signature:签名值;
references:引用。
上述三个结构的DER编解码通过宏在crypto/asn1/x_crl.c中实现,包括new、free、i2d和d2i函数。
28.3 CRL函数
CRL函数主要是set和get函数,如下:
1) int X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev)
添加一个被撤销证书的信息。
2) int X509_CRL_print(BIO *bp,X509_CRL *x)
打印crl内容到BIO中。
3) int X509_CRL_print_fp(FILE *fp, X509_CRL *x)
将crl的内容输出到fp中,此函数调用了X509_CRL_print。
4) int X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name)
设置crl的颁发者。
5) int X509_CRL_set_lastUpdate(X509_CRL *x, ASN1_TIME *tm)
设置crl上次发布时间。
6) int X509_CRL_set_nextUpdate(X509_CRL *x, ASN1_TIME *tm)
设置crl下次发布时间。
7) int X509_CRL_set_version(X509_CRL *x, long version)
设置crl版本。
8) int X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const EVP_MD *md)
对crl进行签名,pkey为私钥,md为摘要算法,结果存放在x-> signature中。
9) int X509_CRL_sort(X509_CRL *c)
根据证书序列号对crl排序,此函数实现采用了堆栈排序,堆栈的比较函数为X509_REVOKED_cmp(crypto/asn1/x_crl.c)。
10)int X509_CRL_add1_ext_i2d(X509_CRL *x, int nid, void *value, int crit, unsigned long flags)
添加CRL扩展,nid为要添加的扩展标识,value为被添加的具体扩展项的内部数据结构地址,crit表明是否为关键扩展,flags表明何种操作。此函数调用X509V3_add1_i2d函数。
11)int X509_CRL_add_ext(X509_CRL *x, X509_EXTENSION *ex, int loc)
添加扩展项到指定堆栈位置,此函数调用X509v3_add_ext,进行堆栈插入操作。
12)int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b)
CRL比较,此函数调用X509_NAME_cmp,只比较颁发者的名字是否相同。
13)X509_EXTENSION *X509_CRL_delete_ext(X509_CRL *x, int loc)
删除CRL扩展项堆栈中的某一项,loc指定被删除项在堆栈中的位置。
14)int X509_CRL_digest(const X509_CRL *data, const EVP_MD *type,
unsigned char *md, unsigned int *len)
CRL摘要,本函数对X509_CRL进行摘要,type指定摘要算法,摘要结果存放在md中,len表明摘要结果长度。
15)X509_CRL_dup
CRL数据拷贝,此函数通过宏来实现。大部分ASN1类型数据都有dup函数,它们的实现方式比较简单:将对象DER编码,然后再解码,这样就实现了ASN1数据的复制。
16) void *X509_CRL_get_ext_d2i(X509_CRL *x, int nid, int *crit, int *idx)
CRL中的获取扩展项,此函数用于获取crl中指定扩展项的内部数据结构,返回值为具体的扩展项数据结构地址,nid为扩展项标识,它调用了X509V3_get_d2i函数。
17)int X509_CRL_get_ext_by_critical(X509_CRL *x, int crit, int lastpos)
获取扩展项在其堆栈中的位置,crit为扩展项是否关键标识,lastpos为堆栈搜索起始位置。此函数调用了X509v3_get_ext_by_critical。
18)int X509_CRL_get_ext_by_NID(X509_CRL *x, int nid, int lastpos)
获取扩展项在其堆栈中的位置,nid为扩展项标识,lastpos为搜索起始位置。如果找到此扩展项,返回其在堆栈中的位置。
19) int X509_CRL_get_ext_by_OBJ(X509_CRL *x, ASN1_OBJECT *obj, int lastpos)
同上。
20)int X509_CRL_get_ext_count(X509_CRL *x)
获取crl中扩展项的个数。
21) int X509_CRL_verify(X509_CRL *a, EVP_PKEY *r)
验证CRL。EVP_PKEY结构r中需要给出公钥。
28.4 编程示例
下面的例子用来生成一个crl文件。
#include <openssl/x509.h>
int main()
{
int ret,len;
unsigned char *buf,*p;
unsigned long e=RSA_3;
FILE *fp;
time_t t;
X509_NAME *issuer;
ASN1_TIME *lastUpdate,*nextUpdate,*rvTime;
X509_CRL *crl=NULL;
X509_REVOKED *revoked;
EVP_PKEY *pkey;
ASN1_INTEGER *serial;
RSA *r;
BIGNUM *bne;
BIO *bp;
/* 生成密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,1024,bne,NULL);
if(ret!=1)
{
printf("RSA_generate_key_ex err!\n");
return -1;
}
pkey=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey,r);
crl=X509_CRL_new();
/* 设置版本*/
ret=X509_CRL_set_version(crl,3);
/* 设置颁发者*/
issuer=X509_NAME_new();
ret=X509_NAME_add_entry_by_NID(issuer,NID_commonName,V_ASN1_PRINTABLESTRING, "CRL issuer",10,-1,0);
ret=X509_CRL_set_issuer_name(crl,issuer);
/* 设置上次发布时间*/
lastUpdate=ASN1_TIME_new();
t=time(NULL);
ASN1_TIME_set(lastUpdate,t);
ret=X509_CRL_set_lastUpdate(crl,lastUpdate);
/* 设置下次发布时间*/
nextUpdate=ASN1_TIME_new();
t=time(NULL);
ASN1_TIME_set(nextUpdate,t+1000);
ret=X509_CRL_set_nextUpdate(crl,nextUpdate);
/* 添加被撤销证书序列号*/
revoked=X509_REVOKED_new();
serial=ASN1_INTEGER_new();
ret=ASN1_INTEGER_set(serial,1000);
ret=X509_REVOKED_set_serialNumber(revoked,serial);
rvTime=ASN1_TIME_new();
t=time(NULL);
ASN1_TIME_set(rvTime,t+2000);
ret=X509_CRL_set_nextUpdate(crl,rvTime);
ret=X509_REVOKED_set_revocationDate(revoked,rvTime);
ret=X509_CRL_add0_revoked(crl,revoked);
/* 排序*/
ret=X509_CRL_sort(crl);
/* 签名*/
ret=X509_CRL_sign(crl,pkey,EVP_md5());
/* 写入文件*/
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
X509_CRL_print(bp,crl);
len=i2d_X509_CRL(crl,NULL);
buf=malloc(len+10);
p=buf;
len=i2d_X509_CRL(crl,&p);
fp=fopen("crl.crl","wb");
fwrite(buf,1,len,fp);
fclose(fp);
BIO_free(bp);
X509_CRL_free(crl);
free(buf);
getchar();
return 0;
}
第二十九章 PKCS7
29.1概述
加密消息语法(pkcs7),是各种消息存放的格式标准。这些消息包括:数据、签名数据、数字信封、签名数字信封、摘要数据和加密数据。
29.2 数据结构
Openssl的pkcs7实现在crypto/pkcs7目录下。pkcs7的各种消息数据结构和函数在crypto/pkcs7/pkcs7.h中定义,主要数据结构如下:
typedef struct pkcs7_st
{
/* 其他项 */
ASN1_OBJECT *type;
union
{
char *ptr;
/* NID_pkcs7_data */
ASN1_OCTET_STRING *data;
/* NID_pkcs7_signed */
PKCS7_SIGNED *sign;
/* NID_pkcs7_enveloped */
PKCS7_ENVELOPE *enveloped;
/* NID_pkcs7_signedAndEnveloped */
PKCS7_SIGN_ENVELOPE *signed_and_enveloped;
/* NID_pkcs7_digest */
PKCS7_DIGEST *digest;
/* NID_pkcs7_encrypted */
PKCS7_ENCRYPT *encrypted;
/* Anything else */
ASN1_TYPE *other;
} d;
} PKCS7;
其中type用于表示是何种类型的pkcs7消息,data、sign、enveloped、signed_and_enveloped、digest和ncrypted对于了6种不同的具体消息。oher用于存放任意数据类型(也可以是pkcs7结构),所以,本结构可以是一个嵌套的数据结构。
pkcs7各种类型数据结构的DER编解码通过宏在crypto/pkcs7/pk7_asn1.c中实现,包括new、free、i2d和d2i函数。
29.3 函数
1) PKCS7_add_attrib_smimecap
给PKCS7_SIGNER_INFO添加NID_SMIMECapabilities属性。
2) int PKCS7_add_attribute(PKCS7_SIGNER_INFO *p7si, int nid, int atrtype,void *value)
给PKCS7_SIGNER_INFO添加属性,nid为属性类型,value为属性的ASN1数据结构,atrtype为value的ASN1类型。
3) int PKCS7_add_certificate(PKCS7 *p7, X509 *x509)
将证书添加到PKCS7对应消息的证书堆栈中,只对NID_pkcs7_signed和NID_pkcs7_signedAndEnveloped两种类型有效。
4) PKCS7_add_crl
将crl添加到PKCS7对应消息的crl堆栈中,只对NID_pkcs7_signed和NID_pkcs7_signedAndEnveloped两种类型有效。
5) PKCS7_add_recipient/ PKCS7_add_recipient_info
添加接收者信息。
6)PKCS7_add_signer
添加一个签名者信息。
7) KCS7_add_signed_attribute
给PKCS7_SIGNER_INFO添加属性。
8) PKCS7_cert_from_signer_info
从pkcs7消息中根据颁发者和证书序列号获取证书。
9) PKCS7_ctrl
控制函数。
10)PKCS7_dataDecode
解析输入的pkcs7消息,将结果存入BIO链表并返回。
11)PKCS7_dataInit/PKCS7_dataFinal
解析输入的pkcs7消息,将结果存入BIO。
12)PKCS7_dataVerify
验证pkcs7数据。
13)PKCS7_sign
签名pkcs7消息。
14) PKCS7_verify
验证pkcs7消息。
15)PKCS7_set_type
设置pkcs7消息类型。
16)PKCS7_dup
拷贝pkcs7结构。
29.4 消息编解码
PKCS7编码时调用函数i2d_PKCS7,在调用此函数之前,需要填充其内部数据结构。PKCS7解码时调用函数d2i_PKCS7获取内部数据结构。
下面是一些编码的示例。
29.4.1 data
/* pkcs7 data */
#include <string.h>
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
int main()
{
PKCS7 *p7;
int len;
char buf[1000],*der,*p;
FILE *fp;
p7=PKCS7_new();
PKCS7_set_type(p7,NID_pkcs7_data);
strcpy(buf,"pkcs7 data !\n");
len=strlen(buf);
ASN1_OCTET_STRING_set(p7->d.data,(const unsigned char *)buf,len);
len=i2d_PKCS7(p7,NULL);
der=(char *)malloc(len);
p=der;
len=i2d_PKCS7(p7,(unsigned char **)&p);
fp=fopen("p7_data.cer","wb");
fwrite(der,1,len,fp);
fclose(fp);
PKCS7_free(p7);
free(der);
return 0;
}
本例用于生成data类型的pkcs7消息。
29.4.2 signed data
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/x509.h>
int main()
{
PKCS7 *p7;
int len;
unsigned char *der,*p;
FILE *fp;
X509 *x;
BIO *in;
X509_ALGOR *md;
PKCS7_SIGNER_INFO *si;
p7=PKCS7_new();
PKCS7_set_type(p7,NID_pkcs7_signed);
p7->d.sign->cert=sk_X509_new_null();
in=BIO_new_file("b64cert.cer","r");
x=PEM_read_bio_X509(in,NULL,NULL,NULL);
sk_X509_push(p7->d.sign->cert,x);
md=X509_ALGOR_new();
md->algorithm=OBJ_nid2obj(NID_md5);
sk_X509_ALGOR_push(p7->d.sign->md_algs,md);
si=PKCS7_SIGNER_INFO_new();
ASN1_INTEGER_set(si->version,2);
ASN1_INTEGER_set(si->issuer_and_serial->serial,333);
sk_PKCS7_SIGNER_INFO_push(p7->d.sign->signer_info,si);
len=i2d_PKCS7(p7,NULL);
der=(unsigned char *)malloc(len);
p=der;
len=i2d_PKCS7(p7,&p);
fp=fopen("p7_sign.cer","wb");
fwrite(der,1,len,fp);
fclose(fp);
free(der);
PKCS7_free(p7);
return 0;
}
本例用于生成signed类型的pkcs7消息。
29.4.3 enveloped
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/x509.h>
int main()
{
PKCS7 *p7;
int len;
char *der,*p;
FILE *fp;
PKCS7_RECIP_INFO *inf;
p7=PKCS7_new();
PKCS7_set_type(p7,NID_pkcs7_enveloped);
ASN1_INTEGER_set(p7->d.enveloped->version,3);
inf=PKCS7_RECIP_INFO_new();
ASN1_INTEGER_set(inf->version,4);
ASN1_INTEGER_set(inf->issuer_and_serial->serial,888888);
inf->key_enc_algor->algorithm=OBJ_nid2obj(NID_des_ede3_cbc);
ASN1_OCTET_STRING_set(inf->enc_key,(const unsigned char *)"key info....",12);
sk_PKCS7_RECIP_INFO_push(p7->d.enveloped->recipientinfo,inf);
p7->d.enveloped->enc_data->algorithm->algorithm=OBJ_nid2obj(NID_des_ede3_cbc);
p7->d.enveloped->enc_data->enc_data=ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(p7->d.enveloped->enc_data->enc_data,(const unsigned char *)"info....",8);
len=i2d_PKCS7(p7,NULL);
der=(char *)malloc(len);
p=der;
len=i2d_PKCS7(p7,(unsigned char **)&p);
fp=fopen("p7_evveloped.cer","wb");
fwrite(der,1,len,fp);
fclose(fp);
PKCS7_free(p7);
free(der);
return 0;
}
本例用于生成enveloped类型的pkcs7消息。
29.4.4 signed_and_enveloped
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
int main()
{
PKCS7 *p7;
int len;
char *der,*p;
FILE *fp;
p7=PKCS7_new();
PKCS7_set_type(p7,NID_pkcs7_signedAndEnveloped);
len=i2d_PKCS7(p7,NULL);
der=(char *)malloc(len);
p=der;
len=i2d_PKCS7(p7,(unsigned char **)&p);
fp=fopen("p7_singAndEnv.cer","wb");
fwrite(der,1,len,fp);
fclose(fp);
PKCS7_free(p7);
free(der);
return 0;
}
本例用于生成signedAndEnveloped类型的pkcs7消息,不过省略了数据结构的填充。
29.4.5 digest
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
int main()
{
PKCS7 *p7;
int ret;
BIO *b;
p7=PKCS7_new();
ret=PKCS7_set_type(p7,NID_pkcs7_digest);
b=BIO_new_file("p7Digest.pem","w");
PEM_write_bio_PKCS7(b,p7);
BIO_free(b);
PKCS7_free(p7);
return 0;
}
本例用于生成digest类型的pkcs7消息,并以PEM格式存储。
29.4.6 encrypted
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/x509.h>
int main()
{
PKCS7 *p7;
int ret,len;
char *der,*p;
FILE *fp;
p7=PKCS7_new();
ret=PKCS7_set_type(p7,NID_pkcs7_encrypted);
ASN1_INTEGER_set(p7->d.encrypted->version,3);
p7->d.encrypted->enc_data->algorithm->algorithm=OBJ_nid2obj(NID_des_ede3_cbc);
p7->d.encrypted->enc_data->enc_data=ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(p7->d.encrypted->enc_data->enc_data,(const unsigned char *)"3434",4);
len=i2d_PKCS7(p7,NULL);
der=(char *)malloc(len);
p=der;
len=i2d_PKCS7(p7,(unsigned char **)&p);
fp=fopen("p7_enc.cer","wb");
fwrite(der,1,len,fp);
fclose(fp);
PKCS7_free(p7);
free(der);
return 0;
}
本例用于生成encrypted类型的pkcs7消息。
29.4.7 读取PEM
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
int main()
{
BIO *b;
PKCS7 *p7;
b=BIO_new_file("p7Digest.pem","r");
p7=PEM_read_bio_PKCS7(b,NULL,NULL,NULL);
BIO_free(b);
PKCS7_free(p7);
return 0;
}
本例用于读取PEM格式的PKCS7数据。
29.4.8 解码pkcs7
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
int main()
{
PKCS7 *p7=NULL;
int ret,len;
char buf[1000],*p,name[1000];
FILE *fp;
fp=fopen("p7_sign.cer","rb");
len=fread(buf,1,1000,fp);
fclose(fp);
p=buf;
d2i_PKCS7(&p7,(const unsigned char **)&p,len);
ret=OBJ_obj2txt(name,1000,p7->type,0);
printf("type : %s \n",name);
PKCS7_free(p7);
return 0;
}
本例解码DER格式的PKCS7消息。
第三十章 PKCS12
30.1 概述
pkcs12 (个人数字证书标准)用于存放用户证书、crl、用户私钥以及证书链。pkcs12中的私钥是加密存放的。
30.2 openss实现
openssl的pkcs12实现在crypto/pkcs12目录,有如下源码:
- p12_add.c:处理PKCS12_SAFEBAG,PKCS12_SAFEBAG用于存放证书和私钥相关的信息;
- p12_attr.c:属性处理;
- p12_crt:生成一个完整的pkcs12;
- p12_init.c:构造一个pkcs12数据结构;
- p12_kiss.c:解析pkcs12结构,获取证书和私钥等信息;
- p12_npas:设置新口令;
- p12_p8e.c:加密处理用户私钥(pkcs8格式);
- p12_p8d.c:解密出用户私钥;
- pk12err.c:错误处理;
- p12_asn.c:pkcs12各个数据结构的DER编解码实现;
- p12_crpt.c:pkcs12的pbe(基于口令的加密)函数;
- p12_decr.c.c:pkcs12的pbe解密;
- p12_key.c:根据用户口令生成对称密钥;
- p12_mutl.c:pkcs12的MAC信息处理;
- p12_utl.c:一些通用的函数。
30.3数据结构
数据结构定义在crypto/pkcs12/pkcs12.h中,如下所示:
1)PKCS12_MAC_DATA
typedef struct
{
X509_SIG *dinfo;
ASN1_OCTET_STRING *salt;
ASN1_INTEGER *iter;
} PKCS12_MAC_DATA;
该结构用于存放pkcs12中的MAC信息,防止他人篡改。xinfo用于存放MAC值和摘要算法,salt和iter用于根据口令来生成对称密钥(pbe)。
2)PKCS12
typedef struct
{
ASN1_INTEGER *version;
PKCS12_MAC_DATA *mac;
PKCS7 *authsafes;
} PKCS12;
pkcs12数据结构,version为版本,mac用于存放MAC信息以及对称密钥相关的信息authsafes为pkcs7结构,用于存放的证书、crl以及私钥等各种信息。
3)PKCS12_BAGS
typedef struct pkcs12_bag_st
{
ASN1_OBJECT *type;
union
{
ASN1_OCTET_STRING *x509cert;
ASN1_OCTET_STRING *x509crl;
ASN1_OCTET_STRING *octet;
ASN1_IA5STRING *sdsicert;
ASN1_TYPE *other;
}value;
} PKCS12_BAGS;
该结构用于存放各种实体对象。
4)PKCS12_SAFEBAG
typedef struct
{
ASN1_OBJECT *type;
union
{
struct pkcs12_bag_st *bag;
struct pkcs8_priv_key_info_st *keybag;
X509_SIG *shkeybag;
STACK_OF(PKCS12_SAFEBAG) *safes;
ASN1_TYPE *other;
}value;
STACK_OF(X509_ATTRIBUTE) *attrib;
} PKCS12_SAFEBAG;
该结构用于存放各种证书、crl和私钥数据。
上述两种结构与pkcs7数据结构的相互转化可参考p12_add.c。在使用中,用户根据证书、私钥以及crl等信息来构造PKCS12_SAFEBAG数据结构,然后将这些结构转化为pkcs12中的pkcs7结构。
30.4函数
1) int PKCS12_gen_mac(PKCS12 *p12, const char *pass, int passlen, unsigned char *mac, unsigned int *maclen)
生成MAC值,pass为用户口令,passlen为口令长度,mac和maclen用于存放MAC值。当p12中pkcs7为数据类型时,本函数有效。
2) int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)
验证pkcs12的MAC,pass为用户口令,passlen为口令长度。PKCS12的MAC值存放在p12-> mac-> dinfo->digest中。本函数根据pass和passlen调用PKCS12_gen_mac生成一个MAC值,与p12中已有的值进行比较。
3) PKCS12_create
成PKCS12数据结构。
4) PKCS12_parse
解析PKCS12,得到私钥和证书等信息。
5) PKCS12_key_gen_asc/PKCS12_key_gen_uni
生成pkcs12密钥,输入口令为ASCII码/UNICODE。
6)unsigned char * PKCS12_pbe_crypt(X509_ALGOR *algor, const char *pass,
int passlen, unsigned char *in, int inlen, unsigned char **data,
int *datalen, int en_de)
PKCS12加解密,algor为对称算法,pass为口令,passlen为口令长度,in为输入数据,inlen为输入数据长度,data和datalen用于存放结果,en_de用于指明时加密还是解密。
7) PKCS7 *PKCS12_pack_p7data(STACK_OF(PKCS12_SAFEBAG) *sk)
打包PKCS12_SAFEBAG堆栈,生成PKCS7数据结构并返回。
8) PKCS12_unpack_p7data
上面函数的逆过程。
9) PKCS12_pack_p7encdata
将PKCS12_SAFEBAG堆栈根据pbe算法、口令和salt加密,生成pkcs7并返回。
10)PKCS12_unpack_p7encdata
上述过程的逆过程。
11) int PKCS12_newpass(PKCS12 *p12, char *oldpass, char *newpass)
替换pkcs12的口令。
12)PKCS12_setup_mac
设置pkcs12的MAC数据结构。
13)PKCS12_set_mac
设置pkcs12的MAC信息。
14)PKCS12_pack_authsafes
将pkcs7堆栈信息打包到pkcs12中。
15)PKCS12_unpack_authsafes
上面函数的逆过程,从pkcs12中解出pkcs7堆栈,并返回。
16) PKCS12 *PKCS12_init(int mode)
生成一个pkcs12数据结构,mode的值必须为NID_pkcs7_data,即pkcs12中的pkcs7类型必须是data类型。
17)PKCS12_PBE_add
加载各种pbe算法。
18)PKCS12_PBE_keyivgen
根据口令生成对称密钥,并做加解密初始化。
19)PKCS12_item_pack_safebag
将输入的数据打包为PKCS12_SAFEBAG并返回。
20)PKCS12_x5092certbag
将证书打包为PKCS12_SAFEBAG并返回。
21)PKCS12_certbag2x509
上述过程的逆过程。
22)PKCS12_x509crl2certbag
将crl打包为PKCS12_SAFEBAG并返回。
23)PKCS12_certbag2x509crl
上述过程的逆过程。
24)PKCS12_item_i2d_encrypt
将数据结构DER编码,然后加密,数据存放在ASN1_OCTET_STRING中并返回。
24)PKCS12_item_decrypt_d2i
上面函数的逆过程,解密输入数据,然后DER解码出数据结构,并返回。
25)int PKCS12_add_friendlyname_uni(PKCS12_SAFEBAG *bag,
const unsigned char *name, int namelen)
给PKCS12_SAFEBAG添加一个属性,属性类型为NID_friendlyName,name为unicode编码。
26)int PKCS12_add_friendlyname_asc(PKCS12_SAFEBAG *bag, const char *name,
int namelen)
给PKCS12_SAFEBAG添加一个属性,属性类型为NID_friendlyName,name为ASCII码。
27) PKCS12_get_friendlyname
上面函数的逆过程,返回一个ASCII码值。
28)PKCS12_add_CSPName_asc
给PKCS12_SAFEBAG添加一个NID_ms_csp_name属性,输入参数为ASCII码。
29)PKCS12_add_localkeyid
给PKCS12_SAFEBAG添加一个NID_localKeyID属性。
30)PKCS12_MAKE_SHKEYBAG
将pkcs8密钥转化为PKCS12_SAFEBAG。
30)PKCS8_PRIV_KEY_INFO *
PKCS12_decrypt_skey(PKCS12_SAFEBAG *bag, const char *pass, int passlen)
上面函数的逆过程,从bag中提取pkcs8密钥信息。
30.5 编程示例
1)pkcs12解码
之一:
#include <string.h>
#include <openssl/pkcs12.h>
int X509_ALGOR_print(BIO *bp,X509_ALGOR *signature)
{
int nid;
unsigned char *p;
PBEPARAM *pbe=NULL;
nid=OBJ_obj2nid(signature->algorithm);
switch(nid)
{
case NID_md5WithRSAEncryption:
printf("md5WithRSAEncryption");
break;
case NID_sha1WithRSAEncryption:
printf("sha1WithRSAEncryption");
break;
case NID_rsaEncryption:
printf("rsaEncryption");
break;
case NID_sha1:
printf("sha1");
break;
case NID_pbe_WithSHA1And3_Key_TripleDES_CBC:
printf("NID_pbe_WithSHA1And3_Key_TripleDES_CBC");
break;
default:
printf("unknown signature.");
break;
}
if(signature->parameter!=NULL)
{
if(nid==NID_pbe_WithSHA1And3_Key_TripleDES_CBC)
{
printf("算法参数:\n");
p=signature->parameter->value.sequence->data;
d2i_PBEPARAM(&pbe,&p,signature->parameter->value.sequence->length);
printf("salt : \n");
i2a_ASN1_INTEGER(bp,pbe->salt);
printf("\n");
printf("iter : %d\n",ASN1_INTEGER_get(pbe->iter));
}
}
printf("\n");
return 0;
}
void X509_SIG_print(BIO *bp,X509_SIG *a)
{
if(a->algor!=NULL)
{
printf("算法:\n");
X509_ALGOR_print(bp,a->algor);
}
if(a->digest!=NULL)
{
printf("摘要:\n");
i2a_ASN1_STRING(bp,a->digest,1);
}
}
void PKCS12_SAFEBAG_print(BIO *bp,PKCS12_SAFEBAG *bag)
{
int nid,attrnum,certl,len=50,k,n,x;
unsigned char *p,buf[50];
PBEPARAM *pbe=NULL;
X509_ATTRIBUTE *attr;
ASN1_TYPE *type;
X509 *cert=NULL;
nid=OBJ_obj2nid(bag->type);
if((nid==NID_pkcs8ShroudedKeyBag)|| (nid==NID_pbe_WithSHA1And3_Key_TripleDES_CBC)) /* pkcs 8 */
{
nid=OBJ_obj2nid(bag->value.shkeybag->algor->algorithm);
if(nid==NID_pbe_WithSHA1And3_Key_TripleDES_CBC)
{
/* alg */
X509_SIG_print(bp,bag->value.shkeybag);
}
}
else if(nid==NID_certBag)
{
nid=OBJ_obj2nid(bag->value.bag->type);
if(nid==NID_x509Certificate)
{
p=bag->value.bag->value.x509cert->data;
certl=bag->value.bag->value.x509cert->length;
d2i_X509(&cert,&p,certl);
if(cert!=NULL)
{
X509_print(bp,cert);
}
}
}
printf("attris : \n");
attrnum=sk_X509_ATTRIBUTE_num(bag->attrib);
for(k=0;k<attrnum;k++)
{
attr=sk_X509_ATTRIBUTE_value(bag->attrib,k);
nid=OBJ_obj2nid(attr->object);
OBJ_obj2txt(buf,len,attr->object,1);
printf("object : %s,nid is %d\n",buf,nid);
if(attr->single==0) /* set */
{
n=sk_ASN1_TYPE_num(attr->value.set);
for(x=0;x<n;x++)
{
type=sk_ASN1_TYPE_value(attr->value.set,x);
if((type->type!=V_ASN1_SEQUENCE) && (type->type!=V_ASN1_SET))
{
if(type->type==V_ASN1_OCTET_STRING)
i2a_ASN1_INTEGER(bp,type->value.octet_string);
else
ASN1_STRING_print(bp,(ASN1_STRING *)type->value.ptr);
}
}
}
printf("\n");
}
}
int main()
{
FILE *fp;
PKCS12 *p12=NULL;
PKCS7 *p7=NULL,*one;
unsigned char buf[10000],*p;
int len,i,num,j,count,ret;
STACK_OF(PKCS7) *p7s;
STACK_OF(PKCS12_SAFEBAG) *bags;
PKCS12_SAFEBAG *bag;
PBEPARAM *pbe=0;
BIO *bp;
char pass[100];
int passlen;
X509 *cert=NULL;
STACK_OF(X509) *ca=NULL;
EVP_PKEY *pkey=NULL;
fp=fopen("timeserver.pfx","rb");
len=fread(buf,1,10000,fp);
fclose(fp);
OpenSSL_add_all_algorithms();
bp=BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
p=buf;
d2i_PKCS12(&p12,&p,len);
printf("input password : \n");
scanf("%s",pass);
ret=PKCS12_parse(p12,pass,&pkey,&cert,&ca);
if(ret!=1)
{
printf("err\n");
return 0;
}
/* 私钥写入文件 */
p=buf;
len=i2d_PrivateKey(pkey,&p);
fp=fopen("prikey.cer","wb");
fwrite(buf,1,len,fp);
fclose(fp);
/* 修改密码 */
ret=PKCS12_newpass(p12,pass,"test");
fp=fopen("newpass.pfx","wb");
ret=i2d_PKCS12_fp(fp,p12);
fclose(fp);
/* version */
printf("version : %d\n",ASN1_INTEGER_get(p12->version));
/* PKCS12_MAC_DATA */
printf("PKCS12_MAC_DATA sig :\n");
X509_SIG_print(bp,p12->mac->dinfo);
printf("salt : \n");
i2a_ASN1_STRING(bp,p12->mac->salt,1);
printf("iter : %d\n",ASN1_INTEGER_get(p12->mac->iter));
/* p7s */
p7s=PKCS12_unpack_authsafes(p12);
num=sk_PKCS7_num(p7s);
for(i=0;i<num;i++)
{
one=sk_PKCS7_value(p7s,i);
if(PKCS7_type_is_data(one))
{
bags = PKCS12_unpack_p7data(one);
count=sk_PKCS12_SAFEBAG_num(bags);
for(j=0;j<count;j++)
{
bag=sk_PKCS12_SAFEBAG_value(bags,j);
PKCS12_SAFEBAG_print(bp,bag);
}
}
else if(PKCS7_type_is_encrypted(one))
{
back:
printf("\ninput password :\n");
scanf("%s",pass);
passlen=strlen(pass);
bags = PKCS12_unpack_p7encdata(one,pass,passlen);
if(bags==NULL)
goto back;
printf("passwod is :%s\n",pass);
count=sk_PKCS12_SAFEBAG_num(bags);
for(j=0;j<count;j++)
{
bag=sk_PKCS12_SAFEBAG_value(bags,j);
PKCS12_SAFEBAG_print(bp,bag);
}
}
}
BIO_free(bp);
sk_PKCS7_pop_free(p7s,PKCS7_free);
PKCS12_free(p12);
return 0;
}
之二:采用PKCS12_parse函数,下面的例子用于解析pkcs12文件,获取证书,以及RSA密钥信息。
int p12_parse
(
char *p12,int p12Len,char *pass,char *cert,int *certlen,
char *n,int *nlen,
char *e,int *elen,
char *d,int *dlen,
char *p,int *plen,
char *q,int *qlen,
char *dmp1,int *dmp1len,
char *dmq1,int *dmq1len,
char *iqmp,int *iqmplen
)
{
int ret=0,certl;
char *pp=NULL,*certp=NULL,*derCert=NULL;
BIO *bp=NULL;
PKCS12 *PK12=NULL;
EVP_PKEY *pkey=NULL;
X509 *cc=NULL;
OpenSSL_add_all_algorithms();
pp=p12;
d2i_PKCS12(&PK12,&pp,p12Len);
if(PK12==NULL)
{
printf("d2i_PKCS12 err\n");
return -1;
}
ret=PKCS12_parse(PK12,pass,&pkey,&cc,NULL);
if(ret!=1)
{
printf("PKCS12_parse err\n");
return -1;
}
/* cert */
certl=i2d_X509(cc,NULL);
certp=(char *)malloc(certl+10);
derCert=certp;
certl=i2d_X509(cc,&certp);
memcpy(cert,derCert,certl);
*certlen=certl;
free(derCert);
/* n */
*nlen=BN_bn2bin(pkey->pkey.rsa->n,n);
/* e */
*elen=BN_bn2bin(pkey->pkey.rsa->e,e);
/* d */
*dlen=BN_bn2bin(pkey->pkey.rsa->d,d);
/* p */
*plen=BN_bn2bin(pkey->pkey.rsa->p,p);
/* q */
*qlen=BN_bn2bin(pkey->pkey.rsa->q,q);
/* dmp1 */
*dmp1len=BN_bn2bin(pkey->pkey.rsa->dmp1,dmp1);
/* dmq1 */
*dmq1len=BN_bn2bin(pkey->pkey.rsa->dmq1,dmq1);
/* iqmp */
*iqmplen=BN_bn2bin(pkey->pkey.rsa->iqmp,iqmp);
PKCS12_free(PK12);
OPENSSL_free(PK12);
return 0;
}
2) 生成pkcs12证书
之一:
#include <openssl/pkcs12.h>
#include <openssl/pkcs7.h>
int main()
{
int ret,len,key_usage,iter,key_nid;
PKCS12 *p12;
PKCS7 *p7;
STACK_OF(PKCS7) *safes;
STACK_OF(PKCS12_SAFEBAG) *bags;
PKCS12_SAFEBAG *bag;
FILE *fp;
unsigned char *buf,*p,tmp[5000];
X509 *cert=NULL;
EVP_PKEY *pkey=NULL;
OpenSSL_add_all_algorithms();
p12=PKCS12_init(NID_pkcs7_data);
/*
p12->mac=PKCS12_MAC_DATA_new();
p12->mac->dinfo->algor->algorithm=OBJ_nid2obj(NID_sha1);
ASN1_STRING_set(p12->mac->dinfo->digest,"aaa",3);
ASN1_STRING_set(p12->mac->salt,"test",4);
p12->mac->iter=ASN1_INTEGER_new();
ASN1_INTEGER_set(p12->mac->iter,3);
*/
/* pkcs7 */
bags=sk_PKCS12_SAFEBAG_new_null();
fp=fopen("time.cer","rb");
len=fread(tmp,1,5000,fp);
fclose(fp);
p=tmp;
/* cert */
d2i_X509(&cert,&p,len);
bag=PKCS12_x5092certbag(cert);
sk_PKCS12_SAFEBAG_push(bags,bag);
/* private key */
fp=fopen("prikey.cer","rb");
len=fread(tmp,1,5000,fp);
fclose(fp);
p=tmp;
pkey=d2i_PrivateKey(EVP_PKEY_RSA,NULL,&p,len);
PKCS12_add_key(&bags,pkey,KEY_EX,PKCS12_DEFAULT_ITER,NID_pbe_WithSHA1And3_Key_TripleDES_CBC,"openssl");
p7=PKCS12_pack_p7data(bags);
safes=sk_PKCS7_new_null();
sk_PKCS7_push(safes,p7);
ret=PKCS12_pack_authsafes(p12,safes);
len=i2d_PKCS12(p12,NULL);
buf=p=malloc(len);
len=i2d_PKCS12(p12,&p);
fp=fopen("myp12.pfx","wb");
fwrite(buf,1,len,fp);
fclose(fp);
printf("ok\n");
return 0;
}
之二:采用PKCS12_create函数:
#include <openssl/pkcs12.h>
#include <openssl/pkcs7.h>
int main()
{
int ret,len,key_usage,iter,key_nid;
PKCS12 *p12;
PKCS7 *p7;
STACK_OF(PKCS7) *safes;
STACK_OF(PKCS12_SAFEBAG) *bags;
PKCS12_SAFEBAG *bag;
FILE *fp;
unsigned char *buf,*p,tmp[5000];
X509 *cert=NULL;
EVP_PKEY *pkey=NULL;
OpenSSL_add_all_algorithms();
fp=fopen("time.cer","rb");
len=fread(tmp,1,5000,fp);
fclose(fp);
p=tmp;
/* cert */
d2i_X509(&cert,&p,len);
/* private key */
fp=fopen("prikey.cer","rb");
len=fread(tmp,1,5000,fp);
fclose(fp);
p=tmp;
pkey=d2i_PrivateKey(EVP_PKEY_RSA,NULL,&p,len);
p12=PKCS12_create("ossl","friend name",pkey,cert,NULL,NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
NID_pbe_WithSHA1And40BitRC2_CBC,PKCS12_DEFAULT_ITER,
-1,KEY_EX);
len=i2d_PKCS12(p12,NULL);
buf=p=malloc(len);
len=i2d_PKCS12(p12,&p);
fp=fopen("myp12.pfx","wb");
fwrite(buf,1,len,fp);
fclose(fp);
printf("ok\n");
return 0;
}
第三十一章 SSL实现
31.1概述
SSL协议最先由netscape公司提出,包括sslv2和sslv3两个版本。当前形成标准的为了tls协议(rfc2246规范)和DTLS(rfc4347,用于支持UDP协议)。sslv3和tls协议大致一样,只是有一些细微的差别。实际应用中,用的最多的为sslv3。
SSL协议能够保证通信双方的信道安全。它能提供数据加密、身份认证以及消息完整性保护,另外SSL协议还支持数据压缩。
SSL协议通过客户端和服务端握手来协商各种算法和密钥。
31.2 openssl实现
SSL协议源码位于ssl目录下。它实现了sslv2、sslv3、TLS以及DTLS(Datagram TLS,基于UDP的TLS实现)。ssl实现中,对于每个协议,都有客户端实现(XXX_clnt.c)、服务端实现(XXX_srvr.c)、加密实现(XXX_enc.c)、记录协议实现(XXX_pkt.c)、METHOD方法(XXX_meth.c)、客户端服务端都用到的握手方法实现(XXX_both.c),以及对外提供的函数实现(XXX_lib.c),比较有规律。
31.3 建立SSL测试环境
为了对SSL协议有大致的了解,我们可以通过openssl命令来建立一个SSL测试环境。
1) 建立自己的CA
在openssl安装目录的misc目录下(或者在apps目录下),运行脚本:./CA.sh -newca(Windows环境下运行:perl ca.pl –newca),出现提示符时,直接回车。 运行完毕后会生成一个demonCA的目录,里面包含了ca证书及其私钥。
2) 生成客户端和服务端证书申请:
openssl req -newkey rsa:1024 -out req1.pem -keyout sslclientkey.pem
openssl req -newkey rsa:1024 -out req2.pem -keyout sslserverkey.pem
3) 签发客户端和服务端证书
openssl ca -in req1.pem -out sslclientcert.pem
openssl ca -in req2.pem -out sslservercert.pem
4) 运行ssl服务端和客户端:
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -ssl3
openssl s_client -ssl3 -CAfile demoCA/cacert.pem
运行客户端程序后,如果正确,会打印类似如下内容:
SSL-Session:
Protocol : SSLv3
Cipher : DHE-RSA-AES256-SHA
Session-ID: A729F5845CBFFBA68B27F701A6BD9D411627FA5BDC780264131EE966D1DFD6F5
Session-ID-ctx:
Master-Key: B00EEBD68165197BF033605F348A91676E872EB48487990D8BC77022578EECC0A9789CD1F929E6A9EA259F9F9F3F9DFA
Key-Arg : None
Start Time: 1164077175
Timeout : 7200 (sec)
Verify return code: 0 (ok)
此时,输入数据然后回车,服务端会显示出来。
命令的其他选项:
a) 验证客户端证书
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -ssl3 -Verify 1
openssl s_client -ssl3 -CAfile demoCA/cacert.pem -cert sslclientcert.pem -key sslclientkey.pem
b) 指定加密套件
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -ssl3 -Verify 1
openssl s_client -ssl3 -CAfile demoCA/cacert.pem -cert sslclientcert.pem -key sslclientkey.pem -cipher AES256-SHA
其中AES256-SHA可用根据openssl ciphers命令获取,s_server也可用指明加密套件:
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -ssl3 -Verify 1 -cipher AES256-SHA
c) 指定私钥加密口令
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -ssl3 -Verify 3 -cipher AES256-SHA -pass pass:123456
openssl s_client -ssl3 -CAfile demoCA/cacert.pem -cert sslclientcert.pem -key sslclientkey.pem -pass pass:123456
用参数pass给出私钥保护口令来源:
-pass file:1.txt (1.txt的内容为加密口令123456);
-pass env:envname (环境变量);
-pass fd:fdname ;
-pass stdin。
比如:
openssl s_client -ssl3 -CAfile demoCA/cacert.pem -cert sslclientcert.pem -key sslclientkey.pem -pass stdin
然后输入口令123456即可。
31.4 数据结构
ssl的主要数据结构定义在ssl.h中。主要的数据结构有SSL_CTX、SSL和SSL_SESSION。SSL_CTX数据结构主要用于SSL握手前的环境准备,设置CA文件和目录、设置SSL握手中的证书文件和私钥、设置协议版本以及其他一些SSL握手时的选项。SSL数据结构主要用于SSL握手以及传送应用数据。SSL_SESSION中保存了主密钥、session id、读写加解密钥、读写MAC密钥等信息。SSL_CTX中缓存了所有SSL_SESSION信息,SSL中包含SSL_CTX。一般SSL_CTX的初始化在程序最开始调用,然后再生成SSL数据结构。由于SSL_CTX中缓存了所有的SESSION,新生成的SSL结构又包含SSL_CTX数据,所以通过SSL数据结构能查找以前用过的SESSION id,实现SESSION重用。
31.5 加密套件
一个加密套件指明了SSL握手阶段和通信阶段所应该采用的各种算法。这些算法包括:认证算法、密钥交换算法、对称算法和摘要算法等。
在握手初始化的时候,双方都会导入各自所认可的多种加密套件。在握手阶段,由服务端选择其中的一种加密套件。
OpenSSL的ciphers命令可以列出所有的加密套件。openssl的加密套件在s3_lib.c的ssl3_ciphers数组中定义。比如有:
/* Cipher 05 */
{
1,
SSL3_TXT_RSA_RC4_128_SHA,
SSL3_CK_RSA_RC4_128_SHA,
SSL_kRSA|SSL_aRSA|SSL_RC4 |SSL_SHA1|SSL_SSLV3,
SSL_NOT_EXP|SSL_MEDIUM,
0,
128,
128,
SSL_ALL_CIPHERS,
SSL_ALL_STRENGTHS,
}
其中1表示是合法的加密套件;SSL3_TXT_RSA_RC4_128_SHA为加密套件的名字,SSL3_CK_RSA_RC4_128_SHA为加密套件ID,SSL_kRSA|SSL_aRSA|SSL_RC4 |SSL_SHA1|SSL_SSLV3表明了各种算法,其中密钥交换采用RSA算法(SSL_kRSA),认证采用RSA算法(SSL_aRSA),对称加密算法采用RC4算法(SSL_RC4),摘要采用SHA1,采用SSL协议第三版本,SSL_NOT_EXP|SSL_MEDIUM表明算法的强度。
在客户端和服务器端建立安全连接之前,双方都必须指定适合自己的加密套件。加密套件的选择可以通过组合的字符串来控制。
字符串的形式举例:ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH。
Openssl定义了4中选择符号:“+”,“-”,“!”,“@”。其中,“+”表示取交集;“-”表示临时删除一个算法;“!”表示永久删除一个算法;“@“表示了排序方法。
多个描述之间可以用“:”、“,”、“ ”、“;”来分开。选择加密套件的时候按照从左到的顺序构成双向链表,存放与内存中。
ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH表示的意义是:首先选择所有的加密套件(不包含eNULL,即空对称加密算法),然后在得到的双向链表之中去掉身份验证采用DH的加密套件;加入包含RC4算法并将包含RSA的加密套件放在双向链表的尾部;再将支持SSLV2的加密套件放在尾部;最后得到的结果按照安全强度进行排序。
SSL建立链接之前,客户端和服务器端用openssl函数来设置自己支持的加密套件。主要的函数有:
int SSL_set_cipher_list(SSL *s,const char *str);
int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);
比如只设置一种加密套件:
int ret=SSL_set_cipher_list(ssl,"RC4-MD5");
如果服务端只设置了一种加密套件,那么客户端要么接受要么返回错误。加密套件的选择是由服务端做出的。
31.6 密钥信息
ssl中的密钥相关信息包括:预主密钥、主密钥、读解密密钥及其iv、写加密密钥及其iv、读MAC密钥、写MAC密钥。
1) 预主密钥
预主密钥是主密钥的计算来源。它由客户端生成,采用服务端的公钥加密发送给服务端。
以sslv3为例,预主密钥的生成在源代码s3_clnt.c的ssl3_send_client_key_exchange函数中,有源码如下:
tmp_buf[0]=s->client_version>>8;
tmp_buf[1]=s->client_version&0xff;
if (RAND_bytes(&(tmp_buf[2]),sizeof tmp_buf-2) <= 0)
goto err;
s->session->master_key_length=sizeof tmp_buf;
……
n=RSA_public_encrypt(sizeof tmp_buf,tmp_buf,p,rsa,RSA_PKCS1_PADDING);
此处,tmp_buf中存放的就是预主密钥。
2) 主密钥
主密钥分别由客户端和服务端根据预主密钥、客户端随机数和服务端随机数来生成,他们的主密钥是相同的。主密钥用于生成各种密钥信息,它存放在SESSION数据结构中。由于协议版本不同,生成方式也不同。sslv3的源代码中,它通过ssl3_generate_master_secret函数生成,tlsv1中它通过tls1_generate_master_secret函数来生成。
3) 对称密钥和MAC密钥
对称密钥(包括IV)和读写MAC密钥通过主密钥、客户端随机数和服务端随机数来生成。sslv3源代码中,它们在ssl3_generate_key_block中生成,在ssl3_change_cipher_state中分配。
31.7 SESSION
当客户端和服务端在握手中新建了session,服务端生成一个session ID,通过哈希表缓存SESSION信息,并通过server hello消息发送给客户端。此ID是一个随机数,SSL v2版本时长度为16字节,SSLv3和TLSv1长度为32字节。此ID与安全无关,但是在服务端必须是唯一的。当需要session重用时,客户端发送包含session id的clientHello消息(无sesion重用时,此值为空)给服务端,服务端可用根据此ID来查询缓存。session重用可以免去诸多SSL握手交互,特别是客户端的公钥加密和服务端的私钥解密所带来的性能开销。session的默认超时时间为60*5+4秒,5分钟。
session相关函数有:
1) int SSL_has_matching_session_id(const SSL *ssl, const unsigned char * id,unsigned int id_len)
SSL中查询session id,id和 id_len为输入的要查询的session id,查询哈希表ssl->ctx->sessions,如果匹配,返回1,否则返回0。
2) int ssl_get_new_session(SSL *s, int session)
生成ssl用的session,此函数可用被服务端或客户端调用,当服务端调用时,传入参数session为1,生成新的session;当客户端调用时,传入参数session为0,只是简单的将session id的长度设为0。
3) int ssl_get_prev_session(SSL *s, unsigned char *session_id, int len)
获取以前用过的session id,用于服务端session重用,本函数由服务端调用,session_id为输入senssion ID首地址,len为其长度,如果返回1,表明要session重用;返回0,表示没有找到;返回-1表示错误。
4) int SSL_set_session(SSL *s, SSL_SESSION *session)
设置session,本函数用于客户端,用于设置session信息;如果输入参数session为空值,它将置空s->session;如果不为空,它将输入信息作为session信息。
5) void SSL_CTX_flush_sessions(SSL_CTX *s, long t)
清除超时的SESSION,输入参数t指定一个时间,如果t=0,则清除所有SESSION,一般用time(NULL)取当前时间。此函数调用了哈希表函数lh_doall_arg来处理每一个SESSION数据。
6) int ssl_clear_bad_session(SSL *s)
清除无效SESSION。
31.8 多线程支持
编写openssl多线程程序时,需要设置两个回调函数:
CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
对于多线程程序的写法,读者可以参考crypto/threads/mttest.c,也可以查考下面的例子。
31.9 编程示例
本示例用多线程实现了一个ssl服务端和一个客户端。
服务端代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#else
#include <winsock2.h>
#include <windows.h>
#endif
#include "pthread.h"
#include <openssl/rsa.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define CERTF "certs/sslservercert.pem"
#define KEYF "certs/sslserverkey.pem"
#define CAFILE "certs/cacert.pem"
pthread_mutex_t mlock=PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t *lock_cs;
static long *lock_count;
#define CHK_NULL(x) if ((x)==NULL) { printf("null\n"); }
#define CHK_ERR(err,s) if ((err)==-1) { printf(" -1 \n"); }
#define CHK_SSL(err) if ((err)==-1) { printf(" -1 \n");}
#define CAFILE "certs/cacert.pem"
int verify_callback_server(int ok, X509_STORE_CTX *ctx)
{
printf("verify_callback_server \n");
return ok;
}
int SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx,char *filename,char *pass)
{
EVP_PKEY *pkey=NULL;
BIO *key=NULL;
key=BIO_new(BIO_s_file());
BIO_read_filename(key,filename);
pkey=PEM_read_bio_PrivateKey(key,NULL,NULL,pass);
if(pkey==NULL)
{
printf("PEM_read_bio_PrivateKey err");
return -1;
}
if (SSL_CTX_use_PrivateKey(ctx,pkey) <= 0)
{
printf("SSL_CTX_use_PrivateKey err\n");
return -1;
}
BIO_free(key);
return 1;
}
static int s_server_verify=SSL_VERIFY_NONE;
void * thread_main(void *arg)
{
SOCKET s,AcceptSocket;
WORD wVersionRequested;
WSADATA wsaData;
struct sockaddr_in service;
int err;
size_t client_len; SSL_CTX *ctx;
SSL *ssl;
X509 *client_cert;
char *str;
char buf[1024];
SSL_METHOD *meth;
ssl=(SSL *)arg;
s=SSL_get_fd(ssl);
err = SSL_accept (ssl);
if(err<0)
{
printf("ssl accerr\n");
return ;
}
printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
client_cert = SSL_get_peer_certificate (ssl);
if (client_cert != NULL)
{
printf ("Client certificate:\n");
str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);
CHK_NULL(str);
printf ("\t subject: %s\n", str);
OPENSSL_free (str);
str = X509_NAME_oneline (X509_get_issuer_name (client_cert), 0, 0);
CHK_NULL(str);
printf ("\t issuer: %s\n", str);
OPENSSL_free (str);
X509_free (client_cert);
}
else
printf ("Client does not have certificate.\n");
memset(buf,0,1024);
err = SSL_read (ssl, buf, sizeof(buf) - 1);
if(err<0)
{
printf("ssl read err\n");
closesocket(s);
return;
}
printf("get : %s\n",buf);
#if 0
buf[err] = '\0';
err = SSL_write (ssl, "I hear you.", strlen("I hear you.")); CHK_SSL(err);
#endif
SSL_free (ssl);
closesocket(s);
}
pthread_t pthreads_thread_id(void)
{
pthread_t ret;
ret=pthread_self();
return(ret);
}
void pthreads_locking_callback(int mode, int type, char *file,
int line)
{
if (mode & CRYPTO_LOCK)
{
pthread_mutex_lock(&(lock_cs[type]));
lock_count[type]++;
}
else
{
pthread_mutex_unlock(&(lock_cs[type]));
}
}
int main ()
{
int err;
int i;
SOCKET s,AcceptSocket;
WORD wVersionRequested;
WSADATA wsaData;
struct sockaddr_in service;
pthread_t pid;
size_t client_len;
SSL_CTX *ctx;
SSL *ssl;
X509 *client_cert;
char *str;
char buf[1024];
SSL_METHOD *meth;
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
meth = SSLv3_server_method();
ctx = SSL_CTX_new (meth);
if (!ctx)
{
ERR_print_errors_fp(stderr);
exit(2);
}
if ((!SSL_CTX_load_verify_locations(ctx,CAFILE,NULL)) ||
(!SSL_CTX_set_default_verify_paths(ctx)))
{
printf("err\n");
exit(1);
}
if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
exit(3);
}
if (SSL_CTX_use_PrivateKey_file_pass(ctx, KEYF, "123456") <= 0)
{
ERR_print_errors_fp(stderr);
exit(4);
}
if (!SSL_CTX_check_private_key(ctx))
{
fprintf(stderr,"Private key does not match the certificate public key\n");
exit(5);
}
s_server_verify=SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|
SSL_VERIFY_CLIENT_ONCE;
SSL_CTX_set_verify(ctx,s_server_verify,verify_callback_server);
SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file(CAFILE));
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
printf("err\n");
return -1;
}
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s<0) return -1;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(1111);
if (bind( s, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR)
{
printf("bind() failed.\n");
closesocket(s);
return -1;
}
if (listen( s, 1 ) == SOCKET_ERROR)
printf("Error listening on socket.\n");
printf("recv .....\n");
lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
lock_count=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
for (i=0; i<CRYPTO_num_locks(); i++)
{
lock_count[i]=0;
pthread_mutex_init(&(lock_cs[i]),NULL);
}
CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
while(1)
{
struct timeval tv;
fd_set fdset;
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(s, &fdset);
select(s+1, &fdset, NULL, NULL, (struct timeval *)&tv);
if(FD_ISSET(s, &fdset))
{
AcceptSocket=accept(s, NULL,NULL);
ssl = SSL_new (ctx);
CHK_NULL(ssl);
err=SSL_set_fd (ssl, AcceptSocket);
if(err>0)
{
err=pthread_create(&pid,NULL,&thread_main,(void *)ssl);
pthread_detach(pid);
}
else
continue;
}
}
SSL_CTX_free (ctx);
return 0;
}
客户端代码如下:
#include <stdio.h>
#include <memory.h>
#include <errno.h>
#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#else
#include <windows.h>
#endif
#include "pthread.h"
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAX_T 1000
#define CLIENTCERT "certs/sslclientcert.pem"
#define CLIENTKEY "certs/sslclientkey.pem"
#define CAFILE "certs/cacert.pem"
static pthread_mutex_t *lock_cs;
static long *lock_count;
pthread_t pthreads_thread_id(void)
{
pthread_t ret;
ret=pthread_self();
return(ret);
}
void pthreads_locking_callback(int mode, int type, char *file,
int line)
{
if (mode & CRYPTO_LOCK)
{
pthread_mutex_lock(&(lock_cs[type]));
lock_count[type]++;
}
else
{
pthread_mutex_unlock(&(lock_cs[type]));
}
}
int verify_callback(int ok, X509_STORE_CTX *ctx)
{
printf("verify_callback\n");
return ok;
}
int SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx,char *filename,char *pass)
{
EVP_PKEY *pkey=NULL;
BIO *key=NULL;
key=BIO_new(BIO_s_file());
BIO_read_filename(key,filename);
pkey=PEM_read_bio_PrivateKey(key,NULL,NULL,pass);
if(pkey==NULL)
{
printf("PEM_read_bio_PrivateKey err");
return -1;
}
if (SSL_CTX_use_PrivateKey(ctx,pkey) <= 0)
{
printf("SSL_CTX_use_PrivateKey err\n");
return -1;
}
BIO_free(key);
return 1;
}
void *thread_main(void *arg)
{
int err,buflen,read;
int sd;
SSL_CTX *ctx=(SSL_CTX *)arg;
struct sockaddr_in dest_sin;
SOCKET sock;
PHOSTENT phe;
WORD wVersionRequested;
WSADATA wsaData;
SSL *ssl;
X509 *server_cert;
char *str;
char buf [1024];
SSL_METHOD *meth;
FILE *fp;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
printf("WSAStartup err\n");
return -1;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
dest_sin.sin_family = AF_INET;
dest_sin.sin_addr.s_addr = inet_addr( "127.0.0.1" );
dest_sin.sin_port = htons( 1111 );
again:
err=connect( sock,(PSOCKADDR) &dest_sin, sizeof( dest_sin));
if(err<0)
{
Sleep(1);
goto again;
}
ssl = SSL_new (ctx);
if(ssl==NULL)
{
printf("ss new err\n");
return ;
}
SSL_set_fd(ssl,sock);
err = SSL_connect (ssl);
if(err<0)
{
printf("SSL_connect err\n");
return;
}
printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
server_cert = SSL_get_peer_certificate (ssl);
printf ("Server certificate:\n");
str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
printf ("\t subject: %s\n", str);
OPENSSL_free (str);
str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
printf ("\t issuer: %s\n", str);
OPENSSL_free (str);
X509_free (server_cert);
err = SSL_write (ssl, "Hello World!", strlen("Hello World!"));
if(err<0)
{
printf("ssl write err\n");
return ;
}
#if 0
memset(buf,0,ONE_BUF_SIZE);
err = SSL_read (ssl, buf, sizeof(buf) - 1);
if(err<0)
{
printf("ssl read err\n");
return ;
}
buf[err] = '\0';
printf ("Got %d chars:'%s'\n", err, buf);
#endif
SSL_shutdown (ssl); /* send SSL/TLS close_notify */
SSL_free (ssl);
closesocket(sock);
}
int main ()
{
int err,buflen,read;
int sd;
struct sockaddr_in dest_sin;
SOCKET sock;
PHOSTENT phe;
WORD wVersionRequested;
WSADATA wsaData;
SSL_CTX *ctx;
SSL *ssl;
X509 *server_cert;
char *str;
char buf [1024];
SSL_METHOD *meth;
int i;
pthread_t pid[MAX_T];
SSLeay_add_ssl_algorithms();
meth = SSLv3_client_method();
SSL_load_error_strings();
ctx = SSL_CTX_new (meth);
if(ctx==NULL)
{
printf("ssl ctx new eer\n");
return -1;
}
if (SSL_CTX_use_certificate_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
exit(3);
}
if (SSL_CTX_use_PrivateKey_file_pass(ctx, CLIENTKEY, "123456") <= 0)
{
ERR_print_errors_fp(stderr);
exit(4);
}
lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
lock_count=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
for (i=0; i<CRYPTO_num_locks(); i++)
{
lock_count[i]=0;
pthread_mutex_init(&(lock_cs[i]),NULL);
}
CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
for(i=0;i<MAX_T;i++)
{
err=pthread_create(&(pid[i]),NULL,&thread_main,(void *)ctx);
if(err!=0)
{
printf("pthread_create err\n");
continue;
}
}
for (i=0; i<MAX_T; i++)
{
pthread_join(pid[i],NULL);
}
SSL_CTX_free (ctx);
printf("test ok\n");
return 0;
}
上述程序在windows下运行成功,采用了windows下的开源pthread库。
需要注意的是,如果多线程用openssl,需要设置两个回调函数
CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
31.10 函数
1) SSL_accept
对应于socket函数accept,该函数在服务端调用,用来进行SSL握手。
2) int SSL_add_client_CA(SSL *ssl,X509 *x)
添加客户端CA名。
3) const char *SSL_alert_desc_string_long(int value)
根据错误号得到错误原因。
4) SSL_check_private_key
检查SSL结构中的私钥。
5) SSL_CIPHER_description
获取SSL加密套件描述。
6) SSL_CIPHER_get_bits
获取加密套件中对称算法的加密长度。
7) SSL_CIPHER_get_name
得到加密套件的名字。
8) SSL_CIPHER_get_version
根据加密套件获取SSL协议版本。
9) SSL_clear
清除SSL结构。
10) SSL_connect
对应于socket函数connect,该函数在客户端调用,用来进行SSL握手。
11) SSL_CTX_add_client_CA
给SSL_CTX添加客户端CA。
12) int SSL_CTX_add_session(SSL_CTX *ctx, SSL_SESSION *c)
往SSL_CTX添加session。
13) SSL_CTX_check_private_key
检查私钥。
14) SSL_CTX_free
释放SSL_CTX空间。
15) long SSL_CTX_get_timeout(const SSL_CTX *s)
获取超时时间。
16) SSL_CTX_get_verify_callback
获取证书验证回调函数。
17) SSL_CTX_get_verify_depth
获取证书验证深度。
18)SSL_CTX_get_verify_mode
获取验证方式,这些值在ssl.h中定义如下:
#define SSL_VERIFY_NONE 0x00
#define SSL_VERIFY_PEER 0x01
#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
#define SSL_VERIFY_CLIENT_ONCE 0x04
19)SSL_get_current_cipher
获取当前的加密套件。
20)SSL_get_fd
获取链接句柄。
21)SSL_get_peer_certificate
获取对方证书。
22)XXX_client/server_method
获取各个版本的客户端和服务端的SSL方法。
23)SSL_read
读取数据。
24) SSL_write
发送数据。
25)SSL_set_fd
设置SSL的链接句柄。
26)SSL_get_current_compression
获取当前的压缩算法的COMP_METHOD。
27)SSL_get_current_expansion
获取当前的解压算法的COMP_METHOD。
28)SSL_COMP_get_name
获取压缩/解压算法的名称。
29)SSL_CTX_set/get_ex_data
设置/读取用户扩展数据。
30)SSL_dup
复制函数。
31)SSL_get_default_timeout
获取默认超时时间。
32)SSL_do_handshake
进行ssl握手。
第三十二章 Openssl命令
32.1概述
Openssl命令源码位于apps目录下,编译的最终结果为openssl(windows下为openssl.exe)。用户可用运行openssl命令来进行各种操作。
32.2 asn1parse
asn1parse命令是一种用来诊断ASN.1结构的工具,也能用于从ASN1.1数据中提取数据。
用法:
openssl asn1parse [-inform PEM|DER] [-in filename] [-out filename] [-noout] [-offset number] [-length number] [-i] [-oid filename] [-strparse offset] [-genstr string ] [-genconf file]
选项:
-inform PEM|DER
输入数据的格式为DER还是PEM,默认为PEM格式。
-in filename
输入文件名,默认为标准输入。
-out filename
输出文件名,默认为标准输出,给定一个PEM文件,采用此选项可用生成一个DER编码的文件。
-noout
无输出打印。
-offset number
数据分析字节偏移量,分析数据时,不一定从头开始分析,可用指定偏移量,默认从头开始分析。
-length number
分析数据的长度,默认的长度为整个数据的长度;
-i
标记实体,加上此选项后,输出会有缩进,将一个ASN1实体下的其他对象缩进显示。此选项非默认选项,加上此选项后,显示更易看懂。
-dump
显示十六进制数据。非默认选项。
-dlimit number
与-dump不同,-dump显示所有的数据,而此选项只能显示由number指定数目的十六进制数据。
-oid file
指定外部的oid文件。
-strparse offset
此选项也用于从一个偏移量开始来分析数据,不过,与-offset不一样。-offset分析偏移量之后的所有数据,而-strparse只用于分析一段数据,并且这种数据必须是SET或者SEQUENCE,它只分析本SET或者SEQUENCE范围的数据。
使用示例:输入文件为一个证书的PEM格式文件,文件名为server.pem,各种命令如下:
openssl asn1parse c:\serverr.pem
openssl asn1parse –in c:\server.pem –inform pem
上面的输出内容如下:
0:d=0 hl=4 l= 489 cons: SEQUENCE
4:d=1 hl=4 l= 338 cons: SEQUENCE
8:d=2 hl=2 l= 1 prim: INTEGER :06
11:d=2 hl=2 l= 13 cons: SEQUENCE
13:d=3 hl=2 l= 9 prim: OBJECT :md5WithRSAEncryption
24:d=3 hl=2 l= 0 prim: NULL
26:d=2 hl=2 l= 91 cons: SEQUENCE
28:d=3 hl=2 l= 11 cons: SET
30:d=4 hl=2 l= 9 cons: SEQUENCE
32:d=5 hl=2 l= 3 prim: OBJECT :countryName
37:d=5 hl=2 l= 2 prim: PRINTABLESTRING :AU
41:d=3 hl=2 l= 19 cons: SET
43:d=4 hl=2 l= 17 cons: SEQUENCE
45:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
50:d=5 hl=2 l= 10 prim: PRINTABLESTRING :Queensland
62:d=3 hl=2 l= 26 cons: SET
64:d=4 hl=2 l= 24 cons: SEQUENCE
………
以其中的一行进行说明:
13:d=3 hl=2 l= 9 prim: OBJECT :md5WithRSAEncryption
13表示偏移量;d=3表示此项的深度;hl=2表示asn1头长度;l=9表示内容长度;prim:OBJECT表示ASN1类型;md5WithRSAEncryption表示oid。
示例如下:
openssl asn1parse –in c:\server.pem –out c:\server.cer
此命令除了显示上面内容外,并生成一个der编码的文件。
openssl asn1parse –in c:\server.pem –i
此命令显示上面的内容,但是有缩进。
openssl asn1parse –in c:\server.pem –i –offset 26
此命令从偏移量26开始分析,到结束。注意,26从前面命令的结果得到。
openssl asn1parse –in c:\server.pem –i –offset 13 –length 11
此命令从偏移量13进行分析,分析长度为11
openssl asn1parse –in c:\server.pem –i –dump
分析时,显示BIT STRING等的十六进制数据;
openssl asn1parse –in c:\server.pem –i –dlimit 10
分析时,显示BIT SRING的前10个十六进制数据。
openssl asn1parse –in c:\server.pem –i –strparse 11
此令分析一个SEQUENCE。
openssl asn1parse –in c:\server.pem –i –strparse 11 –offset 2 –length 11
根据偏移量和长度分析。
32.3 dgst
dgst用于数据摘要。
用法:
openssl dgst [-md5|-md4|-md2|-sha1|-sha|-mdc2|-ripemd160|-dss1 ] [-c] [-d ] [-hex] [-binary] [-out filename] [-sign filename] [-passin arg] [-verify filename] [-prverify filename]
[-signature filename ] [file...]
选项:
-d
打印调试信息。
-sign privatekeyfile
用privatekeyfile中的私钥签名。
-verify publickeyfile
用publickeyfile中的公钥验证签名。
-prverify privatekeyfile
用privatekeyfile中的私钥验证签名。
-keyform PEM | ENGINE
密钥格式,PEM格式或者采用Engine。
-hex
显示ASCII编码的十六进制结果,默认选项。
-binary
显示二进制数据。
-engine e
采用引擎e来运算。
-md5
默认选项,用md5进行摘要。
-md4
用md4摘要。
-md2
用md2摘要。
-sha1
用sha1摘要。
-sha
用sha摘要。
-sha256
用-sha256摘要。
-sha512
用sha512摘要。
-mdc2
用mdc2摘要。
-ripemd160
用ripemd160摘要。
示例:
openssl dgst c:\server.pem
运行此命令后文件的md5值摘要结果会在屏幕打印出来,此结果为摘要结果转换为ASCII码后的值:
MD5(c:\server.cer)= 4ace36445f5ab4bbcc2b9dd55e2f0e3a
openssl dgst –binary c:\server.pem
结果为二进制乱码。
openssl dgst –hex –c c:\server.pem
结果由:分开,如下:
MD5(c:\server.cer)= 4a:ce:36:44:5f:5a:b4:bb:cc:2b:9d:d5:5e:2f:0e:3a
openssl dgst –sign privatekey.pem –sha1 –keyform PEM –c c:\server.pem
将文件用sha1摘要,并用privatekey.pem中的私钥签名。
32.4 gendh
此命令用于生成DH参数。
选项:
-out file
输出结果到file指定的文件;如果不指定,结果显示在屏幕屏幕上;
-2
将2作为生成值,此为默认值;
-5
将5作为生成值;
-rand
指定随机数文件;
-engine e
采用Engine生成;
示例:
openssl gendh
openssl gendh -5 -out dh.pem 1024
32.5 passwd
生成各种口令密文。
用法:
openssl passwd [-crypt] [-1] [-apr1] [-salt string] [-in file] [-stdin] [-noverify] [-quiet] [-table] {password}
选项:
-crypt
默认选项,生成标准的unix口令密文。
-1
md5口令密文。
-apr1
Apache md5口令密文。
-salt string
加入由string指定的salt。
-in file
输入的口令文件,默认从stdin中读取。
-stdin
默认选项,从stdin读取口令。
-noverify
用户输入口令时,不验证。
-quiet
无警告。
-table
用户输入的口令和结果用缩进隔开。
-reverse
用户输入的口令和结果用缩进隔开,输出内容颠倒顺序。
示例:
(1) openssl passwd
(2) openssl passwd -1
(3) openssl passwd -1 –noverify
(4) openssl passwd –table –reverse -noverify
32.6 rand
生成随机数。
用法:
openssl rand [-out file] [-rand file(s)] [-base64] num
选项:
-out file
结果输出到file中。
-engine e
采用engine来生成随机数。
-rand file
指定随机数种子文件。
-base64
输出结果为BASE64编码数据。
num
随机数长度。
示例:
(1) openssl rand –base64 100
(2) openssl rand –base64 –out myr.dat 100
32.7 genrsa
生成RSA密钥。
用法:
openssl genrsa [-out filename] [-passout arg] [-des] [-des3] [-idea] [-f4] [-3] [-rand file(s)] [-engine id] [numbits]
选项:
-des
以des cbc模式加密密钥;
-des3
以3des cbc模式加密密钥;
-idea
以idea cbc模式加密密钥;
-aes128, -aes192, -aes256
cbc模式加密密钥;
-out file
输出文件;
-f4
指定E为0x1001;
-3
指定E为3;
-engine e
指定engine来生成RSA密钥;
-rand file
指定随机数种子文件;
numbits
密钥长度,如果不指定默认为512。
示例:
openssl genrsa -des3 –out prikey.pem –f4 1024
32.8 req
req命令主要用于生成和处理PKCS#10证书请求。
用法:
openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-text] [-pubkey] [-noout] [-verify] [-modulus] [-new] [-rand file(s)] [-newkey rsa:bits] [-newkey dsa:file] [-nodes] [-key filename] [-keyform PEM|DER] [-keyout filename] [-[md5|sha1|md2|mdc2]] [-config filename] [-subj arg] [-multivalue-rdn] [-x509] [-days n] [-set_serial n] [-asn1-kludge] [-newhdr] [-extensions section] [-reqexts section] [-utf8] [-nameopt] [-batch] [-verbose] [-engine id]
选项:
-out
指定输出文件名。
-outform DER|PEM
指定输出格式。
-newkey rsa:bits
用于生成新的rsa密钥以及证书请求。如果用户不知道生成的私钥文件名称,默认采用privkey.pem,生成的证书请求。如果用户不指定输出文件(-out),则将证书请求文件打印在屏幕上。生成的私钥文件可以用-keyout来指定。生成过程中需要用户输入私钥的保护口令以及证书申请中的一些信息。
-new
生成新的证书请求以及私钥,默认为1024比特。
-rand
指定随机数种子文件,比如有随机数文件rand.dat,用户输入:-rand file:rand.dat。
-config file
指定证书请求模板文件,默认采用openssl.cnf,需另行指定时用此选项。配置的写法可以参考openssl.cnf,其中有关于生成证书请求的设置。
-subj arg
用于指定生成的证书请求的用户信息,或者处理证书请求时用指定参数替换。生成证书请求时,如果不指定此选项,程序会提示用户来输入各个用户信息,包括国名、组织等信息,如果采用此选择,则不需要用户输入了。比如:-subj /CN=china/OU=test/O=abc/CN=forxy,注意这里等属性必须大写。
-multivalue-rdn
当采用-subj arg选项时,允许多值rdn名,比如arg参数写作:/CN=china/OU=test/O=abc/UID=123456+CN=forxy。
-reqexts ..
设置证书请求的扩展项,被设置的扩展项覆盖配置文件所指定的扩展项。
-utf8
输入字符为utf8编码,默认输入为ASCII编码。
-batch
不询问用户任何信息(私钥口令除外),采用此选项生成证书请求时,不询问证书请求当各种信息。
-noout
不输出证书请求。
-newhdr
在生成的PME证书请求文件的头尾添加“NEW”,有些软件和CA需要此项。
-engine e
指定硬件引擎。
-keyout
指定生成的私钥文件名称。
示例:
openssl req –new
openssl req –new –config myconfig.cnf
openssl req –subj /CN=cn/O=test/OU=abc/CN=forxy
openssl req -newkey rsa:1024
openssl req -newkey rsa:1024 -out myreq.pem –keyout myprivatekey.pem
openssl req -newkey rsa:1024 -out myreq.pem -keyout myprivatekey.pem -outform DER
-subject
输出证书请求者信息。
-modulus
输出证书请求的模数。
示例:openssl req -in myreq.pem -modulus –subject。
-pubkey
获取证书请求中的公钥信息。
示例:
openssl req -in myreq.pem -pubkey -out pubkey.pem
-in filename
输入的证书请求文件。
-text
打印证书请求或自签名证书信息。
-verify
验证证书请求。
示例:
openssl req -in zcp.pem -verify
-inform DER|PEM
指定输入的格式是DEM还是DER。
-x509
生成自签名证书。
-extensions ..
设置证书扩展项,设置的扩展项优先于配置文件指定的扩展项。
-set_serial
设置生成证书的证书序列号,比如 -set_serial 100或 -set_serial 0x100
-[md5|md4|md2|sha1|mdc2]
生成自签名证书时,指定摘要算法。
-passin
用户将私钥的保护口令写入一个文件,采用此选项指定此文件,可以免去用户输入口令的操作。比如用户将口令写入文件“pwd.txt”,输入的参数为:-passin file:pwd.txt。
-days
指定自签名证书的有效期限。
示例:
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem -days 300
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem -days 300 -text
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem -days 300 -text -md5
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem -days 300 -text -md5 –set_serial 0x100
openssl req -in myreq.pem -x509 -key myprivatekey.pem -out mycert.pem -days 300 -text -md5 –passin file:pwd.txt
这里的myreq.pem为PEM格式的文件,可以用-inform指定其格式。
-out filename
要输出的文件名。
-text
将CSR文件里的内容以可读方式打印出来。
-noout
不要打印CSR文件的编码版本信息。
-modulus
将CSR里面的包含的公共米要的系数打印出来。
-verify
检验请求文件里的签名信息。
示例:
生成ECC证书请求:
openssl ecparam -genkey -name secp160r1 -out ec160.pem
openssl req -newkey ec:ec160.pem
注意,如果由ecparam 中的 -name指定的密钥长度太短,将不能生成请求。因为md5或者sha1等的摘要长度对它来说太长了。
32.9 x509
X509命令是一个多用途的证书工具。它可以显示证书信息、转换证书格式、签名证书请求以及改变证书的信任设置等。
用法:
openssl x509 [-inform DER|PEM|NET] [-outform DER|PEM|NET] [-keyform DER|PEM] [-CAform DER|PEM] [-CAkeyform DER|PEM] [-in filename] [-out filename] [-serial] [-hash] [-subject_hash] [-issuer_hash] [-subject] [-issuer] [-nameopt option] [-email] [-startdate] [-enddate] [-purpose] [-dates] [-modulus] [-fingerprint] [-alias] [-noout] [-trustout] [-clrtrust] [-clrreject] [-addtrust arg] [-addreject arg] [-setalias arg] [-days arg] [-set_serial n] [-signkey filename] [-x509toreq] [-req] [-CA filename] [-CAkey filename] [-CAcreateserial] [-CAserial filename] [-text] [-C] [-md2|-md5|-sha1|-mdc2] [-clrext] [-extfile filename] [-extensions section] [-engine id]
选项:
-inform DER|PEM|NET
指定输入文件的格式,默认为PEM格式。
-outform DER|PEM|NET
指定输出文件格式,默认为PEM格式。
-keyform
指定私钥文件格式,默认为PEM格式。
-CAform
指定CA文件格式,默认为PEM格式。
-CAkeyform
指定CA私钥文件格式,默认为PEM格式。
-in filename
指定输入文件名。
-out filename
指定输出文件名。
-passin
指定私钥保护密钥来源,参考req说明,比如:-passin file:pwd.txt。
-serial
显示证书的序列号。
-subject_hash
显示持有者的摘要值。
-issuer_hash
显示颁发者的摘要值。
-hash
显示证书持有者的摘要值,同-subject_hash。
-subject
显示证书持有者DN。
-issuer
显示证书颁发者DN。
显示email地址。
-enddate
显示证书到期时间。
-startdate
显示证书的起始有效时间。
-purpose
显示证书用途。
-dates
显示证书的有效期。
-modulus
显示公钥模数。
-pubkey
输出公钥。
-fingerprint
打印证书微缩图。
-alias
显示证书别名。
-noout
不显示信息。
-ocspid
显示持有者和公钥的OCSP摘要值。
-trustout
输出可信任证书。
-clrtrust
清除证书附加项里所有有关用途允许的内容。
-clrreject
清除证书附加项里所有有关用途禁止的内容。
-addtrust arg
添加证书附加项里所有有关用途允许的内容。
-addreject arg
添加证书附加项里所有有关用途禁止的内容。
-setalias arg
设置证书别名。
-days arg
设置证书有效期。
-checkend arg
显示证书在给定的arg秒后是否还有效。
-signkey filename
指定自签名私钥文件。
-x509toreq
根据证书来生成证书请求,需要指定签名私钥,如:
openssl x509 -in ca.pem -x509toreq -signkey key.pem
-req
输入为证书请求,需要进行处理。
-CA arg
设置CA文件,必须为PEM格式。
-CAkey arg
设置CA私钥文件,必须为PEM格式。
-CAcreateserial
如果序证书列号文件,则生成。
-CAserial arg
由arg指定序列号文件。
-set_serial
设置证书序列号。
-text
打印证书信息。
-C
用C语言格式显示信息。
-md2|-md5|-sha1|-mdc2
指定使用的摘要算法,缺省为MD5。
-extfile filename
指定包含证书扩展项的文件名,如果没有,那么生成的证书将没有任何扩展项。
-clrext
删除证书所有的扩展项。当一个证书由另外一个证书生成时,可用此项。
-nameopt option
指定打印名字时采用的格式。
-engine e
采用硬件引擎e。
-certopt arg
当采用-text显示时,设置是否打印哪些内容,arg可用是:compatible、no_header、no_version、no_extensions和ext_parse等等,详细信息请参考x509命令的帮助文档。
示例:
openssl x509 -in cert.pem -noout -subject -nameopt RFC2253
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER
openssl x509 -req -in req.pem -extfile openssl.cnf -extensions v3_usr -CA cacert.pem -CAkey key.pem –Cacreateserial
32.10 version
version命令用来打印版本以及openssl其他各种信息。
用法:
version -[avbofp]
选项:
-a
打印所有信息。
-v
打印当前openssl的版本信息。
-b
打印当前版本的openssl是什么时候编译完成的。
-o
建立库时的各种与加密算法和机器字节有关的信息。
-f
编译openssl的编译选项。
-p
平台信息。
32.11 speed
speed命令用于测试库的性能。
用法:
openssl speed [-engine id] [md2] [mdc2] [md5] [hmac] [sha1] [rmd160]
[idea-cbc] [rc2-cbc] [rc5-cbc] [bf-cbc] [des-cbc] [des-ede3] [rc4]
[rsa512] [rsa1024] [rsa2048] [rsa4096] [dsa512] [dsa1024] [dsa2048]
[idea] [rc2] [des] [rsa] [blowfish]
选项:
-engine id
设置硬件引擎id。
-elapsed
测量采用实时时间,不是所用CPU时间,两者时间差异较大。
-mr
生成机器可读显示。
-multi n
并行允许n个测试。
示例:
openssl speed md5
32.12 sess_id
sess_id为SSL/TLS协议的session处理工具。
用法:
openssl sess_id [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename] [-text] [-noout] [-context ID]
选项:
-inform DER|PEM
指定输入格式是DER还是PEM;
-outform DER|PEM
指定输出格式是DER还是PEM;
-in filename
session信息的文件名;
-out filename
输出session信息的文件名;
-text
打印信息;
-cert
打印数字证书;
如果用户需要分析session信息,需要有一个session文件,用户可在程序中将SSL_SESSION写入文件,然后用本命令来分析。
32.13 s_server
s_server是openssl提供的一个SSL服务程序。使用此程序前,需要生成各种证书,可参考:第31章中第建立SSL测试环境一节。本命令可以用来测试ssl客户端,比如各种浏览器的https协议支持。
用法:
openssl s_server [-accept port] [-context id] [-verify depth] [-Verify
depth] [-cert filename] [-key keyfile] [-dcert filename] [-dkey key-
file] [-dhparam filename] [-nbio] [-nbio_test] [-crlf] [-debug] [-msg]
[-state] [-CApath directory] [-CAfile filename] [-nocert] [-cipher
cipherlist] [-quiet] [-no_tmp_rsa] [-ssl2] [-ssl3] [-tls1] [-no_ssl2]
[-no_ssl3] [-no_tls1] [-no_dhe] [-bugs] [-hack] [-www] [-WWW] [-HTTP]
[-engine id] [-rand file(s)]
选项:
-accept arg
监听的TCP端口,缺省为4433。
-context arg
设置ssl 上下文,不设置时采用缺省值。
-cert certname
服务使用的证书文件名。
-certform arg
证书文件格式,默认为PEM。
-keyform arg
私钥文件格式,默认为PEM。
-pass arg
私钥保护口令来源。
-msg
打印协议内容。
-timeout
设置超时。
-key keyfile
服务使用的私钥文件,由-cert指定的文件既可以包含证书,也可用包含私钥,此时,就不需要此选项。
-no_tmp_rsa
不生成临时RSA密钥。
-verify depth
设置证书验证深度。
-Verify arg
如果设置了此项为1,服务端必须验证客户端身份。
-CApath path
设置信任CA文件所在路径,此路径中的ca文件名采用特殊的形式:xxx.0。其中xxx为CA证书持有者的哈希值,可通过x509 -hash命令获得。
-CAfile file
指定CA证书文件。
-state
打印SSL握手状态。
-debug
打印更多的信息。
-nbio
不采用BIO。
-quiet
不打印输出信息。
-ssl2, -ssl3, -tls1
只采用某一种协。;
-no_ssl2, -no_ssl3, -no_tls1
不采用某种协议。
-www
返回给用户一个网页,内容为SSL握手的一些内容。
-WWW -HTTP
将某个文件作为网页发回客户端,例如client的URL请求是 https://myhost/page.html ,则把 ./page.html发回给client。如果不设置-www、-WWW 、-HTTP,客户端在终端输入任何字符,服务端都会响应同样的字符给客户端。
-rand file:file:...
设置随机数种子文件,SSL协议握手中会生成随机数,比如clienthello和serverhello消息。
-crlf
将用户在终端输入的换行回车转化成/r/n。
连接命令,这些输入不是程序运行选项,在程序运行过程中输入,如下:
q
中断当前连接,但不关闭服务。
Q
中断当前连接,退出程序。
r
重新协商。
R
重新协商,并且要求客户端证书。
P
在TCP层直接送一些明文,造成客户端握手错误并断开连接。
S
打印缓存的SESSION信息。
32.14 s_client
s_client为一个SSL/TLS客户端程序,与s_server对应,它不仅能与s_server进行通信,也能与任何使用ssl协议的其他服务程序进行通信。
用法:
openssl s_client [-connect host:port>] [-verify depth] [-cert filename]
[-key filename] [-CApath directory] [-CAfile filename] [-reconnect]
[-pause] [-showcerts] [-debug] [-msg] [-nbio_test] [-state] [-nbio]
[-crlf] [-ign_eof] [-quiet] [-ssl2] [-ssl3] [-tls1] [-no_ssl2]
[-no_ssl3] [-no_tls1] [-bugs] [-cipher cipherlist] [-engine id] [-rand file(s)]
选项:
-host host
设置服务地址.
-port port
设置服务端口,默认为4433。
-connect host:port
设置服务地址和端口。
-verify depth
设置证书验证深度。
-cert arg
设置握手采用的证书。
-certform arg
设置证书格式,默认为PEM。
-key arg
指定客户端私钥文件名,私钥可以与证书存放同一个文件中,这样,只需要-cert选项就可以了,不需要本选项。
-keyform arg
私钥格式,默认为PEM。
-pass arg
私钥保护口令来源,比如:-pass file:pwd.txt,将私钥保护口令存放在一个文件中,通过此选项来指定,不需要用户来输入口令。
-CApath arg
设置信任CA文件所在路径,此路径中的ca文件名采用特殊的形式:xxx.0,其中xxx为CA证书持有者的哈希值,它通过x509 -hash命令获得。
-CAfile arg
指定CA文件名。
-reconnect
重新连接,进行session重用。
-pause
每当读写数据时,sleep 1秒。
-showcerts
显示证书链。
-debug
额外输出信息。
-msg
打印协议消息。
-nbio_test
更多协议测试。
-state
打印SSL状态。
-nbio
不采用BIO。
-quiet
不显示客户端数据。
-ssl2、-ssl3、-tls1、-dtls1
指定客户端协议。
-no_tls1/-no_ssl3/-no_ssl2
不采用某协议。
-bugs
兼容老版本服务端的中的bug。
-cipher
指定加密套件。
-starttls protocol
protocol可以为smtp或pop3,用于邮件安全传输。
-rand file:file:...
设置随机数种子文件,SSL协议握手中会生成随机数,比如clienthello和serverhello消息中的随机数。
-crlf
将用户在终端输入的换行回车转化成/r/n。
32.15 rsa
Rsa命令用于处理RSA密钥、格式转换和打印信息。
用法:
openssl rsa [-inform PEM|NET|DER] [-outform PEM|NET|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-sgckey] [-des] [-des3] [-idea] [-text] [-noout] [-modulus] [-check] [-pubin] [-pubout] [-engine id]
选项:
-inform DER|PEM|NET
指定输入的格式,NET格式是与老的Netscape服务以及微软的IIS兼容的一种不太安全的格式。
-outform DER|PEM|NET
指定输出格式。
-in filename
输入文件名。
-passin arg
私钥保护密钥来源,比如:-passin file:pwd.txt。
-out filename
输出的文件名。
-des|-des3|-idea
指定私钥保护加密算法。
-text
打印密钥信息。
-noout
不打印任何信息。
-modulus
打印密钥模数。
-pubin
表明输入文件为公钥,默认的输入文件是私钥。
-pubout
表明输出文件为公钥。
-check
检查RSA私钥。
-engine id
指明硬件引擎。
示例:
生成明文私钥文件:
openssl genrsa -out key.pem
转换为DER编码:
openssl rsa -in key.pem -outform der -out key.der
将明文私钥文件转换为密码保护:
openssl rsa -inform der -in key.der -des3 -out enckey.pem
将公钥写入文件:
openssl rsa -in key.pem -pubout -out pubkey.pem
打印公钥信息:
openssl rsa -pubin -in pubkey.pem –text -modulus
显示私钥信息,保护密钥写在pwd.txt中
openssl rsa -in enckey.pem –passin file:pwd.txt
32.16 pkcs7
pkcs7命令用于处理DER或者PEM格式的pkcs#7文件。
用法:
openssl pkcs7 [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename] [-print_certs] [-text] [-noout] [-engine id]
选项:
-inform DER|PEM
输入文件格式,默认为PEM格式。
-outform DER|PEM
输出文件格式,默认为PEM格式。
-in filename
输入文件名,默认为标准输入。
-out filename
输出文件名, 默认为标准输出。
-print_certs
打印证书或CRL信息,在一行中打印出持有者和颁发者。
-text
打印证书相信信息。
-noout
不打印信息。
-engine id
指定硬件引擎。
示例:
把一个PKCS#7文件从PEM格式转换成DER格式:
openssl pkcs7 -in file.pem -outform DER -out file.der
打印文件所有证书
openssl pkcs7 -in file.pem -print_certs -out certs.pem
32.17 dsaparam
dsaparam命令用于生成和操作dsa参数。
用法:
openssl dsaparam [-inform DER|PEM] [-outform DER|PEM] [-in filename ] [-out filename] [-noout] [-text] [-C] [-rand file(s)] [-genkey] [-engine id] [numbits]
选项:
-inform DER|PEM
输入文件格式。
-outform DER|PME
输出文件格式。
-in filename
输入文件名。
-out filename
输出文件名。
-nout
不打印输出信息。
-text
打印内容信息。
-C
以C语言格式打印信息。
-rand file(s)
指定随机数种子文件,多个文件用冒号分开。
-genkey
生成dsa密钥。
-engine id
指定硬件引擎。
number
生成密钥时指定密钥大小。
示例:
生成DSA密钥:
openssl dsaparam -genkey 512 -out dsa.pem
打印密钥信息:
openssl dsaparam -in dsa.pem -text
openssl dsaparam -in dsa.pem -C
32.18 gendsa
gendsa根据DSA密钥参数生成DSA密钥,dsa密钥参数可用dsaparam命令生成。
用法:
openssl gendsa [-out filename] [-des] [-des3] [-idea] [-rand file(s)] [-engine id] [paramfile]
选项:
-out filename
指定输出文件。
-des|-des3|-idea|-aes128|-aes192|-aes256
指定私钥口令保护算法,如果不指定,私钥将被明文存放。
-rand file(s)
指定随机数种子文件,多个文件用冒号分开。
-engine id
指定硬件引擎。
paramfile
指定使用的DSA密钥参数文件。
示例:
生成DSA参数:
openssl dsaparam -genkey 512 -out dsaparam.pem
生成DSA密钥:
openssl gendsa -des3 -out encdsa.pem dsaparam.pem
32.19 enc
enc为对称加解密工具,还可以进行base64编码转换。
用法:
openssl enc -ciphername [-in filename] [-out filename] [-pass arg] [-e ] [-d ] [-a ] [-A] [-k password ] [-kfile filename] [-K key] [-iv IV] [-p] [-P] [-bufsize number] [-nopad] [-debug]
选项:
-ciphername
对称算法名字,此命令有两种适用方式:-ciphername方式或者省略enc直接用ciphername。比如,用des3加密文件a.txt:
openssl enc -des3 -e -in a.txt -out b.txt
openssl des3 -e -in a.txt -out b.txt
-in filename
输入文件,默认为标准输入。
-out filename
输出文件,默认为标准输出。
-pass arg
输入文件如果有密码保护,指定密码来源。
-e
进行加密操作,默认操作。
-d
进行解密操作。
-a
当进行加解密时,它只对数据进行运算,有时需要进行base64转换。设置此选项后,加密结果进行base64编码;解密前先进行base64解码。
-A
默认情况下,base64编码结果在文件中是多行的。如果要将生成的结果在文件中只有一行,需设置此选项;解密时,必须采用同样的设置,否则读取数据时会出错。
-k password
指定加密口令,不设置此项时,程序会提示用户输入口令。
-kfile filename
指定口令存放的文件。
-K key
输入口令是16进制的。
-iv IV
初始化向量,为16进制。
比如:openss des-cbc -in a.txt -out b.txt -a -A -K 1111 -iv 2222
-p
打印出使用的salt、口令以及初始化向量IV。
-P
打印使用的salt、口令以及IV,不做加密和解密操作。
-bufsize number
设置I/O操作的缓冲区大小,因为一个文件可能很大,每次读取的数据是有限的。
-debug
打印调试信息。
进行base64编码时,将base64也看作一种对称算法。
32.20 ciphers
显示支持的加密套件。
用法:
openssl ciphers [-v] [-ssl2] [-ssl3] [-tls1] [cipherlist]
选项:
-v
详细列出所有加密套件。包括ssl版本、密钥交换算法、身份验证算法、对称算法、摘要算法以及该算法是否可以出口。
-ssl3
只列出SSLv3使用的加密套件。
-ssl2
只列出SSLv2使用的加密套件。
-tls1
只列出TLSv1使用的加密套件。
cipherlist
此项为一个规则字符串,用此项能列出所有符合规则的加密套件,如果不加-v选项,它只显示各个套件名字;
示例:
openssl ciphers -v 'ALL:eNULL'
openssl ciphers -v '3DES:+RSA'
32.21 CA
ca命令是一个小型CA系统。它能签发证书请求和生成CRL。它维护一个已签发证书状态的文本数据库。
用法:
openssl ca [-verbose] [-config filename] [-name section] [-gencrl]
[-revoke file] [-crl_reason reason] [-crl_hold instruction] [-crl_com
promise time] [-crl_CA_compromise time] [-subj arg] [-crldays days]
[-crlhours hours] [-crlexts section] [-startdate date] [-enddate date]
[-days arg] [-md arg] [-policy arg] [-keyfile arg] [-key arg] [-passin
arg] [-cert file] [-in file] [-out file] [-notext] [-outdir dir]
[-infiles] [-spkac file] [-ss_cert file] [-preserveDN] [-noemailDN]
[-batch] [-msie_hack] [-extensions section] [-extfile section] [-engine
id] B[-utf8] [-multivalue-rdn]
选项:
-verbose
打印附加信息。
-config
指定配置文件,此配置文件中包含了证书存放路径、私钥和生成证书控制等信息。如果默认安装openssl,配置文件在/usr/local/ssl/路径下。我们可以先用apps目录下的CA.sh或者CA.pl脚本来 建立环境:sh CA.sh -newca,输入后回车就会生成一个demonCA的目录。
-name section
替换配置文件指定的default_ca所表示的内容。比如有openssl.cnf配置如下:
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = ./demoCA
certs = $dir/certs
crl_dir = $dir/crl
database = $dir/index.txt
[ my_defaultCA ]
dir = ./demoCA1
certs = $dir/certs
crl_dir = $dir/crl
database = $dir/index.txt
此时用户也可以采用选项来指定default_ca的值: -name my_defaultCA;
-gencrl
生成CRL文件。
-revoke file
撤销证书,file文件中包含了证书。
-crl_reason reason
设置CRLv2撤销原因,原因可以为:unspecified、keyCompromise、CACompromise、affiliationChanged、superseded、cessationOfOperation、certificateHold和removeFromCRL。这些原因区分大小写。
-crl_hold instruction
当crl撤销原因为certificateHold时(证书挂起),采用此项来指定用户行为。instruction的值可以是:holdInstructionNone、holdInstructionCallIssuer和holdInstructionReject。比如用选项: -crl_hold holdInstructionReject时,指明用户必须拒绝挂起的证书。
-crl_compromise time
当crl撤销原因为keyCompromise时(密钥泄露),设置密钥泄露时间time。Time 采用通用时间格式:YYYYMMDDHHMMSSZ。
-crl_CA_compromise time
当crl撤销原因为CACompromise时(CA被破坏),设置其时间,格式同-crl_compromise time。
-subj arg
持有者参数,如/CN=cn/O=test/OU=t/cn=forxy,忽略空格已经\后的字符。
-crldays days
设置下次CRL发布时间,days为下次发布时间距现在的天数。
-crlhours hours
设置下次CRL发布时间,hours为下次发布时间距现在的小时数。
-crlexts section
指定CRL扩展项。section为配置文件中的段,如果不提供crl扩展项段,则生成第一版本的crl,如果提供,则生成第二版本的crl。
-startdate date
设置证书生效起始时间,采用UTCTime格式:YYMMDDHHMMSSZ。
-enddate date
设置证书失效时间,采用UTCTime格式:YYMMDDHHMMSSZ。
-days arg
设置证书有效期,arg为天数。
-md arg
设置摘要算法:md5、sha、sha1 或 mdc2。
-policy arg
指定CA策略,arg为配置文件中的策略段,比如配置文件有如下信息:
[ ca ]
policy = policy_match
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
此时,采用的是policy_match策略(由policy=policy_match指定),用户可以设置采用policy_anything: -policy policy_anything。
-keyfile arg
指定签发证书的私钥文件。
-key arg
指定私钥解密口令。
-passin arg
指定私钥口令来源。
-cert file
指定CA文件。
-in file
输入的证书请求文件。
-out file
输出文件名。
-notext
在证书文件中,不输出文本格式的证书信息。
-outdir dir
设置输出路径。
-infiles ...
处理多个证书请求文件,此选项必须放在最后,此选项后的多个输入都被当作是证书请求文件。
-ss_cert file
指定需要由CA签发的自签名证书。
-preserveDN
证书中的DN顺序由配置文件来决定,如果设置此选项,则证书中DN的顺序与请求文件一致。
-noemailDN
如果证书请求者DN中包含邮件项,生成的证书也将会在持有者DN中包含。但是,较好的方式是将它放入到扩展项(altName)中去,如果设置了此选项,则进行这种操作。
-batch
批处理,不询问用户信息。
-msie_hack
支持很老的IE证书请求。
-extensions section
如果没有通过-extfile选项指定扩展项信息,section为配置文件中与扩展项有关的段,签发证书时添加section指定的扩展项(默认采用x509_extensions),如果不指定扩展,将生成第一版本的数字证书。
-engine id
指定硬件引擎。
-utf8
表明任何输入都必须是utf8编码(用户的终端输入和配置文件),默认为ASCII编码。
-multivalue-rdn
当采用-subj参数时,支持多值RDN,比如:DC=org/DC=OpenSSL/DC=users/UID=123456+CN=John Doe。
示例:下面所有命令在apps目录下运行:
1) 建CA
在apps目录下
sh ca.sh -newca 生成新CA,遇到提示,直接回车;
2) 生成证书请求
openssl req -new -out req.pem -keyout key.pem
openssl req -new -out req2.pem -keyout key2.pem
3) 签发证书
openssl ca -config /usr/local/ssl/openssl.cnf -name CA_default -days 365 -md sha1
-policy policy_anything -cert demoCA/cacert.pem -in req.pem -out cert1.pem -preserveDN -noemailDN -subj /CN=CN/O=JS/OU=WX/cn=myname -extensions myexts
openssl.cnf中相关内容如下:
[ myexts ]
basicConstraints=CA:FALSE
sComment = "OpenSSL Generated Certificate test"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
openssl ca -cert demoCA/cacert.pem -in req2.pem -out cert2.pem
4) 撤销一个证书
openssl ca -revoke cert2.pem
5) 生成crl,设置原因、挂起处理方法
openssl ca -gencrl -out crl.crl
openssl ca -gencrl -crl_reason keyCompromise -crl_compromise 20010101030303Z
-crl_hold holdInstructionReject -crl_CA_compromise 20020101030303Z
-crldays 10 -out crl2.crl
生成一个crl时需要一个crlnumber,它是一个文本文件,内容为数字,比如:03。
32.22 verify
证书验证工具、
用法:
openssl verify [-CApath directory] [-CAfile file] [-purpose purpose] [-untrusted file] [-help] [-issuer_checks] [-verbose] [-crl_check] [-engine e] [certificates]
选项
-CApath directory
信任的CA证书存放目录,它们的文件名为xxxx.0,其中xxxx为其证书持有者的摘要值,通过openssl x509 -hash -in cacert1.pem可以获取。
-CAfile file
CA证书,当其格式为PEM格式时,里面可以有多个CA证书。
-untrusted file
不信任的CA的证书,一个文件中可有多个不信任CA证书。
-purpose purpose
证书的用途,如果不设置此选项,则不会验证证书链。purpose的值可以是:sslclient、sslserver、nssslserver、smimesign和smimeencrypt。
-help
打印帮助信息。
-verbose
打印详细信息。
-issuer_checks
打印被验证书与CA证书间的关系。
-crl_check
验证CRL,可以将CRL内容写在CAfile指定的PEM文件中。
certificates
待验证的证书。
举例:
上一节,我们制作了两个证书:cert1.pem和cert2.pem,并撤销了cert2.pem,生成了一个crl文件。在此基础上,我们将crl文件的内容拷贝到demoCA/cacert.pem的结尾,然后做如下验证命令:
openssl verify -CAfile demoCA/cacert.pem -verbose -purpose sslclient -crl_check cert1.pem cert2.pem
会有如下信息:
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
cert1.pem: OK
cert2.pem: /C=CN/ST=JS/O=WX/OU=JN/CN=test2/emailAddress=test22@a.net
error 23 at 0 depth lookup:certificate revoked
出错信息用户请参考verify文档。
32.23 rsatul
rsautl为RSA工具。本指令能够使用RSA算法签名,验证身份, 加密/解密数据。
用法:
openssl rsautl [-in file] [-out file] [-inkey file] [-pubin] [-certin] [-sign] [-verify] [-encrypt] [-decrypt] [-pkcs] [-ssl] [-raw] [-hexdump] [-engine e] [-passin arg]
选项:
-in filename
指定输入文件名,缺省为标准输入。
-out filename
指定输入文件名, 缺省为标准输出。
-inkey file
输入私钥文件名。
-pubin
表明我们输入的是一个公钥文件,默认输入为私钥文件。
-certin
表明我们输入的是一个证书文件。
-sign
给输入的数据签名。
-verify
对输入的数据进行签名。
-encrypt
用公钥对输入数据加密。
-decrypt
用私钥对输入数据解密。
-pkcs, -oaep, -ssl, -raw
指定填充方式,上述四个值分别代表:PKCS#1.5(默认值)、 PKCS#1OAEP、SSLv2以及不填充。
-hexdump
用十六进制输出数据。
-engine e
指定硬件引擎。
-passin arg
指定私钥保护口令的来源,比如:-passin file:pwd.txt。
举例:
生成RSA密钥:
openssl genrsa -des3 -out prikey.pem
分离出公钥:
openssl rsa -in prikey.pem -pubout -out pubkey.pem
对文件签名:
openssl rsautl -sign -inkey prikey.pem -in a.txt -hexdump,文件a.txt的内容不能太长;
openssl rsautl -sign -inkey prikey.pem -in a.txt -out sig.dat
验证签名:
openssl rsautl -verify -inkey prikey.pem -in sig.dat,验证成功后打印出a.txt的内容;
公钥加密:
openssl rsautl -encrypt -pubin -inkey pubkey.pem -in a.txt -out b.txt
私钥解密:
openssl rsautl -decrypt -inkey prikey.pem -in b.txt
用证书中的公钥加密:
openssl rsautl -encrypt -certin -inkey cert1.pem -in a.txt
32.24 crl
crl工具,用于处里PME或DER格式的CRL文件。
用法:
openssl crl [-inform PEM|DER] [-outform PEM|DER] [-text] [-in filename] [-out filename] [-noout ] [-hash] [-issuer ] [-lastupdate ] [-nextupdate ] [-CAfile file ] [-CApath dir ]
选项:
-inform PEM|DER
输入文件格式,默认为PEM格式。
-outform PEM|DER
输出文件格式,默认为PEM格式。
-text
打印信息。
-in filename
指定输入文件名,默认为标准输入。
-out filename
指定输出文件名,默认为标准输出。
-noout
不打印CRL文件内容。
-hash
打印值。
-issuer
打印颁发者DN。
-lastupdate
上次发布时间。
-nextupdate
下次发布时间。
-CAfile file
指定CA文件。
-CApath dir
指定多个CA文件路径,每个CA文件的文件名为XXXX.0,XXXX为其持有者摘要值。
示例:
请先参考CA一节来生成一个CRL文件,再做如下操作:
openssl crl -in crl.crl -text -issuer -hash -lastupdate –nextupdate显示CRL信息;
验证CRL:
openssl crl -in crl.crl -CAfile demoCA/cacert.pem –noout
输出结果:
verify OK
下面通过指定CA文件路径来验证;
在demoCA目录下建立一个目录:CAfiles
openssl x509 -in demoCA/cacert.pem -hash 得到如下值:(比如)
86cc3989
在CAfiles下建立一个86cc3989.0文件,内容为demoCA/cacert.pem的内容
验证CRL:
openssl crl -in crl.crl -CApath demoCA/CAfiles –noout
32.25 crl2pkcs7
本命令根据CRL或证书来生成pkcs#7消息。
用法:
openssl crl2pkcs7 [-inform PEM|DER ] [-outform PEM|DER ] [-in filename ] [-out filename ] [-certfile filename ] [-nocrl ]
选项:
-inform PME|DER
CRL输入格式,默认为PEM格式。
-outform PME|DER
pkcs#7输出格式,默认为PEM格式。
-in filename
指定CRL文件,不设置此项则从标准输入中获取。
-out filename
指定输出文件,不设置此项则输入到标准输出。
-certfile filename
指定证书文件,PEM格式的证书文件可以包含多个证书,此选项可以多次使用。
-nocrl
不处理crl。一般情况下,输出文件中包含crl信息,设置此选项时,读取时忽略CRL信息,生成的信息不保护CRL信息。
示例:
openssl crl2pkcs7 -in crl.crl -out crlpkcs7.pem
openssl crl2pkcs7 -in crl.crl -certfile demoCA/ca cert.pem -out crlcertpkcs7.pem
openssl crl2pkcs7 -in crl.crl -certfile demoCA/ca cert.pem -out certpkcs7.pem –nocrl 上面生成的三个pkcs7文件包含的内容是不同的,crlpkcs7.pem只有crl信息;crlcertpkcs7.pem既有crl信息又有证书信息;certpkcs7.pem只有证书信息。
所以,不要被crl2pkcs7名字所迷惑,以为它只能将crl转换为pkcs7格式的信息。
32.26 errstr
本命令用于查询错误代码。
用法:
openssl errstr [-stats] <errno>
选项:
-stats
打印哈希表状态。
errno
错误号。
举例:
用户输入:
openssl req -config no.txt
有如下错误信息:
2220:error:02001002:system library:
openssl errstr 02001002
openssl errstr -stats 02001002
32.27 ocsp
在线证书状态工具。
用法:
openssl ocsp [-out file] [-issuer file] [-cert file] [-serial num] [-signer file] [-signkey file ] [-sign_other file ] [-no_certs] [-req_text] [-resp_text] [-text] [-reqout file] [-respout file] [-reqin file] [-respin file] [-nonce] [-no_nonce] [-url URL] [-host host:n] [-path] [-CApath dir] [-CAfile file] [-VAfile file] [-validity_period n] [-status_age n] [-noverify] [-verify_other file] [-trust_other] [-no_intern] [-no_signature_verify] [-no_cert_verify] [-no_chain] [-no_cert_checks] [-port num] [-index file] [-CA file] [-rsigner file] [-rkey file] [-rother file] [-resp_no_certs] [-nmin n] [-ndays n] [-resp_key_id] [-nrequest n]
选项:
-out file
指定输出文件,默认为标准输出。
-issuer file
指定当前颁发者证书,此选项可以用多次,file中的证书必须是PEM格式的。
-cert file
将file指定的证书添加到OCSP请求中去。
-serial num
将数字证书序列号添加到OCSP请求中去,num为证书序列号,0x开始表示是十六进制数据,否则是十进制数据,num可以是负数,前面用-表示。
-signer file, -signkey file
OCSP请求签名时,分别指定证书和私钥;如果只设置-signer选项,私钥和证书都从-signer指定的文件中读取;如果不设置这两项,OCSP请求将不会被签名。
-sign_other filename
签名的请求中添加其他证书。
-no_certs
签名的请求中不添加任何证书。
-req_text
打印OCSP请求信息。
-resp_text
打印OCSP响应信息。
-text
打印OCSP请求或者响应信息。
-reqout file
指定DER编码的OCSP请求输出文件。
-respout file
指定DER编码的OCSP响应输出文件。
-reqin file
指定输入的DER编码的OCSP请求文件。
-respin file
指定输入的DER编码的OCSP响应文件。
-nonce,-no_nonce
设置或不设置OCSP中的nonce扩展。
-url URL
指定OCSP服务的URL。
-host host:n
发送OCSP请求给服务,host为地址或域名n为端口号。
-path
OCSP请求所用的路径。
-CApath dir
可信CA文件目录,CA文件名请参考其他章节说明。
-CAfile file
可信CA文件,file可以包含多个CA证书。
-VAfile file
指定受信任的OCSP服务的证书,file可以包含多个证书;等价于-verify_certs 和-trust_other选项。
-validity_period n
设置OCSP响应中可接受的时间误差,n以秒为单位。默认可接受时间误差为5秒,OCSP认证中有关时间的说明请参考OCSP一章。
-status_age n
如果OCSP响应中没用提供响应的失效时间,则说明马上可以获取到新的响应信息;此时需要检查起始时间是否比当前时间晚n秒;默认情况不做此操作。
-noverify
不验证OCSP响应的签名和nonce。
-verify_other file
设置其他用于搜索OCSP响应者证书的文件。
-trust_other
由-verify_other指定的文件中包含了响应者的证书,用此选项时,不对响应者证书做额外的验证。当不能获取响应者证书的证书链或其根CA时,可用此选项,以保证验证能通过,即:使用了此选项后,verify_other所指定的OCSP服务者证书是可以信任的,即使那些证书有问题。
-no_intern
不搜索OCSP响应者的证书,采用此选项时,OCSP响应者的证书必须在-verify_certs或-VAfile中指定。
-no_signature_verify
不验证响应者的签名,用于测试。
-no_cert_verify
不验证响应者的证书,用于测试。
-no_chain
不验证响应者证书链。
-no_cert_checks
不验证响应者证书,不检查响应者是否有权来发布OCSP响应,用于测试。
-port num
OCSP服务端口。
-index file
指定证书状态索引文件。
-CA file
指定CA证书。
-rsigner file
指定用于签发OCSP响应的证书。
-rkey file
指定用于签发OCSP响应的私钥文件。
-rother file
将其他证书添加到OCSP响应中。
-resp_no_certs
OCSP响应中不包含证书。
-nmin n
距离下次更新时间,n以分钟为单位。
-ndays n
距离下次更新时间,n以天为单位。
-resp_key_id
用响应者的私钥ID来标记OCSP响应,默认为响应者证书的持有者。
-nrequest n
OCSP服务最大响应个数,默认无限制。
举例:
1)请先用req和ca命令生成OCSP服务证书和私钥,下面的OCSP服务证书为ocspservercert.pem,OCSP服务签名私钥为ocspserverkey.pem
2)生成OCSP请求:
openssl ocsp -issuer demoCA/cacert.pem -cert cert.pem -cert -cert2.pem -reqout ocspreq.der
3)打印OCSP请求信息:
openssl ocsp -reqin ocspreq.der -text
4)启动OCSP服务:
openssl ocsp -ndays 1 -index demoCA/index.txt -port 3904 -CA demoCA/cacert.pem -text -rkey ocspserverkey.pem -rsigner ocspservercert.pem
5)请求OCSP响应:
openssl ocsp -issuer demoCA/cacert.pem -url http://127.0.0.1:3904 -reqin ocspreq.der -VAfile ocspservercert.pem -respout resp.der
打印如下信息:
Response verify OK
或者:openssl ocsp -issuer demoCA/cacert.pem -url http://127.0.0.1:3904 -cert cert.pem -cert cert2.pem -VAfile ocspservercert.pem -respout resp.der
打印如下信息:
Response verify OK
cert.pem: unknown
This Update: Mar 9 16:50:12 2007 GMT
Next Update: Mar 10 16:50:12 2007 GMT
cert2.pem: revoked
This Update: Mar 9 16:50:12 2007 GMT
Next Update: Mar 10 16:50:12 2007 GMT
Revocation Time: Mar 9 13:56:51 2007 GMT
5) 根据响应的文件来验证:
openssl ocsp -respin resp.der -VAfile ocspserverc ert.pem -text
32.28 pkcs12
pkcs12文件工具,能生成和分析pkcs12文件。
用法:
openssl pkcs12 [-export] [-chain] [-inkey filename] [-certfile filename] [-CApath arg] [-CAfile arg] [-name name] [-caname name] [-in filename] [-out filename] [-noout] [-nomacver] [-nocerts] [-clcerts] [-cacerts] [-nokeys] [-info] [-des] [-des3] [-aes128] [-aes192] [-aes256] [-idea] [-nodes] [-noiter] [-maciter] [-twopass] [-descert] [-certpbe alg] [-keypbe alg] [-keyex] [-keysig] [-password arg] [-passin arg] [-passout arg] [-rand file(s)] [-engine e]
选项:
-export
输出pkcs12文件。
-chain
添加证书链。
-inkey filename
指定私钥文件,如果不用此选项,私钥必须在-in filename中指定。
-certfile filename
添加filename中所有的文件。
-CApath arg
指定CA文件目录。
-CApath arg
指定CA文件。
-name name
指定证书和私钥的友好名。
-caname name
指定CA友好名,可以多次使用此选项。
-in filename
指定私钥和证书读取的文件,必须为PEM格式。
-out filename
指定输出的pkcs12文件,默认为标准输出。
-noout
不输出信息。
-nomacver
读取文件时不验证MAC。
-nocerts
不输出证书。
-clcerts
只输出客户证书,不包含CA证书。
-cacerts
只输出CA证书,不包含CA证书。
-nokeys
不输出私钥。
-info
输出pkcs12结构信息。
-des3,-aes128 ,-aes192,[-aes256,[-idea
私钥加密算法;。
-nodes
不对私钥加密。
-noiter
不多次加密。
-maciter
加强完整性保护,多次计算MAC。
-twopass
需要用户分别指定MAC口令和加密口令。
-descert
用3DES加密pkcs12文件,默认为RC2-40。
-certpbe alg
指定证书加密算法,默认为RC2-40。
-keypbe alg
指定私钥加密算法,默认为3DES。
-keyex
设置私钥只能用于密钥交换。
-keysig
设置私钥只能用于签名。
-password arg
指定导入导出口令来源。
-passin arg
输入文件保护口令来源。
-passout arg
指定所有输出私钥保护口令来源。
-rand file(s)
指定随机数种子文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”。
-engine e
指定硬件引擎。
举例:
1)生成pkcs12文件,但不包含CA证书:
openssl pkcs12 -export -inkey ocspserverkey.pem -in ocspservercert.pem -out ocspserverpkcs12.pfx
2) 生成pcs12文件,包含CA证书:
openssl pkcs12 -export -inkey ocspserverkey.pem -in ocspservercert.pem -CAfile demoCA/cacert.pem -chain -out ocsp1.pfx
3) 将pcks12中的信息分离出来,写入文件:
openssl pkcs12 –in ocsp1.pfx -out certandkey.pem
4) 显示pkcs12信息:
openssl pkcs12 –in ocsp1.pfx -info
32.29 pkcs8
pkcs8格式的私钥转换工具。
用法:
openssl pkcs8 [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-topk8] [-noiter] [-nocrypt] [-nooct] [-embed] [-nsdb] [-v2 alg] [-v1 alg] [-engine id]
选项:
-inform PEM|DER
输入文件格式。
-outform PEM|DER
输出文件格式。
-in filename
输入文件。
-passin arg
输入文件口令保护来源。
-out filename
指定输出文件。
-passout arg
输出文件口令保护来源。
-topk8
输出pkcs8文件。
-noiter
MAC保护计算次数为1。
-nocrypt
加密输入文件,输出的文件不被加密。
-nooct
不采用八位组表示私钥。
-embed
采用嵌入式DSA参数格式。
-nsdb
采用Netscape DB的DSA格式。
-v2 alg
采用PKCS#5 v2.0,并指定加密算法,可以是des、des3和rc2,推荐des3。
-v1 alg
采用PKCS#5 v1.5或pkcs12,并指定加密算法,可采用算法包括:
PBE-MD2-DES、PBE-MD5-DES、PBE-SHA1-RC2-64、PBE-MD2-RC2-64、PBE-MD5-RC2-64、PBE-SHA1-DES、PBE-SHA1-RC4-128、PBE-SHA1-RC4-40、PBE-SHA1-3DES、PBE-SHA1-2DES、PBE-SHA1-RC2-128和PBE-SHA1-RC2-40。
-engine i
指定硬件引擎。
示例:
1) 将私钥文件转换为pkcs8文件:
openssl pkcs8 -in ocspserverkey.pem -topk8 -out ocspkcs8key.pem
2) pkcs8中的私钥以明文存放:
openssl pkcs8 -in ocspserverkey.pem -topk8 -nocrypt -out ocspkcs8key.pem
32.30 s_time
s_time是openss提供的SSL/TLS性能测试工具,用于测试SSL/TSL服务。
用法:
openssl s_time [-connect host:port] [-www page] [-cert filename] [-key filename] [-CApath directory] [-CAfile filename] [-reuse] [-new] [-verify depth] [-nbio] [-time seconds] [-ssl2] [-ssl3] [-bugs] [-cipher cipherlist]
用法:
-connect host:port
指定服务,默认为本机的4433端口。
-www page
指定获取的web网页。
-cert filename
指定证书。
-key filename
指定私钥。
-CApath directory
指定CA文件目录。
-CAfile filename
指定CA文件。
-reuse
session重用。
-new
新建链接。
-verify depth
设置验证深度。
-nbio
不采用BIO。
-time seconds
指定搜集数据的秒数,默认30秒。
-ssl2,-ssl3
采用的SSL协议。
-bugs
开启SSL bug兼容。
-cipher cipherlist
指定加密套件。
示例:
1) 启动s_server服务:
openssl s_server -cert sslservercert.pem -key sslserverkey.pem -ssl3
2) 启动s_time
openssl s_time -cert sslclientcert.pem -key sslclientkey.pem -CAfile demoCA/cacert.pem -ssl3
32.31 dhparam和dh
Dhparam为dh参数操作和生成工具。dh命令与dhparam用法大致一致,下面只给出了dhparam的说明。
用法:
openssl dhparam [-inform DER|PEM] [-outform DER|PEM] [-in filename] [-out filename] [-dsaparam] [-noout] [-check] [-text] [-C] [-2] [-5] [-rand file(s)] [-engine id] [numbits]
选项:
-inform DER|PEM
输入文件格式,DER或者PEM格式。
-outform DER|PEM
输出格式。
-in filename
读取DH参数的文件,默认为标准输入。
-out filename
dh参数输出文件,默认为标准输出。
-dsaparam
生成DSA参数,并转换为DH格式。
-noout
不输出信息。
-text
打印信息。
-check
检查dh参数。
-C
以C语言风格打印信息。
-2,-5
指定2或5为发生器,默认为2,如果指定这些项,输入DH参数文件将被忽略,自动生成DH参数。
-rand files
指定随机数种子文件。
-engine id
指定硬件引擎。
numbit
指定素数bit数,默认为512。
示例:
1) openssl dhparam –out dhparam.pem -text 512
生成内容如下:
Diffie-Hellman-Parameters: (512 bit)
prime:
00:8f:18:1b:4f:7a:74:e1:89:42:e6:99:0f:15:4e:
72:ad:ca:7b:fb:68:ef:85:7b:16:a8:5b:85:01:82:
dd:db:57:1f:c5:86:89:fa:16:10:6e:d0:05:2b:15:
e2:87:98:0e:53:f2:c8:18:f9:5b:7e:4d:ce:9b:6d:
3f:23:11:52:63
generator: 2 (0x2)
-----BEGIN DH PARAMETERS-----
MEYCQQCPGBtPenThiULmmQ8VTnKtynv7aO+FexaoW4UBgt3bVx/Fhon6FhBu0AUr
FeKHmA5T8sgY+Vt+Tc6bbT8jEVJjAgEC
-----END DH PARAMETERS-----
2) 检查生成的DH参数
openssl dhparam -in dhparam.pem -text -check
32.32 ecparam
椭圆曲线密钥参数生成及操作。
用法:
openssl ecparam [-inform DER|PEM] [-outform DER|PEM] [-in filename] [-out filename] [-noout] [-text] [-C] [-check] [-name arg] [-list_curve] [-conv_form arg] [-param_enc arg] [-no_seed] [-rand file(s)] [-genkey] [-engine id]
用法:
-inform DER|PEM
输入文件格式。
-outform DER|PEM
输出文件格式。
-in filename
输入文件。
-out filename
输出文件。
-noout
不打印信息。
-text
打印信息。
-C
以C语言风格打印信息。
-check
检查参数。
-name arg
采用短名字。
-list_curves
打印所有可用的短名字。
-conv_form arg
指定信息存放方式,可以是compressed、uncompressed或者hybrid,默认为compressed。
-param_enc arg
指定参数编码方法,可以是named_curve和explicit,默认为named_curve。
-no_seed
如果-param_enc指定编码方式为explicit,不采用随机数种子。
-rand file(s)
指定随机数种子。
-genkey
生成密钥。
-engine id
指定硬件引擎。
示例:
openssl ecparam -list_curves
openssl ecparam -name secp112r1 -genkey –text
openssl ecparam -genkey -name secp160r1 -out ec160.pem
openssl req -newkey ec:ec160.pem
32.33 ec
椭圆曲线密钥处理工具。
用法:
openssl ec [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-des] [-des3] [-idea] [-text] [-noout] [-param_out] [-pubin] [-pubout] [-conv_form arg] [-param_enc arg] [-engine id]
选项:
-inform PEM|DER
输入文件格式。
-outform PEM|DER
输出文件格式。
-in filename
输入文件名。
-passin arg
私钥保护口令来源。
-out filename
输出文件名。
-passout arg
输出文件保护口令来源。
-des,-des3,-idea
私钥保护算法。
-noout
不输出信息。
-param_out
输出参数。
-pubin
输入的是公钥。
-pubout
输出公钥。
-conv_form arg
指定信息存放方式,可以是compressed、uncompressed或者hybrid,默认为compressed。
-param_enc arg
指定参数编码方法,可以是named_curve和explicit,默认为named_curve。
-engine id
指定硬件引擎。
示例:
1) 生成ec私钥
openssl ecparam -genkey -name secp112r1 -out eckey.pem -text
2) 转换为DER编码
openssl ec -outform der -in eckey.pem -out eckey.der
3) 给私钥进行口令保护
openssl ec -in eckey.pem -des -out enceckey.pem
4) 将公钥写入文件
openssl ec -in eckey.pem -pubout -out ecpubkey.pem
5) 显示密钥信息
openssl ec -in eckey.pem –text
openssl ec -in ecpubkey.pem -pubin –text
6) 转换为pkcs8格式
openssl pkcs8 -topk8 -in eckey.pem -out eckeypk8.pem
32.34 dsa
dsa命令用于处理DSA密钥、格式转换和打印信息。
用法:
openssl dsa [-inform PEM|DER] [-outform PEM|DER] [-in filename]
[-passin arg] [-out filename] [-passout arg] [-des] [-des3] [-idea]
[-text] [-noout] [-modulus] [-engine id]
选项:
-inform
输入dsa密钥格式,PEM或DER。
-outform
输出文件格式,PEM或DER。
-in filename
输入的DSA密钥文件名。
-passin arg
指定私钥包含口令存放方式。比如用户将私钥的保护口令写入一个文件,采用此选项指定此文件,可以免去用户输入口令的操作。比如用户将口令写入文件“pwd.txt”,输入的参数为:-passin file:pwd.txt。
-out filename
指定输出文件名。
-passout arg
输出文件口令保护存放方式。
-des -des3 -idea
指定私钥保护加密算法。
-text
打印所有信息。
-noout
不打印信息。
-modulus
打印公钥信息。
-engine id
指定引擎。
示例:
1) 生成dsa参数文件
openssl dsaparam -out dsaparam.pem 1024
2) 根据dsa参数文件生成dsa密钥
openssl gendsa -out dsakey.pem dsaparam.pem
3) 将PME密钥转换为DER密钥
openssl dsa -in dsakey.pem -outform DER -out dsakeyder.pem
4) 打印公钥信息
openssl dsa -in dsakey.pem –modulus
5) 打印所有信息
openssl dsa -in dsakey.pem –text
6) 将dsa密钥加密存放
openssl dsa -in dsakey.pem -des -out enckey.pem
32.35 nseq
本命令用于多个证书与netscape证书序列间相互转化。
用法:openssl nseq [-in filename] [-out filename] [-toseq]
选项:
-in filename
输入文件名。
-out filename
输出文件名。
-toseq
含此项时将多个证书转化为netscape证书序列,否则将netscape证书序列转化为多个证书。
示例:
1) 将多个证书写成一个文件
cat newcert.pem > 1.pem
cat cacert.pem >> 1.pem
2) 将多个证书转化为netscape证书序列
openssl nseq -in 1.pem -toseq -out 2.pem
3) 将netscape证书序列转化为多个证书
openssl nseq -in 2.pem -out 3.pem
32.36 prime
检查一个数是否为素数。示例如下:
openssl prime 79
openssl prime -hex 4F
32.37 smime
S/MIME工具,用于处理S/MIME邮件,它能加密、解密、签名和验证S/MIME消息。
用法:
openssl smime [-encrypt] [-decrypt] [-sign] [-verify] [-pk7out] [-des]
[-des3] [-rc2-40] [-rc2-64] [-rc2-128] [-in file] [-certfile file]
[-signer file] [-recip file] [-inform SMIME|PEM|DER] [-passin arg]
[-inkey file] [-out file] [-outform SMIME|PEM|DER] [-content file] [-to
addr] [-from ad] [-subject s] [-text] [-rand file(s)] [cert.pem]...
主要选项:
-encrypt
加密数据。
-decrypt
解密数据。
-sign
签名数据。
-verify
验证数据。
-in
输入文件名。
-out
输出文件名。
-pk7out
输出pkcs7格式的文件。
-des -des3 -rc2-40 –rc2-60 –rc2-128
对称算法。
-signer file
指定签名者证书。
-recip file
指定接收者证书。
-inform
输入文件格式。
-passin arg
私钥保护口令来源。
-inkey file
私钥文件。
-outform
输出文件格式。
示例:
1) 用对方的证书来加密消息
openssl smime -encrypt -in mail.pem -out enced.pem newcert.pem
openssl smime -encrypt -in mail.pem -out enced.pem -des newcert.pem
2) 用私钥解密消息
openssl smime -decrypt -in enced.pem -out mymail.pem -inkey newkey.pem
openssl smime -decrypt -in enced.pem -out mymail.pem -inkey newkey.pem -des
3)用自己的私钥签名数据
openssl smime -sign -in mail.pem -out signedmail.pem -inkey newkey.pem -signer newcert.pem
4) 验证签名
openssl smime -verify -in signedmail.pem -CAfile newcert.pem -signer newcert.pem
此处newcert是一个自签名证书,如果不是自签名证书用如下命令:
openssl smime -verify -in signedmail.pem -CAfile demoCA/cacert.pem -signer newcert2.pem
5) 将数据转化为pkcs7格式
openssl smime -pk7out -in signedmail.pem -out p7.pem
版本:
1.0 原始版本
1.1 补充了椭圆曲线 当前版本
1.2 补充大数
1.3 添加标准;