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 摘要算法

摘要算法是一種能產生特殊輸出格式的算法,這種算法的特點是:無論用戶輸入什么長度的原始數據,經過計算后輸出的密文都是固定長度的,這種算法的原理是根據一定的運算規則對原數據進行某種形式的提取,這種提取就是摘要,被摘要的數據內容與原數據有密切聯系,只要原數據稍有改變,輸出的摘要便完全不同,因此,基於這種原理的算法便能對數據完整性提供較為健全的保障。但是,由於輸出的密文是提取原數據經過處理的定長值,所以它已經不能還原為原數據,即消息摘要算法是不可逆的,理論上無法通過反向運算取得原數據內容,因此它通常只能被用來做數據完整性驗證

如今常用的消息摘要算法經歷了多年驗證發展而保留下來的算法已經不多,這其中包括MD2MD4MD5SHASHA-1/256/383/512

常用的摘要算法主要有MD5SHA1D5的輸出結果為16字節,sha1的輸出結果為20字節。

 

1.3 公鑰算法

在公鑰密碼系統中,加密和解密使用的是不同的密鑰,這兩個密鑰之間存在着相互依存關系:即用其中任一個密鑰加密的信息只能用另一個密鑰進行解密。這使得通信雙方無需事先交換密鑰就可進行保密通信。其中加密密鑰和算法是對外公開的,人人都可以通過這個密鑰加密文件然后發給收信者,這個加密密鑰又稱為公鑰;而收信者收到加密文件后,它可以使用他的解密密鑰解密,這個密鑰是由他自己私人掌管的,並不需要分發,因此又成稱為私鑰,這就解決了密鑰分發的問題。

主要的公鑰算法有:RSADSADHECC

1RSA算法

當前最著名、應用最廣泛的公鑰系統RSA是在1978年,由美國麻省理工學院(MIT)RivestShamirAdleman在題為《獲得數字簽名和公開鑰密碼系統的方法》的論文中提出的。它是一個基於數論的非對稱(公開鑰)密碼體制,是一種分組密碼體制。其名稱來自於三個發明者的姓名首字母。 它的安全性是基於大整數素因子分解的困難性,而大整數因子分解問題是數學上的著名難題,至今沒有有效的方法予以解決,因此可以確保RSA算法的安全性。RSA系統是公鑰系統的最具有典型意義的方法,大多數使用公鑰密碼進行加密和數字簽名的產品和標准使用的都是RSA算法。

RSA算法是第一個既能用於數據加密也能用於數字簽名的算法,因此它為公用網絡上信息的加密和鑒別提供了一種基本的方法。它通常是先生成一對RSA 密鑰,其中之一是保密密鑰,由用戶保存;另一個為公開密鑰,可對外公開,甚至可在網絡服務器中注冊,人們用公鑰加密文件發送給個人,個人就可以用私鑰解密接受。為提高保密強度,RSA密鑰至少為500位長,一般推薦使用1024

RSA算法是R.Rivest、A.Shamir和L.Adleman於1977年在美國麻省理工學院開發,於1978年首次公布。

RSA公鑰密碼算法是目前網絡上進行保密通信和數字簽名的最有效的安全算法之一。RSA算法的安全性基於數論中大素數分解的困難性,所以,RSA需采用足夠大的整數。因子分解越困難,密碼就越難以破譯,加密強度就越高。 

其算法如下:

  1. 選擇兩質數p、q
  2. 計算n = p * q
  3. 計算n的歐拉函數Φ(n) = (p - 1)(q - 1)
  4. 選擇整數e,使e與Φ(n)互質,且1 < e < Φ(n)
  5. 計算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位的密鑰。

2DSA算法

DSADigital Signature Algorithm,數字簽名算法,用作數字簽名標准的一部分),它是另一種公開密鑰算法,它不能用作加密,只用作數字簽名。DSA使用公開密鑰,為接受者驗證數據的完整性和數據發送者的身份。它也可用於由第三方去確定簽名和所簽數據的真實性。DSA算法的安全性基於解離散對數的困難性,這類簽字標准具有較大的兼容性和適用性,成為網絡安全體系的基本構件之一。  

DSA簽名算法中用到了以下參數: 

pL位長的素數,其中L5121024且是64的倍數。 

q160位長且與p-1互素的因子 ,其中h是小於p-1並且滿足 大於1的任意數。  

x是小於q的數。  

另外,算法使用一個單向散列函數Hm)。標准指定了安全散列算法(SHA)。三個參數pqg是公開的,且可以被網絡中所有的用戶公有。私人密鑰是x,公開密鑰是y  

對消息m簽名時:  

1 發送者產生一個小於q的隨機數k  

2 發送者產生: 

rs就是發送者的簽名,發送者將它們發送給接受者。  

3 接受者通過計算來驗證簽名:

如果v=r,則簽名有效。 

3Diffie-Hellman密鑰交換

DH算法是W.DiffieM.Hellman提出的。此算法是最早的公鑰算法。它實質是一個通信雙方進行密鑰協定的協議:兩個實體中的任何一個使用自己的私鑰和另一實體的公鑰,得到一個對稱密鑰,這一對稱密鑰其它實體都計算不出來。DH算法的安全性基於有限域上計算離散對數的困難性。離散對數的研究現狀表明:所使用的DH密鑰至少需要1024位,才能保證有足夠的中、長期安全。

(4) 橢圓曲線密碼體制(ECC)

1985年,N. KoblitzV. Miller分別獨立提出了橢圓曲線密碼體制(ECC)其依據就是定義在橢圓曲線點群上的離散對數問題的難解性。 

為了用橢圓曲線構造密碼系統,首先需要找到一個單向陷門函數,橢圓曲線上的數量乘就是這樣的單向陷門函數。

橢圓曲線的數量乘是這樣定義的:設E為域K上的橢圓曲線,GE上的一點,這個點被一個正整數k相乘的乘法定義為 k個G相加,因而有

kG = G + G + … + G       (共有k個G)

若存在橢圓曲線上的另一點≠ G,滿足方程kG = N。容易看出,給定k和G,計算N相對容易。而給定NG,計算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-mdc2no-cast no-rc2no-rc5no-ripemdno-rc4 no-des no-md2no-md4no-idea no-aesno-bfno-errno-dsano-dhno-ecno-hwno-asmno-krb5no-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)

3make test (可選)

4make install

完成后,openssl會被安裝到/usr/local/openssl目錄,包括頭文件目錄include、可執行文件目錄binman在線幫助、庫目錄lib以及配置文件目錄(ssl)

2.2.2 windows編譯與安裝

安裝步驟如下:

1 安裝VC6.00.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目錄),本目錄下的函數對各種輸入輸出進行抽象,包括文件、內存、標准輸入輸出、socketSSL協議等。

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 sessionasn.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/aescrypto/bfcrypto/castccrypto/ompcrypto/des等目錄)

23) 非對稱算法(crypto/dhcrypto/dsacrypto/eccrypto/ecdh)

24) 摘要算法(crypto/md2crypto/md4crypto/md5crypto/sha)以及密鑰交換/認證算法(crypto/dh crypto/krb5)

ssl庫所有源代碼在ssl目錄下,包括了sslv2sslv3tlsv1DTLS的源代碼。各個版本基本上都有客戶端源碼(*_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) 學會使用opensslasn.1編解碼

openssl中很多函數和源碼都涉及到asn1編解碼,比如數字證書申請、數字證書、crlocsppkcs7pkcs8pkcs12等。

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_sortsk_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函數。

10void 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函數

12unsigned long lh_num_items(const LHASH *lh)

源文件:lhash.c

功能:獲取哈希表中元素的個數。

13void lh_stats_bio(const LHASH *lh, BIO *out)

源文件:lh_stats.c

功能:輸出哈希表統計信息到BIO

14void lh_stats(const LHASH *lh, FILE *fp)

源文件:lh_stats.c

功能:打印哈希表的統計信息,此函數調用了lh_stats_bio。

15unsigned 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可以讓用戶動態加載動態庫來進行函數調用。各個平台下加載動態庫的函數是不一樣的,opensslDSO對各個平台台下的動態庫加載函數進行了封裝,增加了源碼的可移植性。OpensslDSO功能主要用於動態加載壓縮函數(ssl協議)和engine(硬件加速引擎)OpensslDSO功能除了封裝基本的功能外還有其他輔助函數,主要用於解決不同系統下路徑不同的表示方式以及動態庫全名不一樣的問題。比如windows系統下路徑可以用“\\”和“/”表示,而linux下只能使用“/”;windows下動態庫的后綴為.dlllinux下動態庫名字一般為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的時候置1DSO_up_ref時加1,DSO_free時減1

當調用DSO_free,只有當前的references1時才真正釋放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(linuxunix)

DSO_FLAG_NAME_TRANSLATION_EXT_ONLY

加載的文件名會加上lib串,比如用戶加載eay32,真正加載時會加載libeay32(適用於linuxunix)

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_newBIO_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類型的抽象封裝,包括:內存、文件、日志、標准輸入輸出、socketTCP/UDP)、加/解密、摘要和ssl通道等。Openssl BIO通過回調函數為用戶隱藏了底層實現細節,所有類型的bio的調用大體上是類似的。Bio中的數據能從一個BIO傳送到另外一個BIO或者是應用程序。

7.2 數據結構

BIO數據結構主要有2個,在crypto/bio.h中定義如下:

1BIO_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等函數來設置。

2BIO

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關聯一個文件指針時,該標記則置1socket BIO中通過BIO_set_fd關聯一個鏈接時設置該標記為1

shutdownBIO關閉標記,當該值不為0時,釋放資源;改值可以通過控制函數來設置。

flags:有些BIO實現需要它來控制各個函數的行為。比如文件BIO默認該值為BIO_FLAGS_UPLINK,這時文件讀操作調用UP_fread函數而不是調用fread函數。

retry_reason:重試原因,主要用在socketssl BIO 的異步阻塞。比如socket bio中,遇到WSAEWOULDBLOCK錯誤時,openssl告訴用戶的操作需要重試。

