蔡成杭
(北京江南天安科技有限公司 北京 100088)
OpenSSL[1]是一套應用廣泛的、開源的支持傳輸層安全協議的密碼學基礎庫和工具集合,囊括主要的密碼算法、常用的密鑰和證書封裝管理功能及SSL/TLS協議,并提供豐富的API,以供應用系統(tǒng)集成、程序開發(fā)、測試或其他目的使用.它廣泛地集成在各種類型的操作系統(tǒng)中(Linux、BSD家族、MacOS等),即使某些操作系統(tǒng)(Windows、傳統(tǒng)的Unix等)沒有將其集成為組件,通過源代碼下載,也可以十分輕松地構建OpenSSL的開發(fā)及應用環(huán)境.
作為基礎組件之一,OpenSSL簡潔、明了、豐富的應用接口,可簡單、便捷地構筑安全領域等方面的應用,從而深受廣大IT愛好者的喜愛.因此,基于OpenSSL的應用十分廣泛,特別是涉及到安全功能的應用系統(tǒng)和中間件,許多都是基于OpenSSL來構建的.如我們常用的SSH,Apache,Tomcat,Nginx,MySQL等知名系統(tǒng),都是依賴OpenSSL來構建其安全體系的.
在中國,也有大量的安全領域的應用是依賴OpenSSL來構造的.但原始OpenSSL中并不包含符合中國標準的商用密碼算法(簡稱國密)和安全通信協議,構建的安全應用也不符合中國商用密碼行業(yè)標準,不利于國產密碼的推廣.因此,通過對OpenSSL的改造,讓OpenSSL支持國產密碼算法及通信協議,這是十分必要的.
江南天安于2017年推出了天安版國密OpenSSL,將國產密碼算法及國密TLS協議[2]集成進OpenSSL中,實現了支持國產密碼認證體系以及國密TLS(以下簡稱CNTLS)協議,可替換原來基于OpenSSL的上層應用,也可在此基礎上便捷地構建符合國密標準的應用系統(tǒng).
本文以OpenSSL的穩(wěn)定版本1.0.2h為例,詳細介紹支持國產密碼算法的OpenSSL在CentOS 6.5操作系統(tǒng)中的實現及應用.
國產密碼OpenSSL除具備原始OpenSSL的功能特色外,同時兼具著以下幾點:
1) 支持國產密碼算法,提供國產密碼算法開發(fā)和應用接口;
2) 包含國產密碼算法的對象標識符;
3) 支持國產密碼認證體系;
4) 實現國密TLS協議,提供國密TLS協議的開發(fā)和應用接口;
5) 支持以引擎或內置的方式,實現需硬件支持的國產密碼算法,如:SM1,SSF33等.
國產密碼OpenSSL的系統(tǒng)架構如圖1所示,可粗略分為:基礎函數庫、EVP密碼算法封裝接口庫、X509/PKCS認證接口庫、SSL/TLS/CNTLS協議接口庫、應用及開發(fā)接口等.
圖1 國產密碼OpenSSL系統(tǒng)架構及依賴關系圖
圖1中,基礎函數庫包括:國際對稱加密算法、信息摘要算法、公開密鑰算法的軟實現;國產對稱加密算法SM4[3]、信息摘要算法SM3[4]、公開密鑰算法SM2[5](可視為橢圓曲線公鑰算法[6]的一條特定曲線)的軟實現;硬件加速或實現引擎接口;錯誤處理接口;BIO抽象輸入/輸出接口;數據結構等等.
EVP密碼算法封裝接口庫包括:對國際密碼算法、國產密碼算法、硬件實現算法的統(tǒng)一調用接口,它依賴于基礎函數庫,同時也提供信息摘要、自動完成公鑰算法的數字簽名和驗證以及數字信封等等接口,供X509/PKCS,SSL/TLS/CNTLS接口庫調用.
X509/PKCS接口庫:X509或PKCS認證體系的標準接口庫,此接口庫需支持國密碼認證體系.
SSL/TLS/CNTLS接口庫:提供SSL/TLS/標準接口,同時添加支持CNTLS的API及支持雙證書體系(目前只支持RSA和SM2的雙證書)的接口API.
國產密碼算法對象標識[7]簡稱國密OID,用來在OpenSSL及標準的X509/PKCS認證體系中標識國產密碼算法及國產密碼算法的使用方式.
在原始的OpenSSL中集成國產密碼算法對象標識,需要在OpenSSL的原代碼中作如下修改:
1) 編輯OpenSSL源代碼目錄crypto/objects中的objects.txt文件,并在其尾部添加如下數行:
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文件,并在其尾部添加如下數行:
sm3WithRSAEncryption sm3 rsaEncryption
sm3WithRSA sm3 rsa
sm3WithSM2Sign sm3 X9_62_id_ecPublicKey
3) 進入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
這樣即可將國產密碼算法及其使用方法的對象標識添加到OpenSSL中.
國產密碼算法已經公開的算法有:SM2公鑰算法、SM3信息摘要算法、SM4對稱密碼算法、祖沖之流密碼算法和SM9標識密碼算法.
本文只介紹SM2、SM3和SM4算法的OpenSSL實現,這些算法也是目前應用最為廣泛的國產密碼算法.同時,為兼顧被大量使用的SM1和SSF33算法,實現了SM1和SSF33算法的EVP接口,以便在需要時,通過引擎的方式來使用硬件實現SM1和SSF33算法.
1.3.1SM4算法的實現
在國產密碼OpenSSL中,SM4對稱加密算法的實現分為基礎庫軟實現和EVP封裝接口實現2個部分.
1.3.1.1SM4基礎庫軟實現
SM4算法原理及FK,CK,SBOX的值請參見:GM/T 0002—2012 《SM4分組密碼算法》,這里不再贅述.
1) 定義SM4密鑰數據結構及常量,如下:
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) 定義相關的宏(也可以用函數實現)
32 b循環(huán)位移:
#define RSL(A, I) (((A)<<(I))|((A)>>(32-(I))))
密鑰擴展線性變換:
#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) 實現密鑰初始化函數
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) 實現SM4加密函數
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) 實現SM4解密函數
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基礎軟實現已經完成.需要注意的是,在此實現的源代碼中,需要包括頭文件endian.h,否則是找不到函數__bswap_32的.
1.3.1.2SM4EVP封裝接口實現
SM4的EVP封裝接口需要實現4種加密模式,分別是ECB,CBC,CFB和OFB模式.其實現過程如下.
1) 定義EVP封裝的SM4數據結構如下:
typedef struct { SM4_KEY ks;} EVP_SM4_KEY;
2) 實現EVP_CIPHER結構的成員函數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) 實現EVP_CIPHER結構的成員函數do_cipher,因為要實現4種模式,因此要實現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結構,實現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,并添加對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的默認算法. 1.3.2SM1和SSF33的EVP接口 參照SM4 EVP封裝接口的實現,實現SM1和SSF33的EVP接口,并將其中的EVP_CIPHER的成員函數init,do_cipher置NULL(空)即可. 1.3.3SM3摘要算法的實現 在國產密碼OpenSSL中,SM3作為信息摘要函數,也分為基礎庫軟實現和EVP封裝接口的實現. 1.3.3.1SM3基礎庫軟實現 SM3算法的原理請參見GM/T 0004—2012 《SM3密碼雜湊算法》. 1) 定義SM3算法的結構如下所示: typedef struct SM3state_st { SM3_LONG digest[8]; SM3_LONG Nl, Nh; SM3_LONG data[64]; unsigned int num; } SM3_CTX; 2) 實現SM3初始化函數如下所示: 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實現的源代碼文件中,通過如下定義來實現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函數中,實現消息擴展及消息壓縮如下: #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信息摘要算法的基礎庫軟實現完成. 1.3.3.2SM3EVP封裝接口實現 SM3算法EVP封裝接口的實現,首先需要實現結構EVP_MD的成員函數init,update,final;然后需要填充EVP_MD的數據結構的每一項,實現EVP_sm3();然后在evp.h中申明EVP_sm3(void)接口,最后在c_alld.c文件中,將EVP_sm3()加載到OpenSSL的EVP接口庫中. 1) EVP_MD成員函數的實現 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結構,并實現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公鑰算法的實現 國產密碼算法SM2公鑰算法是一條特定的曲線橢圓曲線公鑰算法,它基于ECC的通用算法,按照中國國家密碼管理局發(fā)布的GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》標準,定義了SM2簽名[8]、驗證、公鑰加密[9]、私鑰解密、密鑰協商[10]算法5種算法. 其中,SM2簽名/驗證算法,是對原始信息經過SM3算法做摘要后的結果進行處理的.按照GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》(第二部分的6.1節(jié))要求,先要計算Z值,然后再將Z值和原始信息一起做摘要,最后對摘要值進行簽名/驗證. 因此,SM2公鑰算法中還需要定義計算Z值的函數. 1.3.4.1Z值的計算 按照GM/T 0003—2012 《SM2橢圓曲線公鑰密碼算法》(第二部分的6.1節(jié))所述,Z值的計算如下: ZA=H256(ENT LA‖IDA‖a‖b‖xG‖xG‖ xA‖yA), 其中,H256指的是256 b的信息摘要算法,在國產密碼算法中,此處只取值為SM3信息摘要算法;ENT LA是指由可辨別標識IDA的位(bit)數,轉換而成的2 B;IDA為用戶可辨別標識,當此標識不存在或者未提供時,其值默認為:1234567812345678;a,b為素域橢圓曲線方程y2=x3+ax+b的參數[11]; a的值為FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC;b的值為28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93;xG,xG為基點坐標,其值為:Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7,Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0;xA,yA為簽名者的公鑰坐標. Z值計算函數原型為: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). 其實現很簡單,在此略過. 1.3.4.2SM2簽名驗證算法實現 國產密碼OpenSSL中的SM2簽名、驗證算法函數,依據《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第2部分:數字簽名算法》中所述的原理,按照OpenSSL接口的方式,設計如下: 1) 數字簽名生成函數 ① 在OpenSSL中,SM2簽名的對象是經過SM3做過摘要的結果,因此,其原型設計為 ECDSA_SIG*sm2_do_sign(const unsigned char*dgst, int dgst_len, EC_KEY*eckey), 其中,dgst為信息摘要值,長度為32 B;eckey為簽名的SM2密鑰對;成功簽名后返回ECDSA_SIG數據結構,否則返回NULL值. ② 函數的簽名過程和其實現代碼如下. S0:將輸入的參數摘要值的數據類型轉換為整數e;實現如下(e為大數變量): BN_bin2bn(dgst, dgst_len, e); S1:用隨機數發(fā)生器產生隨機數k ∈[1,n-1];實現如下(k為大數變量,order為SM2參數基點G的階): do { if (!BN_rand_range(k, order)) return NULL; } while (BN_is_zero(k)); S2:計算橢圓曲線點(x1,y1)=[k]G,并將x1的數據類型轉換為整數;其實現為(group為SM2曲線的GROUP值,tmp_point為EC_POINT的變量,x1為大數變量): 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:計算r=(e+x1) modn,若r=0或r+k=n則返回S1;其實現為: 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:計算s=((1+dA)-1*(k-r *dA)) mod n,若s=0則返回S1;其實現為(s為大數變量,d為SM2密鑰對中的私鑰): 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結構,返回ECDSA_SIG結構.實現如下: ECDSA_SIG*sig; sig->r=r; sig->s=s; return sig; 2) 數字簽名驗證函數 ① 國產密碼OpenSSL中,SM2數據簽名驗證函數的原型設計為 int sm2_do_verify(const unsigned char*dgst, int dgst_len, const ECDSA_SIG*sig, EC_KEY*eckey), 其中,dgst為原始信息的摘要值;sig為需要驗證的簽名值;驗證成功返回1,失敗返回0. ② 國產密碼OpenSSL中,SM2數據簽名驗證流程和實現代碼如下. B0:將輸入的摘要值轉換成 e1;實現如下(e1為大數變量): if (!BN_bin2bn(dgst, dgst_len, e1)) return 0; B1:檢驗sig->r∈[1,n-1]是否成立,若不成立則驗證不通過; B2:檢驗sig->s∈[1,n-1]是否成立,若不成立則驗證不通過;實現如下(order為SM2曲線參數,為基點G的階): if (BN_is_zero(e1)) return 0; if (BN_ucmp(e1, order)>=0) return 0; B3:計算t=(sig->r+ sig->s) mod n,若t=0,則驗證不通過; if (!BN_mod_add(t, sig->r, sig->s, order, NULL)) return 0; if (BN_is_zero(t)) return 0; B4:計算橢圓曲線點(x1, y1)=[sig->s]G+[t]PA;其實現如下(group為SM2曲線的GROUP值,point為EC_POINT類型變量,pub_key為驗證用的SM2公鑰): if (!EC_POINT_mul(group, point, sig-> s, pub_key, t, NULL)) return 0; B5:將x1的數據類型轉換為整數,計算R=(e1+x1) mod n,檢驗R=sig->r是否成立,若成立則驗證通過;否則驗證不通過,其實現如下(R為大數變量): 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公鑰加密及密鑰協商算法實現 SM2公鑰加密算法包括:公鑰加密算法、私鑰解密算法,其原理可參見《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第4部分:公鑰加密算法》.其中涉及到密鑰派生函數KDF,可依據ANSI X9.63—2001規(guī)范實現(與OpenSSL 1.0.2h中ECDH_KDF_X9_62的實現兼容).設計其原型為: 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的數據結構為: struct sm2enc_st { ASN1_INTEGER*x; ASN1_INTEGER*y; ASN1_OCTET_STRING*m; ASN1_OCTET_STRING*c; }; typedef struct sm2enc_st SM2ENC; SM2密鑰協商算法的原理可參見《GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第3部分:密鑰交換協議》.在OpenSSL中的設計原型為: 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為協商的結果,其輸出長度由outlen指定;server為指示出己方是發(fā)起方/響應方標識,0為發(fā)起方;非0為響應方;peer_uid為對方可辨別標識,其長度由peer_uid_len指定;如果此項為NULL,或者peer_uid_len為0,此標識取默認值:1234567812345678;self_uid為己方可辨別標識,其長度由self_uid_len指定,如果此項為NULL,或者self_uid_len為0,此標識取默認值:1234567812345678;peer_ecdhe_key為對方SM2臨時公鑰;self_ecdhe_key為己方SM2臨時公鑰;peer_pub_key為對方SM2證書公鑰;self_eckey為己方SM2私鑰;md為指定的摘要算法,對于SM2來說,此值默認為EVP_sm3(). 協商成功返回1,否則返回0或者負值. 由于篇幅所限,本節(jié)所有函數根沒有給出實現.如果有需要此源碼部分,可從https://github.com/jntass/TASSL獲取. 1.3.4.4SM2公鑰算法EVP接口實現 首先,國產密碼OpenSSL將SM2視為ECC的一條特定曲線,因此需要將SM2的算法內置于ECC之中;也就是說,需要實現ECDSA調用接口、ECDH調用接口、EVP_PKEY的調用接口. 1) ECDSA調用接口 在OpenSSL源代碼目錄crypto/ecdsa的ecs_ossl.c文件,需要對函數:ecdsa_do_sign,ecdsa_do_verify以及對ecdsa_sign_setup作出修改;修改方式如下: 在函數ecdsa_do_sign的變量申明之后,添加如下語句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return sm2_do_sign(dgst, dlen, a, b, eckey); 在函數ecdsa_do_verify的變量申明之后,添加如下語句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return sm2_do_verify(dgst, dgst_len, sig, eckey); 在函數ecdsa_sign_setup的變量申明之后,添加如下語句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))==NID_sm2) return 1; 這樣即可實現ECDSA自動調用SM2的簽名驗證算法. 2) ECDH調用接口 SM2由于不支持ECDH算法,而由SM2密鑰協商取而代之,因此,需要防止SM2曲線運用于ECDH算法中.修改方式如下: 在OpenSSL源代碼目錄crypto/ecdh的ech_ossl.c中,修改ecdh_compute_key函數,在其變量申明之后,添加如下語句: if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ecdh))==NID_sm2) return-1; 3) EVP_PKEY的調用接口 此接口是通過修改OpenSSL源代碼目錄crypto/ec中的文件ec_pmeth.c來實現的,其實現方法為: ① 數據結構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; ② 在函數pkey_ec_init,pkey_ec_copy,pkey_ec_cleanup中需要對所添加的成員進行初始化或者清理工作; ③ 需要添加pkey_ec_encrypt和pkey_ec_decrypt,以便實現EVP對SM2公鑰加密以及私鑰解密的調用; ④ 需要定義一些宏,以實現EVP接口對SM2的參數設置,并且在pkey_ec_ctrl中實現相關宏的功能,具體宏定義如下: # 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函數,增加對SM2密鑰協商的支持,修改方式為在其變量申明之后,添加如下代碼: 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; } ⑥ 需要在數據結構常量ec_pkey_meth中,對其成員encrypt,decrypt分別賦值為:pkey_ec_encrypt和pkey_ec_decrypt. 4) 國產密碼OpenSSL中,SM2密鑰需要能夠自動完成對于原始信息的簽名和驗證功能,但由于SM2對原始信息簽名和驗證處理的特殊性,在做摘要之前,需要將簽名者的Z值對原始信息進行補償運算(即:將Z值與原始信息串接在一起),因此需要對OpenSSL源代碼目錄crypto/evp中的m_sigver.c進行修改,以完成SM2的自動簽名、驗證流程.修改方法如下: 在do_sigver_init函數成功返回前,添加如下代碼: 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曲線內置于OpenSSL中 關于國產密碼OpenSSL中,SM2公鑰算法的實現其實有一個前提,那就是SM2曲線必須為OpenSSL所支持的曲線. 原始的OpenSSL中,并不支持SM2曲線,因此需要通過修改源代碼的方式,將SM2曲線參數添加到OpenSSL中. 實現方法是修改OpenSSL源代碼目錄crypto/ec中的文件ec_curve.c.首先在此文件的數據結構_ec_list_element_st之前,添加如下數據結構: 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 } }; 然后,在數據結構常量curve_list中,添加SM2曲線的數據(在任意正確位置都可以): {NID_sm2, &_EC_SM2.h, 0, ″SM2 curve over a 256 bit prime field″}. 到此為止,OpenSSL中即可全面地對國產密碼算法SM2公鑰算法進行支持. 如果需要國產密碼OpenSSL支持其他的國密算法,可以借鑒以上所述的方式來進行添加. 國密TLS協議的實現需要根據GM/T 0024—2014《SSL VPN技術規(guī)范》,并實現國產密碼認證體系,在標準的SSL/TLS協議的基礎上來實現. 通過對比標準的TLS1.1協議,國密TLS1.1協議有如下特點: 1) 傳輸入層的版本號為0x0101,而不是標準的TLS1.1協議的0x0302; 2) 需要雙證書支持,即簽名證書和加密證書同時工作,而標準的TLS協議無需雙證書支持; 3) 客戶端需要獨立API接口來完成國密TLS的建鏈過程; 4) 無需校驗對方是否支持SM2曲線,因為使用國密套件,對方一定會支持SM2曲線. 1.5.1國密TLS協議握手流程 國密TLS協議其實主要體現在建鏈過程之中.圖2所示,是完整的建鏈過程.同時,國密TLS協議也支持會話重用,重用流程如圖3所示. 圖2 國密TLS協議握手流程 圖3 會話重用流程 1.5.2國產密碼雙證書認證體系的實現 在OpenSSL實現的SSL/TLS中,設計有支持RSA的雙證書認證體系,但OpenSSL并沒有提供RSA雙證書體系的API;對于ECC的證書認證體系來說,其簽名和數據加密或密鑰協商均使用同一套證書與其對應的私鑰來完成. 而國產密碼認證體系要求使用雙證書[12-14],無論是RSA證書還是SM2證書.因此,國密TLS協議中所屬套件均要求使用雙證書認證,即簽名證書與其對應的私鑰只用于簽名和驗證書;而數據加密或者密鑰協商需要使用加密或者密鑰協商證書與其對應的私鑰. 要在OpenSSL中實現國密TLS協議,就必須要添加對ECC(SM2其實也是一種特定的ECC)的雙證書認證. 至于SM2雙證書在何時使用,可參見圖2.在國密TLS協議的握手過程之中,ServerKeyExchange和ClientKeyExchange過程需要用到加密或密鑰協商證書進行密鑰協商;同時它也需要使用數字簽名證書(客戶端除外)進行數字簽名;客戶端CertificateVerify過程需要使用數字簽名證書進行數字簽名;而服務端GetClientKeyExchange和GetClientCertVerify過程需要使用數字簽名證書進行簽名驗證. 在國產密碼OpenSSL中,原始的SSL/TLS證書認證體系API可直接用于數字簽名證書的認證接口;針對RSA和SM2證書,設計了如下的一套加密證書接口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); 而針對OpenSSL中經過適當修改后,可適應雙證書認證體系的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構成了國密TLS雙證書認證體系. 1.5.3國密TLS套件的實現 目前,國密TLS協議中定義了許多密碼套件,可用于建立國密TLS協議的鏈路.其中,SM9和RSA相關密碼套件由于使用不多或者有其局限性,這里不作論述,在此,著重說明ECC-SM4-SM3(EC-SM1-SM3和它相同,只不過SM1需要硬件支持)和ECDHE-SM4-SM3(ECDHE-SM1-SM3和它相同,只不過SM1需要硬件支持)這2個密碼套件的實現. 在國產密碼OpenSSL中,ECC-SM4-SM3,ECDHE-SM4-SM3這2個密碼套件的實現,對于客戶端來說需要實現SendClientKeyExchange和Get ServerKeyExchange這2個過程;而對于服務端來說,需要實現Send ServerKeyExchange和GetClientKeyExchange這2個過程. 1.5.3.1ECC-SM4-SM3密碼套件的實現 ECC-SM4-SM3這個密碼套件是指客戶端無證書,或者客戶端只有驗證服務端證書的證書鏈(它一般指的是CA的證書),此時與國密TLS服務器所建的鏈就是使用ECC-SM4-SM3這個密碼套件. 此時,SendServerKeyExchange過程作如下處理: 1) 對客戶端隨機數+服務端隨機數+服務端加密/協商證書進行數字簽名; 2) 發(fā)送此簽名的ASN1的編碼結果. Get ServerKeyExchange過程作如下處理: 1) 取服務端加密證書; 2) 對客戶端隨機數+服務端隨機數+服務端加密/協商證書進行簽名驗證; 3) 成則繼續(xù),否則中斷握手. SendClientKeyExchange過程作如下處理: 1) 在48 B的緩沖區(qū)中,填入0x0101(國密TLS的版本號)+46 B的隨機數,構成預主密鑰; 2) 使用服務端加密證書對此預主密鑰加密; 3) 發(fā)送加密結果的ASN1的編碼結果. GetClientKeyExchange過程作如下處理: 1) 使用加密私鑰對客戶端發(fā)來的結果作解密運算,得到預主密鑰; 2) 使用預主密鑰計算主密鑰; 3) 保留主密鑰,以便會話重用. 1.5.3.2ECDHE-SM4-SM3密碼套件的實現 ECDHE-SM4-SM密碼套件是在服務端和客戶端之間,使用SM2密鑰協商協議,生成48 B主密鑰,然后再完成握手過程. 此時,SendServerKeyExchange過程作如下處理: 1) 生成服務端臨時SM2密鑰對,取臨時公鑰的字符串值; 2) 對臨時公鑰的內容進行簽名; 3) 將臨時公鑰和數字簽名發(fā)送給客戶端. Get ServerKeyExchange過程作如下處理: 1) 取服務端發(fā)送的數據,并對其進行簽名驗證;如果驗證失敗則中斷握手過程; 2) 取服務端臨時密鑰對并保存. SendClientKeyExchange過程作如下處理: 1) 生成客戶端臨時公鑰; 2) 使用默認ID為1234567812345678,調用SM2密鑰協商過程,生成48 B的預主密鑰; 3) 向服務端發(fā)送客戶端的臨時公鑰. GetClientKeyExchange過程作如下處理: 1) 取客戶端發(fā)送的臨時公鑰; 2) 使用默認ID為1234567812345678,調用SM2密鑰協商過程,生成48 B的預主密鑰; 3) 計算主密鑰并保存,以便會話重用. 國產密碼OpenSSL在保留OpenSSL特性的同時,也將國產密碼的相關特性集成到其中,可以應用到許多方面. 在世界范圍內,OpenSSL的使用極其廣泛.我國也有大量的應用軟件、系統(tǒng)軟件是依賴于OpenSSL構建的. 國產密碼OpenSSL不僅提供了國密算法函數,同時也保留了原版OpenSSL的所有功能特點,它可以無縫替換原版OpenSSL,包括命令行工具等等,從而實現應用軟件的密碼算法從國際算法到國密算法的遷移. 目前在大部分國產操作系統(tǒng)中都集成了OpenSSL.通過使用國產密碼OpenSSL替換國產操作系統(tǒng)中原有的OpenSSL,以實現對國密算法的支持. 通過對國密OpenSSL源碼的分析和理解、對其API的應用,再比照國密標準,可以更容易地學習國密算法和國密規(guī)范,讓國密OpenSSL的使用者更加廣泛地、自由地使用國密算法,體會國密算法的優(yōu)越性、安全性. 通過國產密碼OpenSSL可以加深對國密認證體系的認識.國密OpenSSL的雙證書體系不僅僅應用于SM2證書,同樣也可以應用于RSA證書.使國密雙證書的認證體系可以有著更好的應用前景. 國產密碼OpenSSL已經實現了對國產密碼認證體系的支持,參照我國關于CA系統(tǒng)的相關標準,可以構建符合中國標準的CA系統(tǒng). 國產密碼OpenSSL可以和nginx,HAproxy,apache等知名的開源軟件配套使用,從而構建支持國密算法的應用環(huán)境.具體構建方式為,將nginx,HAproxy等源代碼作適量修改,以支持國密雙證書體系及國密TLS協議,在國密OpenSSL的基礎上重新構建. 本著回報社會、推動國產密碼算法推廣的目的,江南天安已經將天安版國產密碼OpenSSL作為TaSSL項目開源(可以從https://github.com/jntass/TASSL獲取),本文中所有的實現,或者是未提供源代碼實現的部分都可以從TaSSL開源的項目中獲取,同時也有大量的示例程序,以供業(yè)界同仁參考,歡迎業(yè)界同仁和密碼技術愛好者下載使用,并且提供寶貴的意見. 本文僅僅是拋磚引玉,志在引起OpenSSL愛好者對國密的興趣和熱情.由于篇幅所限,許多的話題言而未盡,愿與國密同仁及愛好者共勉之,一同為推動國密事業(yè)而努力. [1]OpenSSL Software Foundation. OpenSSL cryptography and SSL/TLS toolkit[OL]. [2018-01-15]. https://www.openssl.org [2]中華人民共和國密碼行業(yè)標準. GM/T 0024—2014 SSL VPN技術規(guī)范[S]. 北京: 中國標準出版社, 2012 [3]中華人民共和國密碼行業(yè)標準. GM/T 0002—2012 SM4分組密碼算法[S]. 北京: 中國標準出版社, 2012 [4]中華人民共和國密碼行業(yè)標準. GM/T 0004—2012 SM3密碼雜湊算法[S]. 北京: 中國標準出版社, 2012 [5]中華人民共和國密碼行業(yè)標準. GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第1部分: 總則[S]. 北京: 中國標準出版社, 2012 [6]Daniel R L, Brown. SEC 1: Elliptic curve cryptography[OL]. 2009 [2018-01-15]. http://www.secg.org/sec1-v2.pdf [7]中華人民共和國密碼行業(yè)標準. GM/T 0006—2012 密碼應用標識規(guī)范[S]. 北京: 中國標準出版社, 2012 [8]中華人民共和國密碼行業(yè)標準. GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第2部分: 數字簽名算法[S]. 北京: 中國標準出版社, 2012 [9]中華人民共和國密碼行業(yè)標準. GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第4部分: 公鑰加密算法[S]. 北京: 中國標準出版社, 2012 [10]中華人民共和國密碼行業(yè)標準. GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第3部分: 密鑰交換協議[S]. 北京: 中國標準出版社, 2012 [11]中華人民共和國密碼行業(yè)標準. GM/T 0003—2012 SM2橢圓曲線公鑰密碼算法 第5部分: 參數定義[S]. 北京: 中國標準出版社, 2012 [12]中華人民共和國密碼行業(yè)標準. GM/T 0009—2012 SM2密碼算法使用規(guī)范[S]. 北京: 中國標準出版社, 2012 [13]中華人民共和國密碼行業(yè)標準. GM/T 0014—2012 數字證書認證系統(tǒng)密碼協議規(guī)范[S]. 北京: 中國標準出版社, 2012 [14]中華人民共和國密碼行業(yè)標準. GM/T 0015—2012 基于SM2密碼算法的數字證書格式規(guī)范[S]. 北京: 中國標準出版社, 20121.4 其他國密算法
1.5 國密TLS協議的實現
2 國產密碼OpenSSL的應用
2.1 替換國產操作系統(tǒng)中的OpenSSL模塊
2.2 學習和推廣國密算法
2.3 構建支持SM2證書簽發(fā)的CA認證中心
2.4 與其他開源軟件配套使用
3 結束語