蔡成杭
(北京江南天安科技有限公司 北京 100088)
OpenSSL[1]是一套應(yīng)用廣泛的、開(kāi)源的支持傳輸層安全協(xié)議的密碼學(xué)基礎(chǔ)庫(kù)和工具集合,囊括主要的密碼算法、常用的密鑰和證書(shū)封裝管理功能及SSL/TLS協(xié)議,并提供豐富的API,以供應(yīng)用系統(tǒng)集成、程序開(kāi)發(fā)、測(cè)試或其他目的使用.它廣泛地集成在各種類型的操作系統(tǒng)中(Linux、BSD家族、MacOS等),即使某些操作系統(tǒng)(Windows、傳統(tǒng)的Unix等)沒(méi)有將其集成為組件,通過(guò)源代碼下載,也可以十分輕松地構(gòu)建OpenSSL的開(kāi)發(fā)及應(yīng)用環(huán)境.
作為基礎(chǔ)組件之一,OpenSSL簡(jiǎn)潔、明了、豐富的應(yīng)用接口,可簡(jiǎn)單、便捷地構(gòu)筑安全領(lǐng)域等方面的應(yīng)用,從而深受廣大IT愛(ài)好者的喜愛(ài).因此,基于OpenSSL的應(yīng)用十分廣泛,特別是涉及到安全功能的應(yīng)用系統(tǒng)和中間件,許多都是基于OpenSSL來(lái)構(gòu)建的.如我們常用的SSH,Apache,Tomcat,Nginx,MySQL等知名系統(tǒng),都是依賴OpenSSL來(lái)構(gòu)建其安全體系的.
在中國(guó),也有大量的安全領(lǐng)域的應(yīng)用是依賴OpenSSL來(lái)構(gòu)造的.但原始OpenSSL中并不包含符合中國(guó)標(biāo)準(zhǔn)的商用密碼算法(簡(jiǎn)稱國(guó)密)和安全通信協(xié)議,構(gòu)建的安全應(yīng)用也不符合中國(guó)商用密碼行業(yè)標(biāo)準(zhǔn),不利于國(guó)產(chǎn)密碼的推廣.因此,通過(guò)對(duì)OpenSSL的改造,讓OpenSSL支持國(guó)產(chǎn)密碼算法及通信協(xié)議,這是十分必要的.
江南天安于2017年推出了天安版國(guó)密OpenSSL,將國(guó)產(chǎn)密碼算法及國(guó)密TLS協(xié)議[2]集成進(jìn)OpenSSL中,實(shí)現(xiàn)了支持國(guó)產(chǎn)密碼認(rèn)證體系以及國(guó)密TLS(以下簡(jiǎn)稱CNTLS)協(xié)議,可替換原來(lái)基于OpenSSL的上層應(yīng)用,也可在此基礎(chǔ)上便捷地構(gòu)建符合國(guó)密標(biāo)準(zhǔn)的應(yīng)用系統(tǒng).
本文以O(shè)penSSL的穩(wěn)定版本1.0.2h為例,詳細(xì)介紹支持國(guó)產(chǎn)密碼算法的OpenSSL在CentOS 6.5操作系統(tǒng)中的實(shí)現(xiàn)及應(yīng)用.
國(guó)產(chǎn)密碼OpenSSL除具備原始OpenSSL的功能特色外,同時(shí)兼具著以下幾點(diǎn):
1) 支持國(guó)產(chǎn)密碼算法,提供國(guó)產(chǎn)密碼算法開(kāi)發(fā)和應(yīng)用接口;
2) 包含國(guó)產(chǎn)密碼算法的對(duì)象標(biāo)識(shí)符;
3) 支持國(guó)產(chǎn)密碼認(rèn)證體系;
4) 實(shí)現(xiàn)國(guó)密TLS協(xié)議,提供國(guó)密TLS協(xié)議的開(kāi)發(fā)和應(yīng)用接口;
5) 支持以引擎或內(nèi)置的方式,實(shí)現(xiàn)需硬件支持的國(guó)產(chǎn)密碼算法,如:SM1,SSF33等.
國(guó)產(chǎn)密碼OpenSSL的系統(tǒng)架構(gòu)如圖1所示,可粗略分為:基礎(chǔ)函數(shù)庫(kù)、EVP密碼算法封裝接口庫(kù)、X509/PKCS認(rèn)證接口庫(kù)、SSL/TLS/CNTLS協(xié)議接口庫(kù)、應(yīng)用及開(kāi)發(fā)接口等.
圖1 國(guó)產(chǎn)密碼OpenSSL系統(tǒng)架構(gòu)及依賴關(guān)系圖
圖1中,基礎(chǔ)函數(shù)庫(kù)包括:國(guó)際對(duì)稱加密算法、信息摘要算法、公開(kāi)密鑰算法的軟實(shí)現(xiàn);國(guó)產(chǎn)對(duì)稱加密算法SM4[3]、信息摘要算法SM3[4]、公開(kāi)密鑰算法SM2[5](可視為橢圓曲線公鑰算法[6]的一條特定曲線)的軟實(shí)現(xiàn);硬件加速或?qū)崿F(xiàn)引擎接口;錯(cuò)誤處理接口;BIO抽象輸入/輸出接口;數(shù)據(jù)結(jié)構(gòu)等等.
EVP密碼算法封裝接口庫(kù)包括:對(duì)國(guó)際密碼算法、國(guó)產(chǎn)密碼算法、硬件實(shí)現(xiàn)算法的統(tǒng)一調(diào)用接口,它依賴于基礎(chǔ)函數(shù)庫(kù),同時(shí)也提供信息摘要、自動(dòng)完成公鑰算法的數(shù)字簽名和驗(yàn)證以及數(shù)字信封等等接口,供X509/PKCS,SSL/TLS/CNTLS接口庫(kù)調(diào)用.
X509/PKCS接口庫(kù):X509或PKCS認(rèn)證體系的標(biāo)準(zhǔn)接口庫(kù),此接口庫(kù)需支持國(guó)密碼認(rèn)證體系.
SSL/TLS/CNTLS接口庫(kù):提供SSL/TLS/標(biāo)準(zhǔn)接口,同時(shí)添加支持CNTLS的API及支持雙證書(shū)體系(目前只支持RSA和SM2的雙證書(shū))的接口API.
國(guó)產(chǎn)密碼算法對(duì)象標(biāo)識(shí)[7]簡(jiǎn)稱國(guó)密OID,用來(lái)在OpenSSL及標(biāo)準(zhǔn)的X509/PKCS認(rèn)證體系中標(biāo)識(shí)國(guó)產(chǎn)密碼算法及國(guó)產(chǎn)密碼算法的使用方式.
在原始的OpenSSL中集成國(guó)產(chǎn)密碼算法對(duì)象標(biāo)識(shí),需要在OpenSSL的原代碼中作如下修改:
1) 編輯OpenSSL源代碼目錄crypto/objects中的objects.txt文件,并在其尾部添加如下數(shù)行:
1 2 156 10197 1:SM-SCHEME:sm-scheme
sm-scheme 102 :SM1:sm1
:SM1-CBC:sm1-cbc
:SM1-ECB:sm1-ecb
:SM1-CFB:sm1-cfb
:SM1-OFB:sm1-ofb
sm-scheme 103 :SSF33:ssf33
:SSF33-CBC:ssf33-cbc
:SSF33-ECB:ssf33-ecb
:SSF33-CFB:ssf33-cfb
:SSF33-OFB:ssf33-ofb
sm-scheme 104 :SM4:sm4
:SM4-CBC:sm4-cbc
:SM4-ECB:sm4-ecb
:SM4-CFB:sm4-cfb
:SM4-OFB:sm4-ofb
sm-scheme 201 :ZUC:zuc
:EEA3-128:eea3-128
:EIA3-128:eia3-128
sm-scheme 301 :SM2:sm2
sm2 1 :sm2signature
sm2 2 :sm2keyagreement
sm2 3 :sm2encrypt
sm-scheme 401 :SM3:sm3
sm3 1 :SM3-DIGEST:sm3-digest
sm3 2 :HMAC-SM3:hmac-sm3
sm-scheme 501:SM2-SM3:sm3WithSM2Sign
sm-scheme 504:RSA-SM3:sm3WithRSAEncryption
sm-scheme 504:RSA-SM3-2:sm3WithRSA
2) 編輯OpenSSL源代碼目錄crypto/objects中的obj_xref.txt文件,并在其尾部添加如下數(shù)行:
sm3WithRSAEncryption sm3 rsaEncryption
sm3WithRSA sm3 rsa
sm3WithSM2Sign sm3 X9_62_id_ecPublicKey
3) 進(jìn)入OpenSSL源代碼目錄crypto/objects,依次執(zhí)行如下2條命令:
perl objects.pl objects.txt obj_mac.num obj_mac.h
perl objxref.pl obj_mac.num obj_xref.txt > obj_xref.h
這樣即可將國(guó)產(chǎn)密碼算法及其使用方法的對(duì)象標(biāo)識(shí)添加到OpenSSL中.
國(guó)產(chǎn)密碼算法已經(jīng)公開(kāi)的算法有:SM2公鑰算法、SM3信息摘要算法、SM4對(duì)稱密碼算法、祖沖之流密碼算法和SM9標(biāo)識(shí)密碼算法.
本文只介紹SM2、SM3和SM4算法的OpenSSL實(shí)現(xiàn),這些算法也是目前應(yīng)用最為廣泛的國(guó)產(chǎn)密碼算法.同時(shí),為兼顧被大量使用的SM1和SSF33算法,實(shí)現(xiàn)了SM1和SSF33算法的EVP接口,以便在需要時(shí),通過(guò)引擎的方式來(lái)使用硬件實(shí)現(xiàn)SM1和SSF33算法.
1.3.1SM4算法的實(shí)現(xiàn)
在國(guó)產(chǎn)密碼OpenSSL中,SM4對(duì)稱加密算法的實(shí)現(xiàn)分為基礎(chǔ)庫(kù)軟實(shí)現(xiàn)和EVP封裝接口實(shí)現(xiàn)2個(gè)部分.
1.3.1.1SM4基礎(chǔ)庫(kù)軟實(shí)現(xiàn)
SM4算法原理及FK,CK,SBOX的值請(qǐng)參見(jiàn):GM/T 0002—2012 《SM4分組密碼算法》,這里不再贅述.
1) 定義SM4密鑰數(shù)據(jù)結(jié)構(gòu)及常量,如下:
struct sm4_key_st
{
uint32_t key[32];
};
typedef struct sm4_key_st SM4_KEY;
const unsigned FK[4]={…};
const unsigned CK[32]={…};
const unsigned char SBOX[25]={…};
2) 定義相關(guān)的宏(也可以用函數(shù)實(shí)現(xiàn))
32 b循環(huán)位移:
#define RSL(A, I) (((A)<<(I))|((A)>>(32-(I))))
密鑰擴(kuò)展線性變換:
#define LCK(A) ((A)^(RSL((A), 13))^(RSL((A), 23)))
輪密鑰生成:
#define KERF_K(K0, K1, K2, K3, K4, CK, RK)
K4=(K1)^(K2)^(K3)^(CK), K4=NT(K4),
RK=K4=(K0)^LCK(K4)
加解密線性變換:
#define LC(A) ((A)^
(RSL((A), 2))^(RSL((A), 10))^
(RSL((A), 18))^(RSL((A), 24)))
加解密非線性變換:
#define NT(A)
((SBOX[((A)>>24)]<<24)|
(SBOX[(((A)>>16) & 0xFF)]<<16)|
(SBOX[(((A)>>8) & 0xFF)]<<8)|
(SBOX[((A) & 0xFF)]))
加解密輪處理:
#define RF_E(X0, X1, X2, X3, X4, RK)
X4=(X1)^(X2)^(X3)^(RK),
X4=NT(X4),
X4=(X0)^LC(X4)
3) 實(shí)現(xiàn)密鑰初始化函數(shù)
int SM4_set_key(const unsigned char*userKey, size_t length, SM4_KEY*key)
{
unsigned*rk=key->key;
unsigned K[5];
int loop;
for (loop=0; loop<4; loop++)
{
K[i]=__bswap_32(*((unsigned*)
(userKey+i*4))); /*需包含
endian.h*/
K[i] ^=FK[i];
}
for (loop=0; loop<32; loop++)
KERF_K(K[loop %5], K[(loop+1)
%5], K[(loop+2) %5],
K[(loop+3) %5],K[(loop+4) %5],
CK[loop], rk[loop]);
return 1;
}
4) 實(shí)現(xiàn)SM4加密函數(shù)
void SM4_encrypt(const unsigned char*in, unsigned char*out, const SM4_KEY*key)
{
const unsigned*rk=key->key;
unsigned X[5];
int loop;
for (loop=0; loop<4; loop++)
X[loop]=__bswap_32(*((unsigned*)
(in+loop*4)));
for (loop=0; loop<32; loop++)
RF_E(X[loop %5], X[(loop+1) %5],
X[(loop+2) %5], X[(loop+3) %5],
X[(loop+4) %5], rk[loop]);
for ((loop=0; loop<4; loop++)
*((unsigned*)(out+loop*4))=
X[loop];
}
5) 實(shí)現(xiàn)SM4解密函數(shù)
void SM4_decrypt(const unsigned char*in, unsigned char*out, const SM4_KEY*key)
{
const unsigned*rk=key->key;
unsigned X[5];
int loop;
for (loop=0; loop<4; loop++)
X[loop]=__bswap_32(*((unsigned*)
(in+loop*4)));
for (loop=0; loop<32; loop++)
RF_E(X[loop %5], X[(loop+1) %5],
X[(loop+2) %5], X[(loop+3) %5],
X[(loop+4) %5], rk[31-loop]);
for ((loop=0; loop<4; loop++)
*((unsigned*)(out+loop*4))=
X[loop];
}
到此為止,SM4基礎(chǔ)軟實(shí)現(xiàn)已經(jīng)完成.需要注意的是,在此實(shí)現(xiàn)的源代碼中,需要包括頭文件endian.h,否則是找不到函數(shù)__bswap_32的.
1.3.1.2SM4EVP封裝接口實(shí)現(xiàn)
SM4的EVP封裝接口需要實(shí)現(xiàn)4種加密模式,分別是ECB,CBC,CFB和OFB模式.其實(shí)現(xiàn)過(guò)程如下.
1) 定義EVP封裝的SM4數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct { SM4_KEY ks;} EVP_SM4_KEY;
2) 實(shí)現(xiàn)EVP_CIPHER結(jié)構(gòu)的成員函數(shù)init:
static int sm4_init(EVP_CIPHER_CTX*ctx, const unsigned char*key, const unsigned char*iv, int enc)
{
EVP_SM4_KEY*dat=(EVP_SM4_KEY*)
ctx->cipher_data;
SM4_set_key(key, 16, &(dat->ks));
return 1;
}
3) 實(shí)現(xiàn)EVP_CIPHER結(jié)構(gòu)的成員函數(shù)do_cipher,因?yàn)橐獙?shí)現(xiàn)4種模式,因此要實(shí)現(xiàn)4種版的do_cipher:
static int sm4_cbc(EVP_CIPHER_CTX*ctx, unsigned char*out, const unsigned char*in, size_t inl)
{
if (ctx->encrypt)
CRYPTO_cbc128_encrypt(in,out,length,
&((EVP_SM4_KEY*)ctx->
cipher_data)->ks,
ctx->iv,
(block128_f) SM4_encrypt);
else
CRYPTO_cbc128_decrypt(in,out,length,
&((EVP_SM4_KEY*)ctx->
cipher_data)->ks,
ctx->iv,
(block128_f) SM4_decrypt);
return 1;
}
static int sm4_ecb(EVP_CIPHER_CTX*ctx, unsigned char*out, const unsigned char*in, size_t inl)
{
size_t i, bl;
bl=ctx->cipher->block_size;
if (inl return 1; inl-=bl; if (ctx->encrypt) for (i=0; i<=inl; i+=bl) SM4_encrypt(in+i, out+i, &((EVP_SM4_KEY*)ctx-> cipher_data)->ks); else for (i=0; i<=inl; i+=bl) SM4_decrypt(in+i, out+i, &((EVP_SM4_KEY*)ctx-> cipher_data)->ks); return 1; } static int sm4_cfb(EVP_CIPHER_CTX*ctx, unsigned char*out, const unsigned char*in, size_t inl) { CRYPTO_cfb128_encrypt(in,out,inl, &((EVP_SM4_KEY*)ctx-> cipher_data)->ks, ctx->iv, &ctx->num, ctx->encrypt, (block128_f) SM4_encrypt); return 1; } static int sm4_ofb(EVP_CIPHER_CTX*ctx, unsigned char*out, const unsigned char*in, size_t inl) { CRYPTO_ofb128_encrypt(in,out,inl, &((EVP_SM4_KEY*)ctx-> cipher_data)->ks, ctx->iv, &ctx->num, (block128_f) SM4_encrypt); return 1; } 4) 填充EVP_CIPHER結(jié)構(gòu),實(shí)現(xiàn)SM4的4種EVP封閉接口: static const EVP_CIPHER sm4_ecb={ NID_sm4_ecb, 16,16, 0, EVP_CIPH_ECB_MODE, sm4_init, sm4_ecb, NULL, sizeof(EVP_SM4_KEY), NULL, NULL, NULL, NULL }; const EVP_CIPHER*EVP_sm4_ecb(void) { return &sm4_ecb; } static const EVP_CIPHER sm4_cbc={ NID_sm4_cbc, 16, 16, 16, EVP_CIPH_CBC_MODE, sm4_init, sm4_cbc, NULL, sizeof(EVP_SM4_KEY), NULL, NULL, NULL, NULL }; const EVP_CIPHER*EVP_sm4_cbc(void) {return &sm4_cbc;} static const EVP_CIPHER sm4_cfb={ NID_sm4_cfb, 1, 16, 16, EVP_CIPH_CFB_MODE, sm4_init, sm4_cfb, NULL, sizeof(EVP_SM4_KEY), NULL, NULL, NULL, NULL }; const EVP_CIPHER*EVP_sm4_cfb(void) {return &sm4_cfb;} static const EVP_CIPHER sm4_ofb={ NID_sm4_ofb, 1, 16, 16, EVP_CIPH_OFB_MODE, sm4_init, sm4_ofb, NULL, sizeof(EVP_SM4_KEY), NULL, NULL, NULL, NULL }; const EVP_CIPHER*EVP_sm4_ofb(void) {return &sm4_ofb;} 5) 修改OpenSSL源代碼目錄crypto/evp中的evp.h,并添加對(duì)SM4 EVP封裝接口的4種模式的定義,如下所示: const EVP_CIPHER*EVP_sm4_ecb(void); const EVP_CIPHER*EVP_sm4_cbc(void) const EVP_CIPHER*EVP_sm4_cfb(void) const EVP_CIPHER*EVP_sm4_ofb(void) 6) 修改OpenSSL源代碼目錄crypto/evp中的c_allc.c,并將SM4 EVP封裝接口的4種模式加入到OpenSSL系統(tǒng)中去,如下所示: EVP_add_cipher(EVP_sm4_cbc()); EVP_add_cipher(EVP_sm4_cfb()); EVP_add_cipher(EVP_sm4_ecb()); EVP_add_cipher(EVP_sm4_ofb()); EVP_add_cipher_alias(SN_sm4_cbc, ″SM4″); 其中,在EVP封裝的接口中,EVP_sm4_cbc作為SM4的默認(rèn)算法. 1.3.2SM1和SSF33的EVP接口 參照SM4 EVP封裝接口的實(shí)現(xiàn),實(shí)現(xiàn)SM1和SSF33的EVP接口,并將其中的EVP_CIPHER的成員函數(shù)init,do_cipher置NULL(空)即可. 1.3.3SM3摘要算法的實(shí)現(xiàn) 在國(guó)產(chǎn)密碼OpenSSL中,SM3作為信息摘要函數(shù),也分為基礎(chǔ)庫(kù)軟實(shí)現(xiàn)和EVP封裝接口的實(shí)現(xiàn). 1.3.3.1SM3基礎(chǔ)庫(kù)軟實(shí)現(xiàn) SM3算法的原理請(qǐng)參見(jiàn)GM/T 0004—2012 《SM3密碼雜湊算法》. 1) 定義SM3算法的結(jié)構(gòu)如下所示: typedef struct SM3state_st { SM3_LONG digest[8]; SM3_LONG Nl, Nh; SM3_LONG data[64]; unsigned int num; } SM3_CTX; 2) 實(shí)現(xiàn)SM3初始化函數(shù)如下所示: int SM3_Init(SM3_CTX*c) { memset(c, 0, sizeof(SM3_CTX)); c->digest[0]=0x7380166F; c->digest[1]=0x4914B2B9; c->digest[2]=0x172442D7; c->digest[3]=0xDA8A0600; c->digest[4]=0xA96F30BC; c->digest[5]=0x163138AA; c->digest[6]=0xE38DEE4D; c->digest[7]=0xB0FB0E4E; return 1; } 3) 在SM3實(shí)現(xiàn)的源代碼文件中,通過(guò)如下定義來(lái)實(shí)現(xiàn)SM3算法: static void SM3_block_data_order(SM3_CTX*ctx, const void*in, size_t num); #define DATA_ORDER_IS_BIG_ENDIAN #define HASH_LONG SM3_LONG #define HASH_CTX SM3_CTX #define HASH_CBLOCK SM3_CBLOCK #define HASH_MAKE_STRING(c, s) do { SM3_LONG ll; unsigned int nn; for (nn=0; nn 4; nn++) { ll=(c)->digest[nn]; (void)HOST_l2c(ll, (s)); } } while (0) #define HASH_UPDATE SM3_Update #define HASH_TRANSFORM SM3_Transform #define HASH_FINAL SM3_Final #define HASH_BLOCK_DATA_ORDER SM3_block_data_order #include ″md32_common.h″ 4) 在SM3_block_data_order函數(shù)中,實(shí)現(xiàn)消息擴(kuò)展及消息壓縮如下: #define RSL(A, I) (((A)<<(I))|((A)>>(32-(I)))) #define FF0_15(X, Y, Z) ((X)^(Y)^(Z)) #define FF16_63(X, Y, Z) (((X) & (Y))|((X) & (Z))|((Y) & (Z))) #define GG0_15(X, Y, Z) ((X)^(Y)^(Z)) #define P0(X) ((X)^RSL((X), 9)^RSL((X), 17)) #define P1(X) ((X)^RSL((X), 15)^RSL((X), 23)) static void SM3_block_data_order(SM3_CTX*ctx, const void*in, size_t num) { int j; SM3_LONG W[68], W1[64]; SM3_LONG A, B, C, D, E, F, G, H, SS1, SS2, TT1, TT2, T0_15, T16_63; const unsigned char*pblock=(const unsigned char*)in; while (num--) { for (j=0; j<16; j++) { HOST_c2l(pblock, W[j]); } for (j=16; j<68; j++) { W[j]=W[j-16]^W[j-9]^ RSL(W[j-3], 15); W[j]=P1(W[j])^ RSL(W[j-13], 7)^W[j-6]; } for (j=0; j<64; j++) { W1[j]=W[j]^W[j+4]; } A=ctx->digest[0], B=ctx->digest[1], C=ctx->digest[2], D=ctx->digest[3]; E=ctx->digest[4], F=ctx->digest[5], G=ctx->digest[6], H=ctx->digest[7]; T0_15=0x79CC4519, T16_63= 0x7A879D8A; for (j=0; j<16; j++) { SS1=RSL(A, 12)+E+ RSL(T0_15, j), SS1=RSL(SS1, 7); SS2=SS1^RSL(A, 12); TT1=FF0_15(A, B, C)+D+ SS2+W1[j]; TT2=GG0_15(E, F, G)+H+ SS1+W[j]; D=C; C=RSL(B, 9); B=A; A=TT1; H=G; G=RSL(F, 19); F=E; E=P0(TT2); } for (j=16; j<64; j++) { SS1=RSL(A, 12)+E+ RSL(T16_63, (j % 32)), SS1=RSL(SS1, 7); SS2=SS1^RSL(A, 12); TT1=FF16_63(A, B, C)+D+ SS2+W1[j]; TT2=GG16_63(E, F, G)+H+ SS1+W[j]; D=C; C=RSL(B, 9); B=A; A=TT1; H=G; G=RSL(F, 19); F=E; E=P0(TT2); } ctx->digest[0] ^=A; ctx->digest[1] ^=B; ctx->digest[2] ^=C; ctx->digest[3] ^=D; ctx->digest[4] ^=E; ctx->digest[5] ^=F; ctx->digest[6] ^=G; ctx->digest[7] ^=H; } } 到此為止,SM3信息摘要算法的基礎(chǔ)庫(kù)軟實(shí)現(xiàn)完成. 1.3.3.2SM3EVP封裝接口實(shí)現(xiàn) SM3算法EVP封裝接口的實(shí)現(xiàn),首先需要實(shí)現(xiàn)結(jié)構(gòu)EVP_MD的成員函數(shù)init,update,final;然后需要填充EVP_MD的數(shù)據(jù)結(jié)構(gòu)的每一項(xiàng),實(shí)現(xiàn)EVP_sm3();然后在evp.h中申明EVP_sm3(void)接口,最后在c_alld.c文件中,將EVP_sm3()加載到OpenSSL的EVP接口庫(kù)中. 1) EVP_MD成員函數(shù)的實(shí)現(xiàn) static int init(EVP_MD_CTX*ctx) { return SM3_Init(ctx->md_data); } static int update(EVP_MD_CTX*ctx, const void*data, size_t count) { return SM3_Update(ctx->md_data, data, count); } static int final(EVP_MD_CTX*ctx, unsigned char*md) { return SM3_Final(md, ctx->md_data); } 2) 填充EVP_MD結(jié)構(gòu),并實(shí)現(xiàn)EVP_sm3() static const EVP_MD sm3_md={ NID_sm3, 0, 32, EVP_MD_FLAG_PKEY_METHOD_SIGNATURE|EVP_MD_FLAG_DIGALGID_ABSENT, init, update, final, NULL, NULL, EVP_PKEY_NULL_method, 64, sizeof(EVP_MD*)+sizeof(SM3_CTX), NULL }; const EVP_MD*EVP_sm3(void) {return (&sm3_md);} 3) 在OpenSSL源代碼目錄crypto/evp中,修改文件c_alld.c,以便加載EVP_sm3()接口 EVP_add_digest(EVP_sm3()); EVP_add_digest_alias(SN_sm3WithRSA Encryption, SN_sm3WithRSA). 1.3.4SM2公鑰算法的實(shí)現(xiàn) 國(guó)產(chǎn)密碼算法SM2公鑰算法是一條特定的曲線橢圓曲線公鑰算法,它基于ECC的通用算法,按照中國(guó)國(guó)家密碼管理局發(fā)布的GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》標(biāo)準(zhǔn),定義了SM2簽名[8]、驗(yàn)證、公鑰加密[9]、私鑰解密、密鑰協(xié)商[10]算法5種算法. 其中,SM2簽名/驗(yàn)證算法,是對(duì)原始信息經(jīng)過(guò)SM3算法做摘要后的結(jié)果進(jìn)行處理的.按照GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》(第二部分的6.1節(jié))要求,先要計(jì)算Z值,然后再將Z值和原始信息一起做摘要,最后對(duì)摘要值進(jìn)行簽名/驗(yàn)證. 因此,SM2公鑰算法中還需要定義計(jì)算Z值的函數(shù). 1.3.4.1Z值的計(jì)算 按照GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》(第二部分的6.1節(jié))所述,Z值的計(jì)算如下: ZA=H256(ENT LA‖IDA‖a‖b‖xG‖xG‖ xA‖yA), 其中,H256指的是256 b的信息摘要算法,在國(guó)產(chǎn)密碼算法中,此處只取值為SM3信息摘要算法;ENT LA是指由可辨別標(biāo)識(shí)IDA的位(bit)數(shù),轉(zhuǎn)換而成的2 B;IDA為用戶可辨別標(biāo)識(shí),當(dāng)此標(biāo)識(shí)不存在或者未提供時(shí),其值默認(rèn)為:1234567812345678;a,b為素域橢圓曲線方程y2=x3+ax+b的參數(shù)[11]; a的值為FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC;b的值為28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93;xG,xG為基點(diǎn)坐標(biāo),其值為:Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7,Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0;xA,yA為簽名者的公鑰坐標(biāo). Z值計(jì)算函數(shù)原型為:int ECDSA_sm2_get_Z(const EC_KEY*ec_key, const EVP_MD*md, const char*uid, int uid_len, unsigned char*z_buf, size_t*z_len). 其實(shí)現(xiàn)很簡(jiǎn)單,在此略過(guò). 1.3.4.2SM2簽名驗(yàn)證算法實(shí)現(xiàn) 國(guó)產(chǎn)密碼OpenSSL中的SM2簽名、驗(yàn)證算法函數(shù),依據(jù)《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第2部分:數(shù)字簽名算法》中所述的原理,按照OpenSSL接口的方式,設(shè)計(jì)如下: 1) 數(shù)字簽名生成函數(shù) ① 在OpenSSL中,SM2簽名的對(duì)象是經(jīng)過(guò)SM3做過(guò)摘要的結(jié)果,因此,其原型設(shè)計(jì)為 ECDSA_SIG*sm2_do_sign(const unsigned char*dgst, int dgst_len, EC_KEY*eckey), 其中,dgst為信息摘要值,長(zhǎng)度為32 B;eckey為簽名的SM2密鑰對(duì);成功簽名后返回ECDSA_SIG數(shù)據(jù)結(jié)構(gòu),否則返回NULL值. ② 函數(shù)的簽名過(guò)程和其實(shí)現(xiàn)代碼如下. S0:將輸入的參數(shù)摘要值的數(shù)據(jù)類型轉(zhuǎn)換為整數(shù)e;實(shí)現(xiàn)如下(e為大數(shù)變量): BN_bin2bn(dgst, dgst_len, e); S1:用隨機(jī)數(shù)發(fā)生器產(chǎn)生隨機(jī)數(shù)k ∈[1,n-1];實(shí)現(xiàn)如下(k為大數(shù)變量,order為SM2參數(shù)基點(diǎn)G的階): do { if (!BN_rand_range(k, order)) return NULL; } while (BN_is_zero(k)); S2:計(jì)算橢圓曲線點(diǎn)(x1,y1)=[k]G,并將x1的數(shù)據(jù)類型轉(zhuǎn)換為整數(shù);其實(shí)現(xiàn)為(group為SM2曲線的GROUP值,tmp_point為EC_POINT的變量,x1為大數(shù)變量): if (!EC_POINT_mul(group, tmp_point, k, NULL, NULL, NULL)) return NULL; if (!EC_POINT_get_affine_coordinates_ GFp(group, tmp_point, x1, NULL, NULL)) return NULL; S3:計(jì)算r=(e+x1) modn,若r=0或r+k=n則返回S1;其實(shí)現(xiàn)為: if (!BN_mod_add(r, e, x1, order, NULL)) return NULL; if (!BN_mod_add(x1, ret->r, k, order, NULL)) return NULL; if (BN_is_zero(r)‖BN_is_zero(x1)) goto S1; S4:計(jì)算s=((1+dA)-1*(k-r *dA)) mod n,若s=0則返回S1;其實(shí)現(xiàn)為(s為大數(shù)變量,d為SM2密鑰對(duì)中的私鑰): if (!BN_mod_add(x1, d, BN_value_one(), order, NULL)) return NULL; if (!BN_mod_inverse(s, x1, order, NULL)) return NULL; if (!BN_mod_mul(x1, r, d, order, NULL)) return NULL; if (!BN_mod_sub(x1, k, x1, order, NULL)) return NULL; if (!BN_mod_mul(s, s, x1, order, NULL)) return NULL if (BN_is_zero(ret->s)) goto S1; S5:將r, s的值填充ECDSA_SIG結(jié)構(gòu),返回ECDSA_SIG結(jié)構(gòu).實(shí)現(xiàn)如下: ECDSA_SIG*sig; sig->r=r; sig->s=s; return sig; 2) 數(shù)字簽名驗(yàn)證函數(shù) ① 國(guó)產(chǎn)密碼OpenSSL中,SM2數(shù)據(jù)簽名驗(yàn)證函數(shù)的原型設(shè)計(jì)為 int sm2_do_verify(const unsigned char*dgst, int dgst_len, const ECDSA_SIG*sig, EC_KEY*eckey), 其中,dgst為原始信息的摘要值;sig為需要驗(yàn)證的簽名值;驗(yàn)證成功返回1,失敗返回0. ② 國(guó)產(chǎn)密碼OpenSSL中,SM2數(shù)據(jù)簽名驗(yàn)證流程和實(shí)現(xiàn)代碼如下. B0:將輸入的摘要值轉(zhuǎn)換成 e1;實(shí)現(xiàn)如下(e1為大數(shù)變量): if (!BN_bin2bn(dgst, dgst_len, e1)) return 0; B1:檢驗(yàn)sig->r∈[1,n-1]是否成立,若不成立則驗(yàn)證不通過(guò); B2:檢驗(yàn)sig->s∈[1,n-1]是否成立,若不成立則驗(yàn)證不通過(guò);實(shí)現(xiàn)如下(order為SM2曲線參數(shù),為基點(diǎn)G的階): if (BN_is_zero(e1)) return 0; if (BN_ucmp(e1, order)>=0) return 0; B3:計(jì)算t=(sig->r+ sig->s) mod n,若t=0,則驗(yàn)證不通過(guò); if (!BN_mod_add(t, sig->r, sig->s, order, NULL)) return 0; if (BN_is_zero(t)) return 0; B4:計(jì)算橢圓曲線點(diǎn)(x1, y1)=[sig->s]G+[t]PA;其實(shí)現(xiàn)如下(group為SM2曲線的GROUP值,point為EC_POINT類型變量,pub_key為驗(yàn)證用的SM2公鑰): if (!EC_POINT_mul(group, point, sig-> s, pub_key, t, NULL)) return 0; B5:將x1的數(shù)據(jù)類型轉(zhuǎn)換為整數(shù),計(jì)算R=(e1+x1) mod n,檢驗(yàn)R=sig->r是否成立,若成立則驗(yàn)證通過(guò);否則驗(yàn)證不通過(guò),其實(shí)現(xiàn)如下(R為大數(shù)變量): if (!EC_POINT_get_affine_coordinates_ GFp(group, point, x1, NULL, NULL)) return 0; if (!BN_nnmod(x1, x1, order, NULL)) return 0; if (!BN_mod_add(R, e1, x1, order, NULL)) return 0; return (BN_ucmp(R, sig->r)==0); 1.3.4.3SM2公鑰加密及密鑰協(xié)商算法實(shí)現(xiàn) SM2公鑰加密算法包括:公鑰加密算法、私鑰解密算法,其原理可參見(jiàn)《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第4部分:公鑰加密算法》.其中涉及到密鑰派生函數(shù)KDF,可依據(jù)ANSI X9.63—2001規(guī)范實(shí)現(xiàn)(與OpenSSL 1.0.2h中ECDH_KDF_X9_62的實(shí)現(xiàn)兼容).設(shè)計(jì)其原型為: SM2ENC*sm2_encrypt(const unsigned char*in, size_t inlen, const EVP_MD*md, EC_KEY*ec_key); int sm2_decrypt(unsigned char*out, size_t*outlen, const SM2ENC*in, const EVP_MD*md, EC_KEY*ec_key); int KDF_GMT003_2012(unsigned char*out, size_t outlen, const unsigned char*Z, size_t Zlen, const unsigned char*SharedInfo, size_t SharedInfolen, const EVP_MD*md); 其中,SM2ENC的數(shù)據(jù)結(jié)構(gòu)為: struct sm2enc_st { ASN1_INTEGER*x; ASN1_INTEGER*y; ASN1_OCTET_STRING*m; ASN1_OCTET_STRING*c; }; typedef struct sm2enc_st SM2ENC; SM2密鑰協(xié)商算法的原理可參見(jiàn)《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第3部分:密鑰交換協(xié)議》.在OpenSSL中的設(shè)計(jì)原型為: int SM2Kap_compute_key(void*out, size_t outlen, int server; const char*peer_uid, int peer_uid_len, const char*self_uid, int self_uid_len; const EC_KEY*peer_ecdhe_key, const EC_KEY*self_ecdhe_key; const EC_KEY*peer_pub_key, const EC_KEY*self_eckey, const EVP_MD*md). 其中:out為協(xié)商的結(jié)果,其輸出長(zhǎng)度由outlen指定;server為指示出己方是發(fā)起方/響應(yīng)方標(biāo)識(shí),0為發(fā)起方;非0為響應(yīng)方;peer_uid為對(duì)方可辨別標(biāo)識(shí),其長(zhǎng)度由peer_uid_len指定;如果此項(xiàng)為NULL,或者peer_uid_len為0,此標(biāo)識(shí)取默認(rèn)值:1234567812345678;self_uid為己方可辨別標(biāo)識(shí),其長(zhǎng)度由self_uid_len指定,如果此項(xiàng)為NULL,或者self_uid_len為0,此標(biāo)識(shí)取默認(rèn)值:1234567812345678;peer_ecdhe_key為對(duì)方SM2臨時(shí)公鑰;self_ecdhe_key為己方SM2臨時(shí)公鑰;peer_pub_key為對(duì)方SM2證書(shū)公鑰;self_eckey為己方SM2私鑰;md為指定的摘要算法,對(duì)于SM2來(lái)說(shuō),此值默認(rèn)為EVP_sm3(). 協(xié)商成功返回1,否則返回0或者負(fù)值. 由于篇幅所限,本節(jié)所有函數(shù)根沒(méi)有給出實(shí)現(xiàn).如果有需要此源碼部分,可從https://github.com/jntass/TASSL獲取. 1.3.4.4SM2公鑰算法EVP接口實(shí)現(xiàn) 首先,國(guó)產(chǎn)密碼OpenSSL將SM2視為ECC的一條特定曲線,因此需要將SM2的算法內(nèi)置于ECC之中;也就是說(shuō),需要實(shí)現(xiàn)ECDSA調(diào)用接口、ECDH調(diào)用接口、EVP_PKEY的調(diào)用接口. 1) ECDSA調(diào)用接口 在OpenSSL源代碼目錄crypto/ecdsa的ecs_ossl.c文件,需要對(duì)函數(shù):ecdsa_do_sign,ecdsa_do_verify以及對(duì)ecdsa_sign_setup作出修改;修改方式如下: 在函數(shù)ecdsa_do_sign的變量申明之后,添加如下語(yǔ)句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return sm2_do_sign(dgst, dlen, a, b, eckey); 在函數(shù)ecdsa_do_verify的變量申明之后,添加如下語(yǔ)句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return sm2_do_verify(dgst, dgst_len, sig, eckey); 在函數(shù)ecdsa_sign_setup的變量申明之后,添加如下語(yǔ)句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return 1; 這樣即可實(shí)現(xiàn)ECDSA自動(dòng)調(diào)用SM2的簽名驗(yàn)證算法. 2) ECDH調(diào)用接口 SM2由于不支持ECDH算法,而由SM2密鑰協(xié)商取而代之,因此,需要防止SM2曲線運(yùn)用于ECDH算法中.修改方式如下: 在OpenSSL源代碼目錄crypto/ecdh的ech_ossl.c中,修改ecdh_compute_key函數(shù),在其變量申明之后,添加如下語(yǔ)句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ecdh))==NID_sm2) return-1; 3) EVP_PKEY的調(diào)用接口 此接口是通過(guò)修改OpenSSL源代碼目錄crypto/ec中的文件ec_pmeth.c來(lái)實(shí)現(xiàn)的,其實(shí)現(xiàn)方法為: ① 數(shù)據(jù)結(jié)構(gòu)EVP_PKEY_EC的尾部,添加如下成員: int server; char*peer_id; char*self_id; int peerid_len; int selfid_len; EC_KEY*peer_ecdhe_key; EC_KEY*self_ecdhe_key; int encdata_format; ② 在函數(shù)pkey_ec_init,pkey_ec_copy,pkey_ec_cleanup中需要對(duì)所添加的成員進(jìn)行初始化或者清理工作; ③ 需要添加pkey_ec_encrypt和pkey_ec_decrypt,以便實(shí)現(xiàn)EVP對(duì)SM2公鑰加密以及私鑰解密的調(diào)用; ④ 需要定義一些宏,以實(shí)現(xiàn)EVP接口對(duì)SM2的參數(shù)設(shè)置,并且在pkey_ec_ctrl中實(shí)現(xiàn)相關(guān)宏的功能,具體宏定義如下: # define EVP_PKEY_CTRL_SET_PEER_ID (EVP_PKEY_ALG_CTRL+11) # define EVP_PKEY_CTRL_SET_SELF_ID (EVP_PKEY_ALG_CTRL+12) # define EVP_PKEY_CTRL_SET_SERVER (EVP_PKEY_ALG_CTRL+13) # define EVP_PKEY_CTRL_SET_PEER_ECDHE (EVP_PKEY_ALG_CTRL+14) # define EVP_PKEY_CTRL_GEN_SELF_ECDHE (EVP_PKEY_ALG_CTRL+15) # define EVP_PKEY_CTRL_GET_SELF_ECDHE (EVP_PKEY_ALG_CTRL+16) # define EVP_PKEY_CTRL_SET_SELF_ECDHE (EVP_PKEY_ALG_CTRL+17) # define EVP_PKEY_CTRL_SET_ENCDATA (EVP_PKEY_ALG_CTRL+18) # define EVP_PKEY_CTX_set_sm2_peer_id(ctx, uid, uid_len) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SET_PEER_ID, uid_len, (void*)uid) # define EVP_PKEY_CTX_set_sm2_self_id(ctx, uid, uid_len) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SET_SELF_ID, uid_len, (void*)uid); # define EVP_PKEY_CTX_set_sm2_server_tag(ctx, tag) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SET_SERVER, tag, NULL) # define EVP_PKEY_CTX_set_sm2_peer_ecdhe(ctx, ecdhe) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SET_PEER_ECDHE, 0, (void*)ecdhe) # define EVP_PKEY_CTX_gen_sm2_ecdhe_key(ctx, ecdhe) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE| EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_GEN_SELF_ECDHE, 0, (void*)ecdhe) # define EVP_PKEY_CTX_get_sm2_ecdhe_key(ctx, ecdhe) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE| EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_GET_SELF_ECDHE, 0, (void*)ecdhe) # define EVP_PKEY_CTX_set_sm2_ecdhe_key(ctx, ecdhe) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_DERIVE| EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_SET_SELF_ECDHE, 0, (void*)ecdhe) # define EVP_PKEY_CTX_set_sm2_encdata_format(ctx, format) EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_ENCRYPT| EVP_PKEY_OP_DECRYPT, EVP_PKEY_CTRL_SET_ENCDATA, format, NULL) ⑤ 需要修改pkey_ec_kdf_derive函數(shù),增加對(duì)SM2密鑰協(xié)商的支持,修改方式為在其變量申明之后,添加如下代碼: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ctx->pkey->pkey.ec))==NID_sm2) { if (!ctx->pkey‖!ctx->peerkey) return 0; if (!key‖(*keylen==0)) return 0; outlen=*keylen; ret=SM2Kap_compute_key(key, outlen, dctx->server, dctx->peer_id, dctx->peerid_len, dctx->self_id, dctx->selfid_len, dctx->peer_ecdhe_key, dctx-> self_ecdhe_key, ctx->peerkey-> pkey.ec, ctx->pkey->pkey.ec, dctx-> kdf_md); if (ret<=0) return ret; return 1; } ⑥ 需要在數(shù)據(jù)結(jié)構(gòu)常量ec_pkey_meth中,對(duì)其成員encrypt,decrypt分別賦值為:pkey_ec_encrypt和pkey_ec_decrypt. 4) 國(guó)產(chǎn)密碼OpenSSL中,SM2密鑰需要能夠自動(dòng)完成對(duì)于原始信息的簽名和驗(yàn)證功能,但由于SM2對(duì)原始信息簽名和驗(yàn)證處理的特殊性,在做摘要之前,需要將簽名者的Z值對(duì)原始信息進(jìn)行補(bǔ)償運(yùn)算(即:將Z值與原始信息串接在一起),因此需要對(duì)OpenSSL源代碼目錄crypto/evp中的m_sigver.c進(jìn)行修改,以完成SM2的自動(dòng)簽名、驗(yàn)證流程.修改方法如下: 在do_sigver_init函數(shù)成功返回前,添加如下代碼: if (ctx->pctx->pkey->type==EVP_PKEY_EC) { if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ctx->pctx->pkey-> pkey.ec)) ==NID_sm2) { unsigned char ex_dgst[EVP_MAX_MD_ SIZE]; size_t ex_dgstlen=EVP_MAX_MD_ SIZE; if (!ECDSA_sm2_get_Z(ctx->pctx-> pkey->pkey.ec, type, NULL, 0, ex_dgst, &ex_dgstlen)) return 0; if (!EVP_DigestUpdate(ctx, ex_dgst, ex_dgstlen)) return 0; } } 1.3.4.5將SM2曲線內(nèi)置于OpenSSL中 關(guān)于國(guó)產(chǎn)密碼OpenSSL中,SM2公鑰算法的實(shí)現(xiàn)其實(shí)有一個(gè)前提,那就是SM2曲線必須為OpenSSL所支持的曲線. 原始的OpenSSL中,并不支持SM2曲線,因此需要通過(guò)修改源代碼的方式,將SM2曲線參數(shù)添加到OpenSSL中. 實(shí)現(xiàn)方法是修改OpenSSL源代碼目錄crypto/ec中的文件ec_curve.c.首先在此文件的數(shù)據(jù)結(jié)構(gòu)_ec_list_element_st之前,添加如下數(shù)據(jù)結(jié)構(gòu): static const struct { EC_CURVE_DATA h; unsigned char data[0+32*6]; } _EC_SM2={ { NID_X9_62_prime_field, 0, 32, 1 }, { /*not need seed*/ /*p=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF*/ 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /*a=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC*/ 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, /*b=28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93*/ 0x28, 0xE9, 0xFA, 0x9E, 0x9D, 0x9F, 0x5E, 0x34, 0x4D, 0x5A, 0x9E, 0x4B, 0xCF, 0x65, 0x09, 0xA7, 0xF3, 0x97, 0x89, 0xF5, 0x15, 0xAB, 0x8F, 0x92, 0xDD, 0xBC, 0xBD, 0x41, 0x4D, 0x94, 0x0E, 0x93, /*Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7*/ 0x32, 0xC4, 0xAE, 0x2C, 0x1F, 0x19, 0x81, 0x19, 0x5F, 0x99, 0x04, 0x46, 0x6A, 0x39, 0xC9, 0x94, 0x8F, 0xE3, 0x0B, 0xBF, 0xF2, 0x66, 0x0B, 0xE1, 0x71, 0x5A, 0x45, 0x89, 0x33, 0x4C, 0x74, 0xC7, /*Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0*/ 0xBC, 0x37, 0x36, 0xA2, 0xF4, 0xF6, 0x77, 0x9C, 0x59, 0xBD, 0xCE, 0xE3, 0x6B, 0x69, 0x21, 0x53, 0xD0, 0xA9, 0x87, 0x7C, 0xC6, 0x2A, 0x47, 0x40, 0x02, 0xDF, 0x32, 0xE5, 0x21, 0x39, 0xF0, 0xA0, /*n=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123*/ 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x72, 0x03, 0xDF, 0x6B, 0x21, 0xC6, 0x05, 0x2B, 0x53, 0xBB, 0xF4, 0x09, 0x39, 0xD5, 0x41, 0x23 } }; 然后,在數(shù)據(jù)結(jié)構(gòu)常量curve_list中,添加SM2曲線的數(shù)據(jù)(在任意正確位置都可以): {NID_sm2, &_EC_SM2.h, 0, ″SM2 curve over a 256 bit prime field″}. 到此為止,OpenSSL中即可全面地對(duì)國(guó)產(chǎn)密碼算法SM2公鑰算法進(jìn)行支持. 如果需要國(guó)產(chǎn)密碼OpenSSL支持其他的國(guó)密算法,可以借鑒以上所述的方式來(lái)進(jìn)行添加. 國(guó)密TLS協(xié)議的實(shí)現(xiàn)需要根據(jù)GM/T 0024—2014《SSL VPN技術(shù)規(guī)范》,并實(shí)現(xiàn)國(guó)產(chǎn)密碼認(rèn)證體系,在標(biāo)準(zhǔn)的SSL/TLS協(xié)議的基礎(chǔ)上來(lái)實(shí)現(xiàn). 通過(guò)對(duì)比標(biāo)準(zhǔn)的TLS1.1協(xié)議,國(guó)密TLS1.1協(xié)議有如下特點(diǎn): 1) 傳輸入層的版本號(hào)為0x0101,而不是標(biāo)準(zhǔn)的TLS1.1協(xié)議的0x0302; 2) 需要雙證書(shū)支持,即簽名證書(shū)和加密證書(shū)同時(shí)工作,而標(biāo)準(zhǔn)的TLS協(xié)議無(wú)需雙證書(shū)支持; 3) 客戶端需要獨(dú)立API接口來(lái)完成國(guó)密TLS的建鏈過(guò)程; 4) 無(wú)需校驗(yàn)對(duì)方是否支持SM2曲線,因?yàn)槭褂脟?guó)密套件,對(duì)方一定會(huì)支持SM2曲線. 1.5.1國(guó)密TLS協(xié)議握手流程 國(guó)密TLS協(xié)議其實(shí)主要體現(xiàn)在建鏈過(guò)程之中.圖2所示,是完整的建鏈過(guò)程.同時(shí),國(guó)密TLS協(xié)議也支持會(huì)話重用,重用流程如圖3所示. 圖2 國(guó)密TLS協(xié)議握手流程 圖3 會(huì)話重用流程 1.5.2國(guó)產(chǎn)密碼雙證書(shū)認(rèn)證體系的實(shí)現(xiàn) 在OpenSSL實(shí)現(xiàn)的SSL/TLS中,設(shè)計(jì)有支持RSA的雙證書(shū)認(rèn)證體系,但OpenSSL并沒(méi)有提供RSA雙證書(shū)體系的API;對(duì)于ECC的證書(shū)認(rèn)證體系來(lái)說(shuō),其簽名和數(shù)據(jù)加密或密鑰協(xié)商均使用同一套證書(shū)與其對(duì)應(yīng)的私鑰來(lái)完成. 而國(guó)產(chǎn)密碼認(rèn)證體系要求使用雙證書(shū)[12-14],無(wú)論是RSA證書(shū)還是SM2證書(shū).因此,國(guó)密TLS協(xié)議中所屬套件均要求使用雙證書(shū)認(rèn)證,即簽名證書(shū)與其對(duì)應(yīng)的私鑰只用于簽名和驗(yàn)證書(shū);而數(shù)據(jù)加密或者密鑰協(xié)商需要使用加密或者密鑰協(xié)商證書(shū)與其對(duì)應(yīng)的私鑰. 要在OpenSSL中實(shí)現(xiàn)國(guó)密TLS協(xié)議,就必須要添加對(duì)ECC(SM2其實(shí)也是一種特定的ECC)的雙證書(shū)認(rèn)證. 至于SM2雙證書(shū)在何時(shí)使用,可參見(jiàn)圖2.在國(guó)密TLS協(xié)議的握手過(guò)程之中,ServerKeyExchange和ClientKeyExchange過(guò)程需要用到加密或密鑰協(xié)商證書(shū)進(jìn)行密鑰協(xié)商;同時(shí)它也需要使用數(shù)字簽名證書(shū)(客戶端除外)進(jìn)行數(shù)字簽名;客戶端CertificateVerify過(guò)程需要使用數(shù)字簽名證書(shū)進(jìn)行數(shù)字簽名;而服務(wù)端GetClientKeyExchange和GetClientCertVerify過(guò)程需要使用數(shù)字簽名證書(shū)進(jìn)行簽名驗(yàn)證. 在國(guó)產(chǎn)密碼OpenSSL中,原始的SSL/TLS證書(shū)認(rèn)證體系A(chǔ)PI可直接用于數(shù)字簽名證書(shū)的認(rèn)證接口;針對(duì)RSA和SM2證書(shū),設(shè)計(jì)了如下的一套加密證書(shū)接口API: int SSL_use_enc_RSAPrivateKey(SSL*ssl, RSA*rsa); int SSL_use_enc_RSAPrivateKey_ASN1(SSL*ssl, unsigned char*d, long len); int SSL_use_enc_RSAPrivateKey_file(SSL*ssl, const char*file, int type); int SSL_use_enc_PrivateKey(SSL*ssl, EVP_PKEY*pkey); int SSL_use_enc_PrivateKey_ASN1(int type, SSL*ssl, const unsigned char*d, long len); int SSL_use_enc_PrivateKey_file(SSL*ssl, const char*file, int type); int SSL_CTX_use_enc_RSAPrivateKey(SSL_CTX*ctx, RSA*rsa); int SSL_CTX_use_enc_RSAPrivateKey_ASN1(SSL_CTX*ctx, const unsigned char*d, long len); int SSL_CTX_use_enc_RSAPrivateKey_file(SSL_CTX*ctx, const char*file, int type); int SSL_CTX_use_enc_PrivateKey(SSL_CTX*ctx, EVP_PKEY*pkey); int SSL_CTX_use_enc_PrivateKey_ASN1(int type, SSL_CTX*ctx, const unsigned char*d, long len) int SSL_CTX_use_enc_PrivateKey_file(SSL_CTX*ctx, const char*file, int type); 而針對(duì)OpenSSL中經(jīng)過(guò)適當(dāng)修改后,可適應(yīng)雙證書(shū)認(rèn)證體系的API如下: int SSL_use_certificate(SSL*ssl, X509*x); int SSL_use_certificate_file(SSL*ssl, const char*file, int type); int SSL_use_certificate_ASN1(SSL*ssl, const unsigned char*d, int len); int SSL_CTX_use_certificate(SSL_CTX*ctx, X509*x); int SSL_CTX_use_certificate_file(SSL_CTX*ctx, const char*file, int type); int SSL_CTX_use_certificate_ASN1(SSL_CTX*ctx, int len,const unsigned char*d); 由這2組API構(gòu)成了國(guó)密TLS雙證書(shū)認(rèn)證體系. 1.5.3國(guó)密TLS套件的實(shí)現(xiàn) 目前,國(guó)密TLS協(xié)議中定義了許多密碼套件,可用于建立國(guó)密TLS協(xié)議的鏈路.其中,SM9和RSA相關(guān)密碼套件由于使用不多或者有其局限性,這里不作論述,在此,著重說(shuō)明ECC-SM4-SM3(EC-SM1-SM3和它相同,只不過(guò)SM1需要硬件支持)和ECDHE-SM4-SM3(ECDHE-SM1-SM3和它相同,只不過(guò)SM1需要硬件支持)這2個(gè)密碼套件的實(shí)現(xiàn). 在國(guó)產(chǎn)密碼OpenSSL中,ECC-SM4-SM3,ECDHE-SM4-SM3這2個(gè)密碼套件的實(shí)現(xiàn),對(duì)于客戶端來(lái)說(shuō)需要實(shí)現(xiàn)SendClientKeyExchange和Get ServerKeyExchange這2個(gè)過(guò)程;而對(duì)于服務(wù)端來(lái)說(shuō),需要實(shí)現(xiàn)Send ServerKeyExchange和GetClientKeyExchange這2個(gè)過(guò)程. 1.5.3.1ECC-SM4-SM3密碼套件的實(shí)現(xiàn) ECC-SM4-SM3這個(gè)密碼套件是指客戶端無(wú)證書(shū),或者客戶端只有驗(yàn)證服務(wù)端證書(shū)的證書(shū)鏈(它一般指的是CA的證書(shū)),此時(shí)與國(guó)密TLS服務(wù)器所建的鏈就是使用ECC-SM4-SM3這個(gè)密碼套件. 此時(shí),SendServerKeyExchange過(guò)程作如下處理: 1) 對(duì)客戶端隨機(jī)數(shù)+服務(wù)端隨機(jī)數(shù)+服務(wù)端加密/協(xié)商證書(shū)進(jìn)行數(shù)字簽名; 2) 發(fā)送此簽名的ASN1的編碼結(jié)果. Get ServerKeyExchange過(guò)程作如下處理: 1) 取服務(wù)端加密證書(shū); 2) 對(duì)客戶端隨機(jī)數(shù)+服務(wù)端隨機(jī)數(shù)+服務(wù)端加密/協(xié)商證書(shū)進(jìn)行簽名驗(yàn)證; 3) 成則繼續(xù),否則中斷握手. SendClientKeyExchange過(guò)程作如下處理: 1) 在48 B的緩沖區(qū)中,填入0x0101(國(guó)密TLS的版本號(hào))+46 B的隨機(jī)數(shù),構(gòu)成預(yù)主密鑰; 2) 使用服務(wù)端加密證書(shū)對(duì)此預(yù)主密鑰加密; 3) 發(fā)送加密結(jié)果的ASN1的編碼結(jié)果. GetClientKeyExchange過(guò)程作如下處理: 1) 使用加密私鑰對(duì)客戶端發(fā)來(lái)的結(jié)果作解密運(yùn)算,得到預(yù)主密鑰; 2) 使用預(yù)主密鑰計(jì)算主密鑰; 3) 保留主密鑰,以便會(huì)話重用. 1.5.3.2ECDHE-SM4-SM3密碼套件的實(shí)現(xiàn) ECDHE-SM4-SM密碼套件是在服務(wù)端和客戶端之間,使用SM2密鑰協(xié)商協(xié)議,生成48 B主密鑰,然后再完成握手過(guò)程. 此時(shí),SendServerKeyExchange過(guò)程作如下處理: 1) 生成服務(wù)端臨時(shí)SM2密鑰對(duì),取臨時(shí)公鑰的字符串值; 2) 對(duì)臨時(shí)公鑰的內(nèi)容進(jìn)行簽名; 3) 將臨時(shí)公鑰和數(shù)字簽名發(fā)送給客戶端. Get ServerKeyExchange過(guò)程作如下處理: 1) 取服務(wù)端發(fā)送的數(shù)據(jù),并對(duì)其進(jìn)行簽名驗(yàn)證;如果驗(yàn)證失敗則中斷握手過(guò)程; 2) 取服務(wù)端臨時(shí)密鑰對(duì)并保存. SendClientKeyExchange過(guò)程作如下處理: 1) 生成客戶端臨時(shí)公鑰; 2) 使用默認(rèn)ID為1234567812345678,調(diào)用SM2密鑰協(xié)商過(guò)程,生成48 B的預(yù)主密鑰; 3) 向服務(wù)端發(fā)送客戶端的臨時(shí)公鑰. GetClientKeyExchange過(guò)程作如下處理: 1) 取客戶端發(fā)送的臨時(shí)公鑰; 2) 使用默認(rèn)ID為1234567812345678,調(diào)用SM2密鑰協(xié)商過(guò)程,生成48 B的預(yù)主密鑰; 3) 計(jì)算主密鑰并保存,以便會(huì)話重用. 國(guó)產(chǎn)密碼OpenSSL在保留OpenSSL特性的同時(shí),也將國(guó)產(chǎn)密碼的相關(guān)特性集成到其中,可以應(yīng)用到許多方面. 在世界范圍內(nèi),OpenSSL的使用極其廣泛.我國(guó)也有大量的應(yīng)用軟件、系統(tǒng)軟件是依賴于OpenSSL構(gòu)建的. 國(guó)產(chǎn)密碼OpenSSL不僅提供了國(guó)密算法函數(shù),同時(shí)也保留了原版OpenSSL的所有功能特點(diǎn),它可以無(wú)縫替換原版OpenSSL,包括命令行工具等等,從而實(shí)現(xiàn)應(yīng)用軟件的密碼算法從國(guó)際算法到國(guó)密算法的遷移. 目前在大部分國(guó)產(chǎn)操作系統(tǒng)中都集成了OpenSSL.通過(guò)使用國(guó)產(chǎn)密碼OpenSSL替換國(guó)產(chǎn)操作系統(tǒng)中原有的OpenSSL,以實(shí)現(xiàn)對(duì)國(guó)密算法的支持. 通過(guò)對(duì)國(guó)密OpenSSL源碼的分析和理解、對(duì)其API的應(yīng)用,再比照國(guó)密標(biāo)準(zhǔn),可以更容易地學(xué)習(xí)國(guó)密算法和國(guó)密規(guī)范,讓國(guó)密OpenSSL的使用者更加廣泛地、自由地使用國(guó)密算法,體會(huì)國(guó)密算法的優(yōu)越性、安全性. 通過(guò)國(guó)產(chǎn)密碼OpenSSL可以加深對(duì)國(guó)密認(rèn)證體系的認(rèn)識(shí).國(guó)密OpenSSL的雙證書(shū)體系不僅僅應(yīng)用于SM2證書(shū),同樣也可以應(yīng)用于RSA證書(shū).使國(guó)密雙證書(shū)的認(rèn)證體系可以有著更好的應(yīng)用前景. 國(guó)產(chǎn)密碼OpenSSL已經(jīng)實(shí)現(xiàn)了對(duì)國(guó)產(chǎn)密碼認(rèn)證體系的支持,參照我國(guó)關(guān)于CA系統(tǒng)的相關(guān)標(biāo)準(zhǔn),可以構(gòu)建符合中國(guó)標(biāo)準(zhǔn)的CA系統(tǒng). 國(guó)產(chǎn)密碼OpenSSL可以和nginx,HAproxy,apache等知名的開(kāi)源軟件配套使用,從而構(gòu)建支持國(guó)密算法的應(yīng)用環(huán)境.具體構(gòu)建方式為,將nginx,HAproxy等源代碼作適量修改,以支持國(guó)密雙證書(shū)體系及國(guó)密TLS協(xié)議,在國(guó)密OpenSSL的基礎(chǔ)上重新構(gòu)建. 本著回報(bào)社會(huì)、推動(dòng)國(guó)產(chǎn)密碼算法推廣的目的,江南天安已經(jīng)將天安版國(guó)產(chǎn)密碼OpenSSL作為TaSSL項(xiàng)目開(kāi)源(可以從https://github.com/jntass/TASSL獲取),本文中所有的實(shí)現(xiàn),或者是未提供源代碼實(shí)現(xiàn)的部分都可以從TaSSL開(kāi)源的項(xiàng)目中獲取,同時(shí)也有大量的示例程序,以供業(yè)界同仁參考,歡迎業(yè)界同仁和密碼技術(shù)愛(ài)好者下載使用,并且提供寶貴的意見(jiàn). 本文僅僅是拋磚引玉,志在引起OpenSSL愛(ài)好者對(duì)國(guó)密的興趣和熱情.由于篇幅所限,許多的話題言而未盡,愿與國(guó)密同仁及愛(ài)好者共勉之,一同為推動(dòng)國(guó)密事業(yè)而努力. [1]OpenSSL Software Foundation. OpenSSL cryptography and SSL/TLS toolkit[OL]. [2018-01-15]. https://www.openssl.org [2]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0024—2014 SSL VPN技術(shù)規(guī)范[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [3]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0002—2012 SM4分組密碼算法[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [4]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0004—2012 SM3密碼雜湊算法[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [5]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第1部分: 總則[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [6]Daniel R L, Brown. SEC 1: Elliptic curve cryptography[OL]. 2009 [2018-01-15]. http://www.secg.org/sec1-v2.pdf [7]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0006—2012 密碼應(yīng)用標(biāo)識(shí)規(guī)范[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [8]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第2部分: 數(shù)字簽名算法[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [9]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第4部分: 公鑰加密算法[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [10]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第3部分: 密鑰交換協(xié)議[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [11]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第5部分: 參數(shù)定義[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [12]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0009—2012 SM2密碼算法使用規(guī)范[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [13]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0014—2012 數(shù)字證書(shū)認(rèn)證系統(tǒng)密碼協(xié)議規(guī)范[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 2012 [14]中華人民共和國(guó)密碼行業(yè)標(biāo)準(zhǔn). GM/T 0015—2012 基于SM2密碼算法的數(shù)字證書(shū)格式規(guī)范[S]. 北京: 中國(guó)標(biāo)準(zhǔn)出版社, 20121.4 其他國(guó)密算法
1.5 國(guó)密TLS協(xié)議的實(shí)現(xiàn)
2 國(guó)產(chǎn)密碼OpenSSL的應(yīng)用
2.1 替換國(guó)產(chǎn)操作系統(tǒng)中的OpenSSL模塊
2.2 學(xué)習(xí)和推廣國(guó)密算法
2.3 構(gòu)建支持SM2證書(shū)簽發(fā)的CA認(rèn)證中心
2.4 與其他開(kāi)源軟件配套使用
3 結(jié)束語(yǔ)