num:該值因具體BIO而異,比如socket BIOnum用來存放鏈接字。

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_readBIO_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中。函數一般以CONFNCONF(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通過使用摘要算法來生成隨機數,可用的摘要算法有:sha1md5mdc2md2。具體采用那種摘要算法在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,則說明此時用戶不應生成隨機數,需要調用seedadd函數來添加熵值。

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.cca.cocsp.copenssl文本數據庫典型的例子為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,但是年齡合法,那么該記錄能插入到年齡列對應的哈希表中,而不能插入名字列對應的哈希表。

errorarg1arg2arg_row用於存放錯誤信息。

10.3 函數說明

1 TXT_DB *TXT_DB_read(BIO *in, int num)

用於從BIO中讀入數據,轉換為TXT_DBnum用於明確指明列數,本函數不建立哈希表。

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_DBhash為一行數據的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;

};

各項意義如下:

dBN_ULONG(應系統而異,win32下為4個字節)數組指針首地址,大數就存放在這里面,不過是倒放的。比如,用戶要存放的大數為12345678000(通過BN_bin2bn放入),則d的內容如下:0x30 0x30 0x30 0x38 0x37 0x36 0x35 0x34 0x33 0x32 0x31

top:用來指明大數占多少個BN_ULONG空間,上例中top3

dmaxd數組的大小。

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)

將大數轉換為內存形式。輸入參數為大數ato為輸出緩沖區地址,緩沖區需要預先分配,返回值為緩沖區的長度。

示例:

#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

比較兩個大數。

11BIGNUM *BN_mod_inverse(BIGNUM *in, const BIGNUM *a, 

const BIGNUM *n, BN_CTX *ctx)

計算ax=1(mod n)

用戶使用openssl函數編程時,一般用不着進行大數運算。BN_bin2bnBN_hex2bn、BN_dec2bn、BN_bin2bnBN_bn2binBN_bn2hexBN_bn2dec比較常用。比如給定RSA密鑰的內存形式,用戶可以調用BN_bin2bn來構造RSA密鑰的大數元素來進行RSA運算,或者已經生成了RSA密鑰,用戶調用BN_bn2binRSA各個元素導出到內存中再寫入密鑰文件。

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個字節(輸入數據字節數除31),即8比特,則需要填充2"==",因為要補齊6比特,需要加200

3)輸入數據最后編碼是2個字節(輸入數據字節數除32),則需要填充1"=",因為補齊6比特,需要加一個00

舉例如下:

0x30編碼:

1) 0x30的二進制為:00110000

2) 分組為:001100 00

3) 填充200001100 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_EncodeInitEVP_EncodeUpdate(可以多次)EVP_EncodeFinal

解碼調用次序為EVP_DecodeInitEVP_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 OneX.208),是一套靈活的標記語言,它允許定義多種數據類型,從integerbit string 一類的簡單類型到結構化類型,如set sequence,並且可以使用這些類型構建復雜類型。

DER編碼是ANS.1定義的將對象描述數據編碼成八位串值的編碼規則,它給出了對ANS.1值(對象的類型和值)的唯一編碼規則。

ANS.1中,一個類型是一組值,對於某些類型,值的個數是已知的,而有些類型中值的個數是不固定的。ANS.1中有四種類型:

1) 簡單類型

BIT STRING 任意01位串;

IA5String   任意IA5(ASCII)字符串;

INTEGER   任意一個整數;

NULL      空值;

OBJECT IDENTIFIER  一個對象標識號(一串整數),標識算法或屬性類型等對象;

OCTET STRING  8位串;

PrintableString 任意可打印字符串;

T61String 任意T.618位)字符串;

UTCTime 一個“協同世界時”或“格林威治標准時(G.M.T)”。

2) 結構類型

結構類型由組件組成,ANS.1定義了四種結構類型:

SEQUENCE 一個或多個類型的有序排列;

SEQUENCE OF 一個給定類型的0個或多個有序排列;

SET 一個或多個類型的無序集合;

SET OF    一個給定類型的0個或多個無序集合。

3) 帶標記類型

在一個應用內部區分類型的有效方法是使用標記,標記也同樣用於區分一個結構類型內部不同的組件。例如SETSEQUENCE類型可選項通常使用上下文標記以避免混淆。有兩種標記類型的方法:隱式和顯式。隱式標記類型是將其它類型的標記改變,得到新的類型。隱式標記的關鍵字是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語法中的trueflase。用戶以用UltraEdit等工具編輯一個二進制文件來查看,此二進制文件的內容為:0x30 0x03 0x01 0x01 0x00,然后用asn1view工具查看此文件內容。顯示如下:

 

其中0x01 (表示為BOOLEAN) 0x01(表示后面值的長度) 0x00(值)為本例BOOLEANDER編碼。

2 ASN1_OBJECT

ASN1中的OBJECT表明來一個對象,每個對象有一個OID(object id)。例如:OUOID2.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計算而來)為此OBJECTDER編碼。

3) ASN1_INTEGER

ASN1中的INTEGER類型用於表示整數。編輯一個二進制文件,其內容為:0x30 0x03 0x02(整數) 0x01 (整數值長度)0x55 (整數值)。用an1view查看如下:

 

4 ASN1_ENUMERATED

ASN1枚舉類型,示例如下:

 

5) ASN1_BIT_STRING

示例如下:

 

此圖顯示0x01 0x02DER編碼:0x03BIT STRING 類型) 0x02(長度) 0x01 0x02(比特值)。

6 ASN1_OCTET_STRING

如下:

 

顯示0x01 0x02OCTET STRING編碼:0x04(OCTET STRING) 0x02(長度) 0x01 0x02(值)。

7ASN1_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

OpensslASN.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;

這些都是定義基本數據結構的必要元素。

對於每種類型,均有四種最基本的函數:newfreei2dd2i。其中new函數用於生成一個新的數據結構;free用於釋放該結構; i2d用於將該內部數據結構轉換成DER編碼;d2i用於將DER編碼轉換成內部數據結構。另外,大部分類型都有setget函數,用於給內部數據結構賦值和從中取值。以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 用opensslASN.1DER編解碼

當采用OpensslASN.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 OpensslASN.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的基本的數據類型一般都有如下函數:newfreei2dd2ii2aa2iprintsetgetcmpdup。其中newfreei2dd2i函數通過宏定義實現。new函數用於分配空間,生成ASN1數據結構;free用於釋放空間;i2d函數將ASN1數據結構轉換為DER編碼;d2iDER編碼轉換為ASN1數據結構,i2a將內部結構轉換為ASCII碼,a2iASCII碼轉換為內部數據結構。set函數用於設置ASN1類型的值,get函數用於獲取ASN1類型值;printASN1類型打印;cmp用於比較ASN1數據結構;dup函數進行數據結構的拷貝。

常用的函數有:

1 int a2d_ASN1_OBJECT(unsigned char *out, int olen, const char *buf, int num)

計算OIDDER編碼,比如將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;

}

3int 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的前四個字節即變成"測試"

4unsigned 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;

}

5int 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。

6ASN1_BIT_STRING_set

設置ASN1_BIT_STRING的值,它調用了ASN1_STRING_set函數;

7void *ASN1_d2i_bio(void *(*xnew)(void), d2i_of_void *d2i, BIO *in, void **x)

bio的數據DER解碼,xnew無意義,d2iDER解碼函數,inbio數據,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;

}

8void *ASN1_d2i_fp(void *(*xnew)(void), d2i_of_void *d2i, FILE *in, void **x)

in指向的文件進行DER解碼,其內部調用了ASN1_d2i_bi函數,用法與ASN1_d2i_bi類似。

9int 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表示。

10int 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。

12void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, char *x)

ASN1數據復制。xASN1內部數據結構,本函數先將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;

}

15BIGNUM *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將在內部分配空間。

16int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *a)

檢查輸入參數是不是合法的ASN1_GENERALIZEDTIME類型。

17int ASN1_parse_dump(BIO *bp, const unsigned char *pp, long len, int indent, int dump)

本函數用於將pplen指明的DER編碼值寫在BIO中,其中indentdump用於設置打印的格式。indent用來設置打印出來當列之間空格個數,ident越小,打印內容越緊湊。dump表明當asn1單元為BIT STRINGOCTET 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編碼的文件,比如一個數字證書。

18int 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數據類型簽名。i2dASN1數據的DER方法,signature用於存放簽名結果,dataASN1數據指針,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整數簽名。 

19ASN1_STRING *ASN1_STRING_dup(ASN1_STRING *str)

ASN1_STRING類型拷貝。內部申請空間,需要用戶調用ASN1_STRING_free釋放該空間。

20int 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;

}

21unsigned char * ASN1_STRING_data(ASN1_STRING *x)

獲取ASN1_STRING數據存放地址,即ASN1_STRING數據結構中data地址。本函數由宏實現。

22int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len)

設置ASN1字符串類型的值。strASN1_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);

23ASN1_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表示對象idminsize表示此nid值的最小長度,maxsize表示此nid值的最大長度,mask為此nid可以采用的ASN1_STRING類型:B_ASN1_BMPSTRING、B_ASN1_UTF8STRING、B_ASN1_T61STRING和B_ASN1_UTF8STRING,flags用於標記是否為擴展或是否已有mask

24ASN1_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編碼的漢字獲取nidNID_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;

}

25void 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。

26int ASN1_STRING_set_default_mask_asc(char *p)

設置ASN1_STRING_set_by_NID函數返回的ASN1_STRING類型。字符串p可以的值有:nombstr、pkix、utf8only和default,如果設置為default,則相當於沒有調用本函數。

27int 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)。

28void ASN1_STRING_TABLE_cleanup(void) 

清除用戶自建的擴展ASN1_STRING_TABLE表。

29int 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

30int 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

31OBJ_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;

}

32OBJ_create

根據oid以及名稱信息生成一個內部的object,示例:

nid=OBJ_create("1.2.3.44","testSn","testLn")。

33OBJ_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_cipherEVP_add_digest函數調用OBJ_NAME_initOBJ_NAME_add函數,將EVP_CIPHEREVP_MD信息放入哈希表,EVP_get_cipherbynameEVP_get_digestbyname函數調用OBJ_NAME_get函數從哈希表中查詢需要的信息,EVP_cleanup函數清除存放到EVP_CIPHEREVP_MD信息。另外,程序可以通過調用OpenSSL_add_all_ciphersOpenSSL_add_all_digests函數將所有的對稱算法和摘要算法放入哈希表。

34int OBJ_new_nid(int num)

此函數將內部的new_nidnum,返回原nid

35const char *OBJ_nid2ln(int n)

根據nide得到對象的描訴。

36OBJ_nid2obj

根據nid得到對象。

37const char *OBJ_nid2sn(int n)

根據nid得到對象的sn(簡稱)

38int OBJ_obj2nid(const ASN1_OBJECT *a)

根據對象獲取其nid

39OBJ_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

40int OBJ_sn2nid(const char *s)

根據對象別名稱獲取nid

41OBJ_txt2nid

根據sn或者ln獲取對象的nid

42OBJ_txt2obj

根據sn或者ln得到對象。

13.8 屬性證書編碼

對屬性證書(x509v4)編碼

以下是采用Opensslasn.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)

可以看出,庫的個數不能大於2550xff),函數個數和錯誤原因不能大於40950xfff)。除非計算出來的值與已有的值沒有沖突。

主要數據結構有兩個,定義在crypto/err/err.h中,如下:

1ERR_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中實現。

2ERR_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層錯誤的行號。

topbottom:用於指明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

獲取錯誤處理函數,與哈希表操作相關。

11ERR_get_state

獲取ERR_STATE表。

12ERR_lib_error_string

根據錯誤號,獲取是哪個庫出錯。

13ERR_load_strings

加載錯誤信息,由各個模塊調用。

14ERR_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

各個模塊實現的,加載各自錯誤信息。

15ERR_peek_error

獲取第一個錯誤號。

16ERR_peek_error_line

獲取第一個錯誤的出錯行。

17ERR_peek_error_line_data

獲取第一個錯誤的行數和錯誤信息。

18ERR_peek_last_error

獲取最后一個錯誤號。

19ERR_peek_last_error_line

獲取最后一個錯誤的行號。

20ERR_peek_last_error_line_data

獲取最后一個錯誤的行號和錯誤信息。

21ERR_print_errors

將錯誤信息輸出到bio中。

22ERR_print_errors_cb

根據用戶設置的回調函數來打印錯誤信息。

23ERR_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 概述

摘要函數用於將任意數據通過計算獲取唯一對應值,而這個值的長度比較短。它是一種多對一的關系。理論上,這個短的值就對應於原來的數據。這個過程是不可逆的,即不能通過摘要值來計算原始數據。摘要在信息安全中有非常重要的作用。很多網絡應用都通過摘要計算來存放口令。摘要是安全協議中不可或卻的要素,特別是身份認證與簽名。用戶需要對數據進行簽名時,不可能對大的數據進行運算,這樣會嚴重影響性能。如果只對摘要結果進行計算,則會提供運算速度。常用摘要算法有:shasha1sha256以及md5等。其他還有md4md2mdc2以及ripemd160等。

15.2 openssl摘要實現

openssl摘要實現的源碼位於crypto目錄下的各個子目錄下,如下所示:

  • crypto/ripemd:ripemd摘要實現(包括匯編代碼)及其測試程序;
  • crypto/md2md2摘要實現及其測試程序;
  • crypto/mdc2mdc2摘要實現及其測試程序;
  • crypto/md4md4摘要實現及其測試程序;
  • crypto/md5md5摘要實現及其測試程序;
  • crypto/shashasha1sha256sha512實現及其測試程序(包含匯編源碼)

上述各種摘要源碼在openssl中都是底層的函數,相對獨立,能單獨提取出來,而不必包含openssllibcrypto(因為這個庫一般比較大)

 

15.3 函數說明

所有的摘要算法都有如下幾個函數:

1) XXX_Init

XXX為具體的摘要算法名稱,該函數初始化上下問,用於多數據摘要。

2) XXX_Update

XXX為具體的摘要算法名稱,進行摘要計算,該函數可運行多次,對多個數據摘要。

3) XXX_Final

XXX為具體的摘要算法名稱,進行摘要計算,該函數與1)2)一起用。

4) XXX

對一個數據進行摘要。該函數由上述12)和3)實現,只是XXX_Update只調用一次。對應源碼為XXX_one.c

這些函數的測試程序,可參考各個目錄下對應的測試程序源碼。

15.4 編程示例

以下示例了MD2MD4MD5SHASHA1函數的使用方法:

#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緩沖區中。其中:

mdc2md4md5摘要結果為16字節,128比特;

ripemd160shasha1摘要結果為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運算的數據指針地址;

nd的長度;

md用於存放HMAC值;

md_lenHMAC值的長度。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第十六章 數據壓縮

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:具體的解壓算法,本函數必須實現;

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

};

各項意義如下:

methCOMP_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:最初的大素數
  • dmp1e*dmp1 = 1 (mod (p-1))
  • dmq1:e*dmq1 = 1 (mod (q-1))
  • iqmp:q*iqmp = 1 (mod p )

其中,公鑰為ne;私鑰為nd。在實際應用中,公鑰加密一般用來協商密鑰;私鑰加密一般用來簽名。

17.2 opensslRSA實現

OpensslRSA實現源碼在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回調函數。

5rsa_err.c

RSA錯誤處理。

6rsa_gen.c

RSA密鑰生成,如果RSA_METHOD中的rsa_keygen回調函數不為空,則調用它,否則調用其內部實現。

7rsa_lib.c

主要實現了RSA運算的四個函數(公鑰/私鑰,加密/解密),它們都調用了RSA_METHOD中相應都回調函數。

8rsa_none.c

實現了一種填充和去填充。

9rsa_null.c

實現了一種空的RSA_METHOD

10) rsa_oaep.c

實現了oaep填充與去填充。

11rsa_pk1.

實現了pkcs1填充與去填充。

12rsa_sign.c

實現了RSA的簽名和驗簽。

13rsa_ssl.c

實現了ssl填充。

14rsa_x931.c

實現了一種填充和去填充。

17.3 RSA簽名與驗證過程

RSA簽名過程如下:

1) 對用戶數據進行摘要;

2 構造X509_SIG結構並DER編碼,其中包括了摘要算法以及摘要結果。

32)的結果進行填充,填滿RSA密鑰長度字節數。比如1024RSA密鑰必須填滿128字節。具體的填充方式由用戶指定。

43)的結果用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數據結構中包含了公/私鑰信息(如果僅有ne,則表明是公鑰),定義如下:

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:硬件引擎;

nedpqdmp1dmq1iqmpRSA密鑰的各個值;

ex_data:擴展數據結構,用於存放用戶數據;

references:RSA結構引用數。

17.5 主要函數

1 RSA_check_key

檢查RSA密鑰。

2RSA_new

生成一個RSA密鑰結構,並采用默認的rsa_pkcs1_eay_meth RSA_METHOD方法。

3RSA_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是公鑰指數ecallback回調函數由用戶實現,用於干預密鑰生成過程中的一些運算,可為空。

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

各種去除填充函數。

10int RSA_print(BIO *bp, const RSA *x, int off)

RSA信息輸出到BIO中,off為輸出信息在BIO中的偏移量,比如是屏幕BIO,則表示打印信息的位置離左邊屏幕邊緣的距離。

11int DSA_print_fp(FILE *fp, const DSA *x, int off)

RSA信息輸出到FILE中,off為輸出偏移量。

12RSA_public_decrypt

RSA公鑰解密。

13RSA_public_encrypt

RSA公鑰加密。

14RSA_set_default_method/ RSA_set_method

設置RSA結構中的method,當用戶實現了一個RSA_METHOD時,調用此函數來設置,使RSA運算采用用戶的方法。

15RSA_set_ex_data

設置擴展數據。

16RSA_sign

RSA簽名。

17RSA_sign_ASN1_OCTET_STRING

另外一種RSA簽名,不涉及摘要算法,它將輸入數據作為ASN1_OCTET_STRING進行DER編碼,然后直接調用RSA_private_encrypt進行計算。

18RSA_size

獲取RSA密鑰長度字節數。

19RSA_up_ref

RSA密鑰增加一個引用。

20RSA_verify

RSA驗證。

21RSA_verify_ASN1_OCTET_STRING

另一種RSA驗證,不涉及摘要算法,與RSA_sign_ASN1_OCTET_STRING對應。

22RSAPrivateKey_asn1_meth

獲取RSA私鑰的ASN1_METHOD,包括i2dd2inewfree函數地址。

23RSAPrivateKey_dup

復制RSA私鑰。

24RSAPublicKey_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_keyRSA_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)算法是一種公鑰算法。其密鑰由如下部分組成:

1p

一個大素數,長度為L(64的整數倍)比特。

2q

一個160比特素數。

3g

g=h(p-1)/q mod p,其中h小於p-1

4x

小於q

5) y

y=gx mod p

其中x為私鑰,y為公鑰。pqg是公開信息(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 opensslDSA實現

OpensslDSA實現源碼在crypto/dsa目錄下。主要源碼如下:

1) dsa_asn1.c

DSA密鑰參數(pqg)DSA公鑰(pub_key、pqg)以及DSA私鑰(priv_key、pub_key、pqg)DER編解碼實現。

2dsa_depr.c

生成DSA密鑰參數。

3dsa_err.c

DSA錯誤處理。

4dsa_gen.c

生成DSA密鑰參數。

5dsa_key.c

根據DSA中的密鑰參數產生公鑰和私鑰。

6dsa_lib.c

實現了DSA通用的一些函數。

7dsa_ossl.c

實現了一個DSA_METHOD,該DSA_METHOD為openssl默認的DSA方法,主要實現了如下三個回調函數:dsa_do_sign(簽名)、dsa_sign_setup(根據密鑰參數生成公私鑰)和dsa_do_verify(驗簽)。

8dsa_sign.c

實現了DSA簽名和根據密鑰參數生成公私鑰。

9dsa_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結構。

11DSA_OpenSSL

獲取openssl_dsa_meth方法。

12DSA_print

DSA密鑰信息輸出到BIO中。

13DSA_print_fp

DSA密鑰信息輸出到FILE中。

14DSA_set_default_method

設置默認的DSA_METHOD

15) DSA_set_ex_data

設置擴展數據。

16DSA_set_method

獲取當前DSADSA_METHOD方法。

17DSA_SIG_new

生成一個DSA_SIG簽名值結構。

18DSA_SIG_free

釋放DSA_SIG結構。

19DSA_sign

DSA簽名。

20DSA_sign_setup

根據密鑰參數生成公私鑰。

21DSA_size

獲取DSA密鑰長度的字節數。

22DSA_up_ref

DSA結構添加一個引用。

23DSA_verify

簽名驗證。

24DSAparams_print

DSA密鑰參數輸出到bio

25DSAparams_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.DiffieM.Hellman提出的。此算法是最早的公鑰算法。它實質是一個通信雙方進行密鑰協商的協議:兩個實體中的任何一個使用自己的私鑰和另一實體的公鑰,得到一個對稱密鑰,這一對稱密鑰其它實體都計算不出來。DH算法的安全性基於有限域上計算離散對數的困難性。離散對數的研究現狀表明:所使用的DH密鑰至少需要1024位,才能保證有足夠的中、長期安全。

首先,發送方和接收方設置相同的大數數ng,這兩個數不是保密的,他們可以通過非安全通道來協商這兩個素數;

接着,他們用下面的方法協商密鑰:

發送方選擇一個大隨機整數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

其他人可以知道ngXY,但是他們不能計算出密鑰,除非他們能恢復xyDH算法不能抵御中間人攻擊,中間人可以偽造假的XY分別發送給雙方來獲取他們的秘密密鑰,所以需要保證XY的來源合法性。

19.2 opensslDH實現

OpensslDH實現在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私鑰(另一方)來生成一個共享密鑰,用於密鑰交換。

6dh_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提供默認方法。各項意義如下:

nameDH_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、glengthDH密鑰參數;

pub_keyDH公鑰;

priv_keyDH私鑰;

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數據結構。

10DH_OpenSSL

獲取openssl提供的DH_METHOD

11DH_set_default_method

設置默認的DH_METHOD方法,當用戶實現了自己的DH_METHOD時,可調用本函數來設置,控制DH各種計算。

12DH_set_ex_data

獲取擴展數據。

13DH_set_method

替換已有的DH_METHOD

14DH_size

獲取DH密鑰長度的字節數。

15) DH_up_ref

增加DH結構的一個引用。

16DHparams_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;

}

/* pg為公開的密鑰參數,因此可以拷貝 */

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) 橢圓曲線參數:

素數域:

(pabGnh)

其中,p為素數,確定Fpab確定橢圓曲線方程,G為基點,nG的階,h為余因子。

二進制擴展域:

(mf(x)abGnh)

其中,m確定F2mf(x)為不可約多項式,ab用於確定橢圓曲線方程,G為基點,nG的階,h為余因子。

6) 橢圓曲線公鑰和私鑰

橢圓曲線的私鑰是一個隨機整數,小於n

橢圓曲線的公鑰是橢圓曲線上的一個點:Q=私鑰*G

20.2 opensslECC實現

Openssl實現了ECC算法。ECC算法系列包括三部分:ECC算法(crypto/ec)、橢圓曲線數字簽名算法ECDSA (crypto/ecdsa)以及橢圓曲線密鑰交換算法ECDH(crypto/ecdh)

研究橢圓曲線需要注意的有:

1) 數據結構

  • 橢圓曲線數據結構:EC_GROUP,該結構不僅包含各個參數,還包含了各種算法;
  • 點的表示:EC_POINT,其中的大數XYZ為雅克比投影坐標;
  • 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中,內置的橢圓曲線密鑰生成時,首先用戶需要選取一種橢圓曲線(opensslcrypto/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)

說明:二進制域橢圓曲線,給定壓縮坐標xy_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)

說明:素數域橢圓曲線,給定壓縮坐標xy_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的投影坐標系坐標xyz

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)

說明:設置橢圓曲線的基Ggenerator、order和cofactor為輸入參數;

10) size_t EC_GROUP_set_seed(EC_GROUP *group, const unsigned char *p, size_t len)

說明:設置橢圓曲線隨機數,用於生成ab

11) EC_GROUP *EC_GROUP_new_curve_GF2m(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)

說明:生成二進制域上的橢圓曲線,輸入參數為pab

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)

說明:獲取橢圓曲線參數的隨機數,該隨機數可選,用於生成橢圓曲線參數中的ab

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)

說明:獲取二進制域橢圓曲線上某個點的xy的幾何坐標;

12) int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group, const EC_POINT *point,        BIGNUM *x, BIGNUM *y, BN_CTX *ctx)

說明:獲取素數域上橢圓曲線上某個點的xy的幾何坐標;

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)

說明:獲取素數域橢圓曲線上某個點的xyz的投影坐標系坐標。

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

說明:獲取內置的橢圓曲線。當輸入參數rNULL或者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密鑰大小字節數。

18ECDSA_sign

說明:簽名,返回1表示成功。

19ECDSA_verify

說明:驗簽,返回1表示合法。

20EC_KEY_get0_public_key

說明:獲取公鑰。

21EC_KEY_get0_private_key

說明:獲取私鑰。

22ECDH_compute_key

說明:生成共享密鑰

23EC_KEY *d2i_ECPrivateKey(EC_KEY **a, const unsigned char **in, long len)

說明:DER解碼將橢圓曲線密鑰;

24int     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 ] */

};

該結構用來存放非對稱密鑰信息,可以是RSADSADHECC密鑰。其中,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.cc_alld.cc_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.cbio_enc.cbio_md.cbio_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 

返回md5EVP_MD。

2)  EVP_sha1

返回sha1EVP_MD。

3)  EVP_sha256

返回sha256EVP_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回調函數。

10EVP_BytesToKey

計算密鑰函數,它根據算法類型、摘要算法、salt以及輸入數據計算出一個對稱密鑰和初始化向量iv

11PKCS5_PBE_keyivgen和PKCS5_v2_PBE_keyivgen

實現了PKCS5基於口令生成密鑰和初始化向量的算法。

12PKCS5_PBE_add

加載所有openssl實現的基於口令生成密鑰的算法。

13EVP_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_PKEYRSA密鑰結構。

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解碼,並獲取最終結果。

7EVP_EncodeBlock

BASE64編碼函數,本函數可單獨調用。

8EVP_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_EncryptInitEVP_DecryptInit函數也調用本函數。

10) EVP_CipherUpdate

對稱計算(加/解密)函數,它調用了EVP_EncryptUpdateEVP_DecryptUpdate函數

11 EVP_CipherFinal/EVP_CipherFinal_ex

對稱計算(/)函數,調用了EVP_EncryptFinal_ex)和EVP_DecryptFinal(_ex);本函數主要用來處理最后加密分組,可能會有對稱計算。

12EVP_cleanup

清除加載的各種算法,包括對稱算法、摘要算法以及PBE算法,並清除這些算法相關的哈希表的內容。

13) EVP_get_cipherbyname

根據字串名字來獲取一種對稱算法(EVP_CIPHER),本函數查詢對稱算法哈希表。

14) EVP_get_digestbyname

根據字串獲取摘要算法(EVP_MD),本函數查詢摘要算法哈希表。

15) EVP_get_pw_prompt

獲取口令提示信息字符串.

16int 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.cPKCS12_pbe_crypt函數).

17) EVP_PBE_cleanup

刪除所有的PBE信息,釋放全局堆棧中的信息.

18EVP_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

比較非對稱密鑰的密鑰參數,用於DSAECC密鑰。

22EVP_PKEY_copy_parameters

拷貝非對稱密鑰的密鑰參數,用於DSAECC密鑰。

23EVP_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密鑰。

26EVP_PKEY_get1_RSA/EVP_PKEY_set1_RSA

獲取/設置EVP_PKEY中結構中的RSA結構密鑰。

27) EVP_PKEY_missing_parameters

檢查非對稱密鑰參數是否齊全,用於DSAECC密鑰。

28) EVP_PKEY_new

生成一個EVP_PKEY結構。

29) EVP_PKEY_size

獲取非對稱密鑰的字節大小。

30) EVP_PKEY_type

獲取EVP_PKEY中表示的非對稱密鑰的類型。

31int EVP_read_pw_string(char *buf,int length,const char *prompt,int verify)

獲取用戶輸入的口令;buf用來存放用戶輸入的口令,lengthbuf長度,prompt為提示給用戶的信息,如果為空,它采用內置的提示信息,verify0時,不要求驗證用戶輸入的口令,否則回要求用戶輸入兩遍。返回0表示成功。

32) EVP_set_pw_prompt

設置內置的提示信息,用於需要用戶輸入口令的場合。

21.9  對稱加密過程

對稱加密過程如下:

1 EVP_EncryptInit

設置buf_len0,表明臨時緩沖區buf沒有數據。

2 EVP_EncryptUpdate

ctx結構中的buf緩沖區用於存放上次EVP_EncryptUpdate遺留下來的未加密的數據,buf_len指明其長度。如果buf_len0,加密的時候先加密輸入數據的整數倍,將余下的數據拷貝到buf緩沖區。如果buf_len不為0,先加密buf里面的數據和輸入數據的一部分(湊足一個分組的長度),然后用上面的方法加密,輸出結果是加過密的數據。

3 EVP_ EncryptFinal

加密ctxbuf中余下的數據,如果長度不夠一個分組(分組長度不為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數據來生成所需要的keyiv

輸出:

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使用PEMPrivacy 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 opensslPEM實現

OpensslPEM模塊實現位於crypto/pem目錄下,並且還依賴於opensslASN1模塊。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處理函數主要是writeread函數。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中讀取PEMXXXX代表類型的信息

XXXX可用代表的有:SSL_SESSIONX509X509_REQX509_AUXX509_CRLRSAPrivateKeyRSAPublicKeyDSAPrivateKeyPrivateKeyPKCS7DHparamsNETSCAPE_CERT_SEQUENCEPKCS8PrivateKeyDSAPrivateKeyDSA_PUBKEYDSAparamsECPKParametersECPrivateKeyEC_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即是輸入參數又是輸出參數。

nameheader和data等用OPENSSL_free釋放內存。

 

第二十三章 Engine

23.1 Engine概述

Openssl硬件引擎(Engine)能夠使用戶比較容易地將自己的硬件加入到openssl中去,替換其提供的軟件算法。一個Engine提供了密碼計算中各種計算方法的集合,它用於控制openssl的各種密碼計算。

23.2 Engine支持的原理

Openssl中的許多數據結構不僅包含數據本身,還包含各種操作,並且這些操作是可替換的。Openssl中這些結構集合一般叫做XXX_METHOD,有DSO_METHODDSA_METHODEC_METHODECDH_METHODECDSA_METHODDH_METHODRAND_METHODRSA_METHODEVP_CIPHEREVP_MD等。以RSA結構為例(crypto/rsa/rsa.h)RSA結構不僅包含了大數nedp等等數據項目,還包含一個RSA_METHOD回調函數集合。該方法給出了RSA各種運算函數。

對於各種數據類型,要進行計算必須至少有一個可用的方法(XXX_METHOD)。因此,openssl對各種類型都提供了默認的計算方法(軟算法)。如果用戶實現了自己的XXX_METHOD,那么就能替換openssl提供的方法,各種計算由用戶自己控制。硬件Engine就是這種原理。根據需要,一個硬件Engine可實現自己的RAND_METHODRSA_METHODEVP_CIPHERDSA_METHODDH_METHODECDH_METHODEVP_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)供用戶來實現。各項意義如下:

idEngine標識;

nameEngine的名字;

rsa_methRSA方法集合;

dsa_methDSA方法集合;

dh_methDH方法集合;

ecdh_methECDH方法結合;

ecdsa_methECDSA方法集合;

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源碼

OpensslEngine源碼分為四類:

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)

獲取EngineDH_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

11ENGINE_get_digests/ENGINE_set_digests

獲取/設置指定Engine的摘要算法選取函數地址,該函數用於從Engine中選擇一種摘要算法

12) const DSA_METHOD *ENGINE_get_DSA(const ENGINE *e)

獲取EngineDSA方法

13) int ENGINE_register_XXX(ENGINE *e)

注冊函數,將某一個Engine添加到對應方法的哈希表中

14) void ENGINE_unregister_XXX(ENGINE *e)

將某一個Engine從對應的哈希表中刪除

15) void ENGINE_register_all_XXX(void)

將所有的Engine注冊到對應方法的哈希表中

16ENGINE_set_default_XXXX

設置某Engine為對應XXXX方法的默認Engine

17) ENGINE_get_default_XXXX

獲取XXXX方法的默認Engine

18ENGINE_load_XXXX

加載某種Engine

19) ENGINE_get_RAND/ENGINE_set_RAND

獲取/設置Engine的隨機數方法。

20) ENGINE_get_RSA/ENGINE_set_RSA

獲取/設置EngineRSA方法。

21) ENGINE_get_first/ENGINE_get_next/ENGINE_get_prev/ENGINE_get_last

Engine鏈表操作函數

22ENGINE_set_name/ENGINE_get_name

設置/獲取Engine名字

23ENGINE_set_id/ENGINE_get_id

設置/獲取Engineid

24) int ENGINE_set_default(ENGINE *e, unsigned int flags)

根據flagse設置為各種方法的默認Engine

25) ENGINE_set_XXX_function

設置EngineXXX對應的函數。

26) ENGINE_get_XXX_function

獲取EngineXXX對應的函數。

27) ENGINE_ctrl

Engine控制函數

28) ENGINE_get_ex_data/ENGINE_set_ex_data

獲取/設置Engine的擴展數據

29ENGINE_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,

};

/* Engineid */

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編解碼的。用戶主要需要了解它們各項的意義、對它們的編解碼以及對它們的setget操作。

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中。包括是個函數:newfreei2dd2i

編程示例如下:

#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中實現,包括newfreei2dd2i函數。

用於簽名的摘要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對應於一個證書中的COUO等實體名稱,其中object表明了實體的類型是C還是OU等;value表明了該實體的內容,這兩項用於DER編解碼。該結構的DER編解碼在crypto/asn1/x_name.c中由宏實現,包括newfreei2dd2idup函數。

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)

根據nidX509_NAMEX509_NAME_ENTRY堆棧中添加一項;bytes 為要添加項的值,type指明了typesASN1類型,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指明了typesASN1類型。

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_NAMEX509_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的結果處理后的值。

22char *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_NAMEbio中。

24) X509_NAME_print_ex_fp

打印X509_NAMEFILE中。

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.cerPEM格式的數字證書 */

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中由宏實現,包括newfreei2dd2idup函數。擴展項的DER編解碼可直接采用i2dd2i來完成,也可用采用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表示是何種擴展項,以itd2ii2d函數來指明來它的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.cCRL發布點,實現了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。

openssl509V3_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對應的擴展項,此時如果flagsX509V3_ADD_REPLACE_EXISTINGX509V3_ADD_DELETE 則報錯:無此擴展項;

b) 擴展項堆棧中有nid對應的擴展項,如果flagsX509V3_ADD_KEEP_EXISTING,成功返回;如果flagsX509V3_ADD_DEFAULT 報錯,表明此擴展項已經存在;如果flagsX509V3_ADD_DELETE,則刪除這個擴展項;如果flags是  X509V3_ADD_REPLACE_EXISTING,則替換此擴展項。

編程示例1

調用函數X509_EXTENSION_create_by_NIDX509_EXTENSION_create_by_OBJ生成擴展項,並調用X509_EXTENSION_get_objectX509_EXTENSION_get_dataX509_EXTENSION_get_critical獲取擴展項信息; 調用X509_EXTENSION_set_objectX509_EXTENSION_set_criticalX509_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;

/* 從時間197011000秒往后算時間,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;

/* 從時間197011000秒往后算時間,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中由宏實現,實現了newfreei2dd2idup函數。

主要函數:

1) i nt X509_ATTRIBUTE_count(X509_ATTRIBUTE *attr)

獲取屬性中ASN1_TYPE的個數。

2X509_ATTRIBUTE *X509_ATTRIBUTE_create(int nid, int atrtype, void *value)

生成一個屬性。id用來生成ASN1_OBJECT,指明是哪種屬性,atrtypeASN1類型,value為值,用來設置set堆棧。

3X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_OBJ(X509_ATTRIBUTE **attr,

     const ASN1_OBJECT *obj, int atrtype, const void *data, int len)

生成一個屬性,obj指明了屬性類型,atrtypeASN1類型,datalen指明了需要設置的值。

4X509_ATTRIBUTE_create_by_NID

同上,屬性類型由nid指定。

5X509_ATTRIBUTE_create_by_txt

同上,屬性類型由ASN1_OBJECT的名字指定。

6X509_ATTRIBUTE_dup

拷貝函數。

7ASN1_TYPE *X509_ATTRIBUTE_get0_type(X509_ATTRIBUTE *attr, int idx)

獲取屬性中由堆棧位置idx指定的ASN1_TYPE,如果屬性不是set集合則返回value. single。

8void *X509_ATTRIBUTE_get0_data(X509_ATTRIBUTE *attr, int idx,

int atrtype, void *data)

idxASN1類型atrtype來獲取value. ptr。

9X509_ATTRIBUTE_get0_object

獲取屬性類型信息。

10) X509_ATTRIBUTE_set1_data

設置屬性信息。

11) X509_ATTRIBUTE_set1_object

設置屬性類別。

12STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr(

STACK_OF(X509_ATTRIBUTE) **x, X509_ATTRIBUTE *attr)

性堆棧中添加一個屬性,返回屬性堆棧;如果*xNULL,則生成一個新的堆棧。

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指明了bytesASN1類型。

14X509at_add1_attr_by_OBJ

同上,屬性類型由ASN1_OBJECT指定。

15X509at_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中對應的項。比如,當typeGEN_DIRNAME時,數據存放在directoryName中。openssl中最常用的是X509_NAME。

上述兩種結構的DER編碼在crypto/x509v3/v3_genn.c中由宏實現,實現了newfreei2dd2i四個函數。

編程示例:

#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#10rfc2314

25.2 數據結構

根據PKCS#10opensslX509數字證書申請結構定義在crypto/x509.h中,如下所示,主要由兩部分組成:

1X509_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中由宏實現,實現了newfreei2dd2i函數。

2X509_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中由宏實現,實現了newfreei2dd2i函數。

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釋放空間。

10int X509_REQ_extension_nid(int req_nid)

判斷req_nid是否為NID_ext_req、NID_ms_ext_req或其他由用戶設置的NID,如果是返回1,否則返回0

11STACK_OF(X509_EXTENSION) *X509_REQ_get_extensions(X509_REQ *req)

獲取X509_REQ中的屬性信息,並將屬性信息轉換為X509_EXTENSION堆棧。該函數從X509_REQ的屬性堆棧中查找包含合法的nid類型的屬性(X509_REQ_get_extension_nids函數說明),如果找到一個,則將屬性值通過DER解碼轉換為擴展項堆棧。

12X509_REQ_get1_email

獲取證書申請中申請者的郵件地址信息,信息來自X509_NAME *subject和STACK_OF(X509_ATTRIBUTE) *attributes,返回一個堆棧。

13X509_REQ_get_attr

根據指定位置,獲取屬性堆棧中的一個屬性。

14int X509_REQ_get_attr_by_NID(const X509_REQ *req, int nid, int lastpos)

根據屬性nid,從req的屬性堆棧中查找對應屬性,並返回。查找堆棧時,從lastpos位置開始查找。

15X509_REQ_get_attr_by_OBJ

同上,根據ASN1_OBJECT來查找屬性。

16X509_REQ_get_attr_count

屬性堆棧中屬性的個數。

17X509_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。

18X509_REQ_get_pubkey

獲取公鑰。

19X509_REQ_print

將證書申請信息輸出到BIO中。

20int 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)

21X509_REQ_print_fp

將證書申請消息輸出到FILE中。

22X509_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算法,另外,沒有處理證書擴展項。

24int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey)

設置證書請求的公鑰。

25int 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_verifyX509_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.txtPEM格式的證書請求文件,certreq2.txtDER編碼格式。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第二十六章 X509數字證書

26.1 X509數字證書

數字證書是將用戶(或其他實體)身份與公鑰綁定的信息載體。一個合法的數字證書不僅要符合X509格式規范,還必須有CA的簽名。用戶不僅有自己的數字證書,還必須有對應的私鑰。

X509v3數字證書主要包含的內容有[1]:證書版本、證書序列號、簽名算法、頒發者信息、有效時間、持有者信息、公鑰信息、頒發者ID、持有者ID和擴展項。

26.2 opessl實現

openssl實現了標准的x509v3數字證書,其源碼在crypto/x509crypto/x509v3中。其中x509目錄實現了數字證書以及證書申請相關的各種函數,包括了X509X509_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中實現,包括各自的newfreei2dd2i函數。

DER解碼編程示例如下:

#include <openssl/x509.h>

int main()

{

X509 *x;

FILE *fp;

unsigned char buf[5000],*p;

int len,ret;

BIO *b;

 

/* cert.cerDER編碼的數字證書 

用戶如果是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.cerPEM格式的數字證書 */

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,該函數用於判斷證書是受信任的。

Opensslx509_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得到結果。

2X509_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_OBJECTreject堆棧通過X509_add1_reject_object來存放一個應該拒絕的ASN1_OBJECT。這兩個堆棧在x509/x509_trs.cobj_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為證書用途IDcheck_purpose為檢查證書用途函數。基本的用途IDx509v3.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

Opensslx509v3/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為證書用途NIDca表明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_datausr_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堆棧中,在指定位置添加一項。

10X509_ALGOR_dup

算法拷貝。

11X509_alias_get0/X509_alias_set1

獲取/設置別名。

12X509_asn1_meth

獲取X509ASN1_METHOD,包括newfreei2dd2i函數。

13X509_certificate_type

獲取證書和公鑰類型。

14int X509_check_issued(X509 *issuer, X509 *subject);

檢查subject證書是否由issuer頒發,如果是則返回X509_V_OK,即0

15X509_check_private_key

檢查私鑰與證書中的公鑰是否匹配,匹配返回1

16X509_cmp

證書比較。

17)  int X509_cmp_current_time(ASN1_TIME *s)

s與當前時間進行比較,返回值小於0s早於當前時間,大於0s晚與當前時間。

18int X509_cmp_time(ASN1_TIME *ctm, time_t *cmp_time)

如果ctm時間在cmp_time之后,則返回值大於0

19) X509_delete_ext

刪除擴展項堆棧中指定位置的擴展項。

20X509_digest

根據指定的摘要算法對X509結構做摘要。

20) X509_dup

拷貝函數。

21X509_find_by_issuer_and_serial

根據頒發者的X509_NAME名稱和證書序列號,在X509堆棧中查找對應的證書並返回。

22) X509_find_by_subject

從證書堆棧中根據持有者名字查詢證書,並返回。

23X509_get0_pubkey_bitstr

獲取X509結構中的DER編碼的公鑰信息。

24X509_load_cert_crl_file

加載證書和crl,用於驗證證書。

25X509_PURPOSE_get0

根據X509_PURPOSE的位置獲取對應的X509_PURPOSE

26X509_PURPOSE_get0_name

獲取X509_PURPOSE的名字。

27X509_PURPOSE_get0_sname

獲取X509_PURPOSE的別名。

28X509_PURPOSE_get_by_id

根據證書用途ID獲取X509_PURPOSE在當前數組(xstandard)或堆棧(xptable)中的位置,如果沒有返回-1

29X509_PURPOSE_get_by_sname

根據別名獲取對應的X509_PURPOSE在數組或堆棧中的位置。

30X509_PURPOSE_get_count

獲取所有的X509_PURPOSE個數,包括標准的和用戶動態添加的。

31X509_PURPOSE_get_id

獲取X509_PURPOSEID

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

堆棧操作,去除指定位置的擴展項。

35int X509V3_EXT_print(BIO *out, X509_EXTENSION *ext, 

unsigned long flag, int indent)

本函數用於打印單個擴展項,outBIO類型的輸出對象,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

36int 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

38int 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)

功能同上。

40X509_EXTENSION *X509v3_get_ext(const STACK_OF(X509_EXTENSION) *x, 

int loc)

獲取擴展項,loc為擴展項在堆棧x中的位置,如果不成功,返回NULL

41int X509v3_get_ext_count(const STACK_OF(X509_EXTENSION) *x)

獲取擴展項的個數,此函數調用堆棧操作sk_X509_EXTENSION_num(x)來獲取擴展項的個數。

42STACK_OF(CONF_VALUE) * X509V3_get_section(X509V3_CTX *ctx, char *section)

獲取配置信息,section為配置信息中的“段”信息。比如有配置信息如下:

[CA]

Name1=A

Name2=B

section應是”CA”,返回的信息為它包含的內容信息。

43char * X509V3_get_string(X509V3_CTX *ctx, char *name, char *section)

根據段和屬性獲取值,比如有如下配置信息:

[CA]

Name1=A

Name2=B

調用此函數時name”Name1”,sectionwei “CA”,則返回值為”A”。

44int X509V3_get_value_bool(CONF_VALUE *value, int *asn1_bool)

判斷配置信息的布爾值,如果value表示的值為trueTRUEyYyesYES*asn1_bool 的值設為xff,並返回1,如果為falseFALSEnNNOno, *asn1_bool設置為 0,並返回1。此函數調用不成功時返回0

45) int X509V3_get_value_int(CONF_VALUE *value, ASN1_INTEGER **aint)

value中的值轉換為ASN1_INTEGER類型,結果存放在**aint中,函數調用成功返回1,否則返回0

46STACK_OF(CONF_VALUE) *X509V3_parse_list(const char *line)

分析配置信息的一行數據,返回結果。

26.7 證書驗證

26.7.1證書驗證項

數字證書驗證中,主要考察的項有:

  1. 有效期,看證書是否已經失效;
  2. 簽名,用頒發者的公鑰來驗證簽名;
  3. 證書用途;
  4. 名字比較,證書中的頒發者信息應與頒發者證書的持有者信息一致;
  5. 擴展項約束;

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] rfc2459Internet X.509 Public Key Infrastructure Certificate and CRL Profile

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第二十七章 OCSP

27.1 概述

在線證書狀態協議(OCSPOnline Certificate Status Protocolrfc2560)用於實時表明證書狀態。OCSP客戶端通過查詢OCSP服務來確定一個證書的狀態。OCSP可以通過HTTP協議來實現。rfc2560定義了OCSP客戶端和服務端的消息格式。

27.2 openssl實現

opensslcrypto/ocsp目錄實現了ocsp模塊,包括客戶端和服務端各種函數。主要源碼如下:

  • ocsp_asn.cocsp消息的DER編解碼實現,包括基本的newfreei2dd2i函數;
  • ocsp_cl.cocsp客戶端函數實現,主要用於生成ocsp請求;
  • ocsp_srv.cocsp服務端思想,主要用於生成ocsp響應;
  • ocsp_err.cocsp錯誤處理;
  • ocsp_ext.cocsp擴展項處理;
  • 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指定的的算法,簽名私鑰以及摘要算法由輸入參數指定。

10int 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中,摘要算法由輸入參數指定。

12OCSP_REQUEST_verify

驗證ocsp請求簽名,公鑰由輸入參數指定。

13OCSP_BASICRESP_verify

驗證ocsp響應簽名,公鑰由輸入參數指定。

14OCSP_request_verify

驗證ocsp響應,該函數做全面的驗證,包括簽名、證書目的以及證書鏈等。

15int OCSP_basic_sign(OCSP_BASICRESP *brsp,X509 *signer, EVP_PKEY *key, 

const EVP_MD *dgst,STACK_OF(X509) *certs, unsigned long flags)

本函數用輸入參數signerkeydgstcertsflags來填充brsp數據結構,並對brsp結構簽名,成功返回1,否則返回0

16) int OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec)

時間檢查計算,合法返回1thisupd為本次更新時間,nextupd為下次更新時間。thisupdnextupd由響應服務生成,他們被傳給請求者。請求者收到響應之后需要驗證ocsp消息的時間有效性。要求如下:

  • 本次更新時間不能比當前時間提前太多,提前時間不能大於nsec,比如ocsp服務器多時間比請求者系統時間快很多,導致thisupd錯誤非法;
  • 本次更新時間不能晚於當前時間太多,否則ocsp消息失效,晚的時間不能大於maxsec
  • 下次更新時間不能晚於當前時間太多,晚多時間不大於nsec(由於下一條規則限制,也不能大於maxsec)
  • 下次更新時間必須大於本次更新時間。

總之,本次更新時間和下次更新時間必須在以當前時間為中心的一個窗口內。

17OCSP_CERTID_dup

復制函數。

18OCSP_CERTSTATUS_dup

復制函數。

19OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *req, 

OCSP_CERTID *cid)

本函數用於往請求消息中添加一個證書ID;它將一個OCSP_CERTID信息存入OCSP_REQUEST結構,返回內部生成的OCSP_ONEREQ指針。根據cid構造一個OCSP_ONEREQ信息,並將此信息放入req請求消息的堆棧。

20int OCSP_request_set1_name(OCSP_REQUEST *req, X509_NAME *nm)

本函數用於設置消息請求者的名字。

21int OCSP_request_add1_cert(OCSP_REQUEST *req, X509 *cert)

本函數往消息請求中添加一個證書。此證書信息放在OCSP_REQUEST結構的一個堆棧中,並將此證書結構的引用加1

22int OCSP_response_status(OCSP_RESPONSE *resp)

本函數獲取OCSP響應狀態。

23OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *resp)

本函數從響應數據結構中獲取OCSP_BASICRESP信息。

24int OCSP_resp_count(OCSP_BASICRESP *bs)

本函數獲取響應消息中包含的證書狀態的個數。

25OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *bs, int idx);

給定單個響應的序號,從堆棧中取出。

26int OCSP_resp_find(OCSP_BASICRESP *bs, OCSP_CERTID *id, int last)

根據ocsp證書ID查詢對應的響應在堆棧中的位置,last為搜索堆棧時的起始位置,如果小於0,從0開始。

27int 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

如果證書被撤銷,並且reasonrevtime參數不為空,將撤銷原因以及撤銷時間返回。並且對於這個證書給出thisUpdatenextUpdate

28int 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函數,idOCSP證書ID,它依次調用OCSP_resp_findOCSP_resp_get0 OCSP_single_get0_status函數,其中status為返回的證書狀態。

29int OCSP_request_add1_nonce(OCSP_REQUEST *req, unsigned char *val, int len)

添加nonce擴展項,vallen表明了nonce,如果val為空,則內部生成長度為len的隨機數作為nonce

30int OCSP_basic_add1_nonce(OCSP_BASICRESP *resp, unsigned char *val, int len)

功能同上。

31int OCSP_check_nonce(OCSP_REQUEST *req, OCSP_BASICRESP *bs)

檢測nonce,用於防止重放攻擊;檢查請求和響應的nonce擴展項,看他們是否相等,OCSP服務端應當將請求中的nonce拷貝到響應中。如果請求和響應中的nonce擴展項都存在,比較nonce值,如果不相等,返回錯誤,或者,請求中有nonce,而響應中沒有nonce,也返回錯誤。驗證正確時返回值大於0

32int OCSP_copy_nonce(OCSP_BASICRESP *resp, OCSP_REQUEST *req)

將請求中都nonce拷貝到響應中。

33X509_EXTENSION *OCSP_crlID_new(char *url, long *n, char *tim)

根據crlurlcrl個數以及生成crl的時間生成X509_EXTENSION擴展項。

34X509_EXTENSION *OCSP_accept_responses_new(char **oids)

根據多個oid的名字生成擴展項,其中oids指針數組,以NULL結尾。本函數由客戶端調用,告訴服務端它所要的端響應的類型,參考rfc2560對於AcceptableResponses擴展項的說明。

35X509_EXTENSION *OCSP_archive_cutoff_new(char* tim)

生成單個證書的Archive Cutoff擴展項,某已被撤銷的證書的Archive Cutoff時間為本次OCSP生效時間(producedAt)減去被撤銷時的時間。可以將它看作已撤銷了多長時間。

36X509_EXTENSION *OCSP_url_svcloc_new(X509_NAME* issuer, char **urls);

根據頒發者名字和一個或多個url生成擴展項。擴展項內容為AuthorityInfoAccessurls為指針數組,以NULL結束。

37OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *dgst, X509 *subject, X509 *issuer)

根據摘要算法、持有者證書和頒發者證書生成OCSP_CERTID數據結構。

38OCSP_CERTID *OCSP_cert_id_new(const EVP_MD *dgst,

                              X509_NAME *issuerName,

                              ASN1_BIT_STRING* issuerKey,

                              ASN1_INTEGER *serialNumber)

本函數根據摘要算法、頒發者名字、頒發者公鑰DER編碼以及證書持有者的證書序列號生成OCSP_CERTID;奇怪的是serialNumber可以為空,無法標識需要查詢狀態證書。

39int OCSP_id_issuer_cmp(OCSP_CERTID *a, OCSP_CERTID *b)

比較OCSP_CERTID,如果相等返回0,不相等返回非0。本函數不比較證書序列號。

40int 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響應碼獲取響應狀態信息。

43char *OCSP_cert_status_str(long s)

根據證書狀態碼獲取證書狀態信息。

44) char *OCSP_crl_reason_str(long s)

根據狀態碼獲取證書撤銷原因。

45int OCSP_REQUEST_print(BIO *bp, OCSP_REQUEST* o, unsigned long flags)

OCSP請求OCSP_REQUEST的信息輸出到bp,flags表明不支持到擴展項 的處理方式,參考X509V3_extensions_print以及X509V3_EXT_print函數。

46int OCSP_RESPONSE_print(BIO *bp, OCSP_RESPONSE* o, unsigned long flags)

OCSP請求OCSP_RESPONSE的信息輸出到bp,flags表明不支持到擴展項到處理方式,參考X509V3_extensions_print以及X509V3_EXT_print 函數。

47int OCSP_request_onereq_count(OCSP_REQUEST *req)

獲取OCSP請求中請求列表的個數,即多少個證書狀態需要查詢。

48OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i)

根據在堆棧中到位置獲取OCSP_ONEREQ,OCSP_ONEREQ包含了單個證書的信息。

49OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *one)

獲取OCSP_ONEREQ中到證書ID信息。

50int 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

52OCSP_RESPONSE *OCSP_response_create(int status, OCSP_BASICRESP *bs)

生成OCSP響應數據,status為響應狀態,bs為響應的具體內容。

53OCSP_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的堆棧中,並返回此狀態信息。

54int OCSP_basic_add1_cert(OCSP_BASICRESP *resp, X509 *cert)

添加一個證書到響應信息中。

55ASN1_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_STRINGi2d為輸入數據的i2d方法,data為輸入數據結構,sk為輸入對象堆棧。如果data不為空,則DER編碼data指向的數據結構;如果data為空,sk不為空,則DER編碼sk堆棧表示的內容。

56int OCSP_REQUEST_get_ext_count(OCSP_REQUEST *x)

獲取OCSP_REQUEST結構中tbsRequest成員的擴展項的個數。

57int OCSP_REQUEST_get_ext_by_NID(OCSP_REQUEST *x, int nid, int lastpos)

根據對象nid獲取擴展項在x->tbsRequest->requestExtensions中的位置。

58int OCSP_REQUEST_get_ext_by_OBJ(OCSP_REQUEST *x,

 ASN1_OBJECT *obj, int lastpos)

獲取對象在x->tbsRequest->requestExtensions中的位置。

59int OCSP_REQUEST_get_ext_by_critical(OCSP_REQUEST *x, int crit, int lastpos)

根據是否關鍵crit以及堆棧搜索基准lastpos獲取x->tbsRequest->requestExtensions中擴展項的位置。

60X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *x, int loc)

根據擴展項在堆棧中的位置獲取擴展項。

61X509_EXTENSION *OCSP_REQUEST_delete_ext(OCSP_REQUEST *x, int loc)

根據擴展項在堆棧中的位置刪除擴展項。

62void *OCSP_REQUEST_get1_ext_d2i(OCSP_REQUEST *x, int nid, int *crit, int *idx)

根據擴展項nid獲取擴展項信息,其中返回值為擴展項數據結構的指針地址,crit返回是否時關鍵擴展,idx表明它在堆棧中的位置。

63int 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

64int OCSP_REQUEST_add_ext(OCSP_REQUEST *x, X509_EXTENSION *ex, int loc)

將擴展項添加到x->tbsRequest->requestExtensions堆棧中,loc表示堆棧位置。

65int 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中的順序。

2X509_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數據結構,各項意義如下:

crlcrl信息主體;

sig_alg:簽名算法,與X509_CRL_INFO中的一致;

signature:簽名值;

references:引用。

上述三個結構的DER編解碼通過宏在crypto/asn1/x_crl.c中實現,包括newfreei2dd2i函數。

28.3 CRL函數

CRL函數主要是setget函數,如下:

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)

10int 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函數。

11int X509_CRL_add_ext(X509_CRL *x, X509_EXTENSION *ex, int loc)

添加擴展項到指定堆棧位置,此函數調用X509v3_add_ext,進行堆棧插入操作。

12int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b)

CRL比較,此函數調用X509_NAME_cmp,只比較頒發者的名字是否相同。

13X509_EXTENSION *X509_CRL_delete_ext(X509_CRL *x, int loc)

刪除CRL擴展項堆棧中的某一項,loc指定被刪除項在堆棧中的位置。

14int X509_CRL_digest(const X509_CRL *data, const EVP_MD *type,

 unsigned char *md, unsigned int *len)

CRL摘要,本函數對X509_CRL進行摘要,type指定摘要算法,摘要結果存放在md中,len表明摘要結果長度。

15X509_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函數。

17int X509_CRL_get_ext_by_critical(X509_CRL *x, int crit, int lastpos)

獲取擴展項在其堆棧中的位置,crit為擴展項是否關鍵標識,lastpos為堆棧搜索起始位置。此函數調用了X509v3_get_ext_by_critical。

18int 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)

同上。

20int X509_CRL_get_ext_count(X509_CRL *x)

獲取crl中擴展項的個數。

21) int X509_CRL_verify(X509_CRL *a, EVP_PKEY *r)

驗證CRLEVP_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 數據結構

Opensslpkcs7實現在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中實現,包括newfreei2dd2i函數。

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

添加接收者信息。

6PKCS7_add_signer

添加一個簽名者信息。

7) KCS7_add_signed_attribute

給PKCS7_SIGNER_INFO添加屬性。

8) PKCS7_cert_from_signer_info

pkcs7消息中根據頒發者和證書序列號獲取證書。

9) PKCS7_ctrl

控制函數。

10PKCS7_dataDecode

解析輸入的pkcs7消息,將結果存入BIO鏈表並返回。

11PKCS7_dataInit/PKCS7_dataFinal

解析輸入的pkcs7消息,將結果存入BIO

12PKCS7_dataVerify

驗證pkcs7數據。

13PKCS7_sign

簽名pkcs7消息。

14) PKCS7_verify

驗證pkcs7消息。

15PKCS7_set_type

設置pkcs7消息類型。

16PKCS7_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實現

opensslpkcs12實現在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.cpkcs12各個數據結構的DER編解碼實現;
  • p12_crpt.c:pkcs12pbe(基於口令的加密)函數;
  • p12_decr.c.cpkcs12pbe解密;
  • p12_key.c:根據用戶口令生成對稱密鑰;
  • p12_mutl.cpkcs12MAC信息處理;
  • p12_utl.c:一些通用的函數。

30.3數據結構

數據結構定義在crypto/pkcs12/pkcs12.h中,如下所示:

1PKCS12_MAC_DATA

typedef struct 

{

X509_SIG *dinfo;

ASN1_OCTET_STRING *salt;

ASN1_INTEGER *iter;

} PKCS12_MAC_DATA;

該結構用於存放pkcs12中的MAC信息,防止他人篡改。xinfo用於存放MAC值和摘要算法,saltiter用於根據口令來生成對稱密鑰(pbe)

2PKCS12

typedef struct 

{

ASN1_INTEGER *version;

PKCS12_MAC_DATA *mac;

PKCS7 *authsafes;

} PKCS12;

pkcs12數據結構,version為版本,mac用於存放MAC信息以及對稱密鑰相關的信息authsafes為pkcs7結構,用於存放的證書、crl以及私鑰等各種信息。

3PKCS12_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;

該結構用於存放各種實體對象。

4PKCS12_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為口令長度,macmaclen用於存放MAC值。當p12pkcs7為數據類型時,本函數有效。

2) int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)

驗證pkcs12MACpass為用戶口令,passlen為口令長度。PKCS12MAC值存放在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

6unsigned 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為輸入數據長度,datadatalen用於存放結果,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並返回。

10PKCS12_unpack_p7encdata

上述過程的逆過程。

11) int PKCS12_newpass(PKCS12 *p12, char *oldpass, char *newpass)

替換pkcs12的口令。

12PKCS12_setup_mac

設置pkcs12MAC數據結構。

13PKCS12_set_mac

設置pkcs12MAC信息。

14PKCS12_pack_authsafes

pkcs7堆棧信息打包到pkcs12中。

15PKCS12_unpack_authsafes

上面函數的逆過程,從pkcs12中解出pkcs7堆棧,並返回。

16) PKCS12 *PKCS12_init(int mode)

生成一個pkcs12數據結構,mode的值必須為NID_pkcs7_data,即pkcs12中的pkcs7類型必須是data類型。

17PKCS12_PBE_add

加載各種pbe算法。

18PKCS12_PBE_keyivgen

根據口令生成對稱密鑰,並做加解密初始化。

19PKCS12_item_pack_safebag

將輸入的數據打包為PKCS12_SAFEBAG並返回。

20PKCS12_x5092certbag

將證書打包為PKCS12_SAFEBAG並返回。

21PKCS12_certbag2x509

上述過程的逆過程。

22PKCS12_x509crl2certbag

crl打包為PKCS12_SAFEBAG並返回。

23PKCS12_certbag2x509crl

上述過程的逆過程。

24PKCS12_item_i2d_encrypt

將數據結構DER編碼,然后加密,數據存放在ASN1_OCTET_STRING中並返回。

24PKCS12_item_decrypt_d2i

上面函數的逆過程,解密輸入數據,然后DER解碼出數據結構,並返回。

25int PKCS12_add_friendlyname_uni(PKCS12_SAFEBAG *bag,

                                 const unsigned char *name, int namelen)

給PKCS12_SAFEBAG添加一個屬性,屬性類型為NID_friendlyName,nameunicode編碼。

26int PKCS12_add_friendlyname_asc(PKCS12_SAFEBAG *bag, const char *name,

                                 int namelen)

給PKCS12_SAFEBAG添加一個屬性,屬性類型為NID_friendlyName,nameASCII碼。

27) PKCS12_get_friendlyname

上面函數的逆過程,返回一個ASCII碼值。

28PKCS12_add_CSPName_asc

給PKCS12_SAFEBAG添加一個NID_ms_csp_name屬性,輸入參數為ASCII碼。

29PKCS12_add_localkeyid

給PKCS12_SAFEBAG添加一個NID_localKeyID屬性。

30PKCS12_MAKE_SHKEYBAG

pkcs8密鑰轉化為PKCS12_SAFEBAG。

30PKCS8_PRIV_KEY_INFO *

PKCS12_decrypt_skey(PKCS12_SAFEBAG *bag, const char *pass,                                                                int passlen)

上面函數的逆過程,從bag中提取pkcs8密鑰信息。

30.5 編程示例

1pkcs12解碼

之一:

#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公司提出,包括sslv2sslv3兩個版本。當前形成標准的為了tls協議(rfc2246規范)DTLSrfc4347,用於支持UDP協議)。sslv3tls協議大致一樣,只是有一些細微的差別。實際應用中,用的最多的為sslv3

SSL協議能夠保證通信雙方的信道安全。它能提供數據加密、身份認證以及消息完整性保護,另外SSL協議還支持數據壓縮。

SSL協議通過客戶端和服務端握手來協商各種算法和密鑰。

31.2 openssl實現

SSL協議源碼位於ssl目錄下。它實現了sslv2sslv3TLS以及DTLSDatagram TLS,基於UDPTLS實現)。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_CTXSSLSSL_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握手階段和通信階段所應該采用的各種算法。這些算法包括:認證算法、密鑰交換算法、對稱算法和摘要算法等。

在握手初始化的時候,雙方都會導入各自所認可的多種加密套件。在握手階段,由服務端選擇其中的一種加密套件。

OpenSSLciphers命令可以列出所有的加密套件。openssl的加密套件在s3_lib.cssl3_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為加密套件IDSSL_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.cssl3_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字節,SSLv3TLSv1長度為32字節。此ID與安全無關,但是在服務端必須是唯一的。當需要session重用時,客戶端發送包含session idclientHello消息(無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 idid和 id_len為輸入的要查詢的session id,查詢哈希表ssl->ctx->sessions,如果匹配,返回1,否則返回0

2 int ssl_get_new_session(SSL *s, int session)

生成ssl用的session,此函數可用被服務端或客戶端調用,當服務端調用時,傳入參數session1,生成新的session;當客戶端調用時,傳入參數session0,只是簡單的將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

獲取證書驗證深度。

18SSL_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

19SSL_get_current_cipher

獲取當前的加密套件。

20SSL_get_fd

獲取鏈接句柄。

21SSL_get_peer_certificate

獲取對方證書。

22XXX_client/server_method

獲取各個版本的客戶端和服務端的SSL方法。

23SSL_read

讀取數據。

24) SSL_write

發送數據。

25SSL_set_fd

設置SSL的鏈接句柄。

26SSL_get_current_compression

獲取當前的壓縮算法的COMP_METHOD。

27SSL_get_current_expansion

獲取當前的解壓算法的COMP_METHOD。

28SSL_COMP_get_name

獲取壓縮/解壓算法的名稱。

29SSL_CTX_set/get_ex_data

設置/讀取用戶擴展數據。

30SSL_dup

復制函數。

31SSL_get_default_timeout

獲取默認超時時間。

32SSL_do_handshake

進行ssl握手。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第三十二章 Openssl命令

32.1概述

Openssl命令源碼位於apps目錄下,編譯的最終結果為opensslwindows下為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

指定E0x1001

-3

指定E3

-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.pemPEM格式的文件,可以用-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

顯示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_idSSL/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_serveropenssl提供的一個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。其中xxxCA證書持有者的哈希值,可通過x509 -hash命令獲得。

-CAfile file 

指定CA證書文件。

-state 

打印SSL握手狀態。

-debug 

打印更多的信息。

-nbio 

不采用BIO

-quiet 

不打印輸出信息。

-ssl2, -ssl3, -tls1

只采用某一種協。;

-no_ssl2, -no_ssl3, -no_tls1

不采用某種協議。

-www 

返回給用戶一個網頁,內容為SSL握手的一些內容

-WWW -HTTP

將某個文件作為網頁發回客戶端,例如clientURL請求是 https://myhost/page.html ,則把 ./page.html發回給client。如果不設置-www、-WWW 、-HTTP,客戶端在終端輸入任何字符,服務端都會響應同樣的字符給客戶端。

-rand file:file:...

設置隨機數種子文件,SSL協議握手中會生成隨機數,比如clienthelloserverhello消息。

-crlf 

將用戶在終端輸入的換行回車轉化成/r/n

連接命令,這些輸入不是程序運行選項,在程序運行過程中輸入,如下:

q

中斷當前連接,但不關閉服務。

中斷當前連接,退出程序。

 r 

重新協商。

R

重新協商,並且要求客戶端證書。

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,其中xxxCA證書持有者的哈希值,它通過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可以為smtppop3,用於郵件安全傳輸。

-rand file:file:...

設置隨機數種子文件,SSL協議握手中會生成隨機數,比如clienthelloserverhello消息中的隨機數。

-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撤銷原因,原因可以為:unspecifiedkeyCompromiseCACompromiseaffiliationChangedsupersededcessationOfOperationcertificateHoldremoveFromCRL。這些原因區分大小寫。

-crl_hold instruction

crl撤銷原因為certificateHold(證書掛起),采用此項來指定用戶行為。instruction的值可以是:holdInstructionNoneholdInstructionCallIssuerholdInstructionReject。比如用選項: -crl_hold holdInstructionReject時,指明用戶必須拒絕掛起的證書。

-crl_compromise time

crl撤銷原因為keyCompromise(密鑰泄露),設置密鑰泄露時間timeTime 采用通用時間格式: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

設置摘要算法:md5shasha1 或 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目錄下運行:

1CA

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.pemcert2.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

rsautlRSA工具。本指令能夠使用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工具,用於處里PMEDER格式的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.0XXXX為其持有者摘要值。

示例:

請先參考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)請先用reqca命令生成OCSP服務證書和私鑰,下面的OCSP服務證書為ocspservercert.pemOCSP服務簽名私鑰為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.5pkcs12,並指定加密算法,可采用算法包括:

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_timeopenss提供的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

指定25為發生器,默認為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密鑰格式,PEMDER

-outform

輸出文件格式,PEMDER

-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 添加標准;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM