支持国产密码算法的OpenSSL设计实现及应用
2018-02-28蔡成杭
蔡成杭
(北京江南天安科技有限公司 北京 100088)
OpenSSL[1]是一套应用广泛的、开源的支持传输层安全协议的密码学基础库和工具集合,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL/TLS协议,并提供丰富的API,以供应用系统集成、程序开发、测试或其他目的使用.它广泛地集成在各种类型的操作系统中(Linux、BSD家族、MacOS等),即使某些操作系统(Windows、传统的Unix等)没有将其集成为组件,通过源代码下载,也可以十分轻松地构建OpenSSL的开发及应用环境.
作为基础组件之一,OpenSSL简洁、明了、丰富的应用接口,可简单、便捷地构筑安全领域等方面的应用,从而深受广大IT爱好者的喜爱.因此,基于OpenSSL的应用十分广泛,特别是涉及到安全功能的应用系统和中间件,许多都是基于OpenSSL来构建的.如我们常用的SSH,Apache,Tomcat,Nginx,MySQL等知名系统,都是依赖OpenSSL来构建其安全体系的.
在中国,也有大量的安全领域的应用是依赖OpenSSL来构造的.但原始OpenSSL中并不包含符合中国标准的商用密码算法(简称国密)和安全通信协议,构建的安全应用也不符合中国商用密码行业标准,不利于国产密码的推广.因此,通过对OpenSSL的改造,让OpenSSL支持国产密码算法及通信协议,这是十分必要的.
江南天安于2017年推出了天安版国密OpenSSL,将国产密码算法及国密TLS协议[2]集成进OpenSSL中,实现了支持国产密码认证体系以及国密TLS(以下简称CNTLS)协议,可替换原来基于OpenSSL的上层应用,也可在此基础上便捷地构建符合国密标准的应用系统.
本文以OpenSSL的稳定版本1.0.2h为例,详细介绍支持国产密码算法的OpenSSL在CentOS 6.5操作系统中的实现及应用.
1 国产密码OpenSSL的实现
国产密码OpenSSL除具备原始OpenSSL的功能特色外,同时兼具着以下几点:
1) 支持国产密码算法,提供国产密码算法开发和应用接口;
2) 包含国产密码算法的对象标识符;
3) 支持国产密码认证体系;
4) 实现国密TLS协议,提供国密TLS协议的开发和应用接口;
5) 支持以引擎或内置的方式,实现需硬件支持的国产密码算法,如:SM1,SSF33等.
1.1 国产密码OpenSSL的系统架构
国产密码OpenSSL的系统架构如图1所示,可粗略分为:基础函数库、EVP密码算法封装接口库、X509/PKCS认证接口库、SSL/TLS/CNTLS协议接口库、应用及开发接口等.
图1 国产密码OpenSSL系统架构及依赖关系图
图1中,基础函数库包括:国际对称加密算法、信息摘要算法、公开密钥算法的软实现;国产对称加密算法SM4[3]、信息摘要算法SM3[4]、公开密钥算法SM2[5](可视为椭圆曲线公钥算法[6]的一条特定曲线)的软实现;硬件加速或实现引擎接口;错误处理接口;BIO抽象输入/输出接口;数据结构等等.
EVP密码算法封装接口库包括:对国际密码算法、国产密码算法、硬件实现算法的统一调用接口,它依赖于基础函数库,同时也提供信息摘要、自动完成公钥算法的数字签名和验证以及数字信封等等接口,供X509/PKCS,SSL/TLS/CNTLS接口库调用.
X509/PKCS接口库:X509或PKCS认证体系的标准接口库,此接口库需支持国密码认证体系.
SSL/TLS/CNTLS接口库:提供SSL/TLS/标准接口,同时添加支持CNTLS的API及支持双证书体系(目前只支持RSA和SM2的双证书)的接口API.
1.2 国产密码算法对象标识的集成
国产密码算法对象标识[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,依次执行如下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中.
1.3 国产密码算法的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循环位移:
#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系统中去,如下所示: 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的通用算法,按照中国国家密码管理局发布的GM/T 0003—2012 《SM2椭圆曲线公钥密码算法》标准,定义了SM2签名[8]、验证、公钥加密[9]、私钥解密、密钥协商[10]算法5种算法. 其中,SM2签名/验证算法,是对原始信息经过SM3算法做摘要后的结果进行处理的.按照GM/T 0003—2012 《SM2椭圆曲线公钥密码算法》(第二部分的6.1节)要求,先要计算Z值,然后再将Z值和原始信息一起做摘要,最后对摘要值进行签名/验证. 因此,SM2公钥算法中还需要定义计算Z值的函数. 1.3.4.1Z值的计算 按照GM/T 0003—2012 《SM2椭圆曲线公钥密码算法》(第二部分的6.1节)所述,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:用随机数发生器产生随机数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规范实现(与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为指示出己方是发起方/响应方标识,0为发起方;非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或者负值. 由于篇幅所限,本节所有函数根没有给出实现.如果有需要此源码部分,可从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技术规范》,并实现国产密码认证体系,在标准的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) 发送此签名的ASN1的编码结果. Get ServerKeyExchange过程作如下处理: 1) 取服务端加密证书; 2) 对客户端随机数+服务端随机数+服务端加密/协商证书进行签名验证; 3) 成则继续,否则中断握手. SendClientKeyExchange过程作如下处理: 1) 在48 B的缓冲区中,填入0x0101(国密TLS的版本号)+46 B的随机数,构成预主密钥; 2) 使用服务端加密证书对此预主密钥加密; 3) 发送加密结果的ASN1的编码结果. GetClientKeyExchange过程作如下处理: 1) 使用加密私钥对客户端发来的结果作解密运算,得到预主密钥; 2) 使用预主密钥计算主密钥; 3) 保留主密钥,以便会话重用. 1.5.3.2ECDHE-SM4-SM3密码套件的实现 ECDHE-SM4-SM密码套件是在服务端和客户端之间,使用SM2密钥协商协议,生成48 B主密钥,然后再完成握手过程. 此时,SendServerKeyExchange过程作如下处理: 1) 生成服务端临时SM2密钥对,取临时公钥的字符串值; 2) 对临时公钥的内容进行签名; 3) 将临时公钥和数字签名发送给客户端. Get ServerKeyExchange过程作如下处理: 1) 取服务端发送的数据,并对其进行签名验证;如果验证失败则中断握手过程; 2) 取服务端临时密钥对并保存. SendClientKeyExchange过程作如下处理: 1) 生成客户端临时公钥; 2) 使用默认ID为1234567812345678,调用SM2密钥协商过程,生成48 B的预主密钥; 3) 向服务端发送客户端的临时公钥. GetClientKeyExchange过程作如下处理: 1) 取客户端发送的临时公钥; 2) 使用默认ID为1234567812345678,调用SM2密钥协商过程,生成48 B的预主密钥; 3) 计算主密钥并保存,以便会话重用. 国产密码OpenSSL在保留OpenSSL特性的同时,也将国产密码的相关特性集成到其中,可以应用到许多方面. 在世界范围内,OpenSSL的使用极其广泛.我国也有大量的应用软件、系统软件是依赖于OpenSSL构建的. 国产密码OpenSSL不仅提供了国密算法函数,同时也保留了原版OpenSSL的所有功能特点,它可以无缝替换原版OpenSSL,包括命令行工具等等,从而实现应用软件的密码算法从国际算法到国密算法的迁移. 目前在大部分国产操作系统中都集成了OpenSSL.通过使用国产密码OpenSSL替换国产操作系统中原有的OpenSSL,以实现对国密算法的支持. 通过对国密OpenSSL源码的分析和理解、对其API的应用,再比照国密标准,可以更容易地学习国密算法和国密规范,让国密OpenSSL的使用者更加广泛地、自由地使用国密算法,体会国密算法的优越性、安全性. 通过国产密码OpenSSL可以加深对国密认证体系的认识.国密OpenSSL的双证书体系不仅仅应用于SM2证书,同样也可以应用于RSA证书.使国密双证书的认证体系可以有着更好的应用前景. 国产密码OpenSSL已经实现了对国产密码认证体系的支持,参照我国关于CA系统的相关标准,可以构建符合中国标准的CA系统. 国产密码OpenSSL可以和nginx,HAproxy,apache等知名的开源软件配套使用,从而构建支持国密算法的应用环境.具体构建方式为,将nginx,HAproxy等源代码作适量修改,以支持国密双证书体系及国密TLS协议,在国密OpenSSL的基础上重新构建. 本着回报社会、推动国产密码算法推广的目的,江南天安已经将天安版国产密码OpenSSL作为TaSSL项目开源(可以从https://github.com/jntass/TASSL获取),本文中所有的实现,或者是未提供源代码实现的部分都可以从TaSSL开源的项目中获取,同时也有大量的示例程序,以供业界同仁参考,欢迎业界同仁和密码技术爱好者下载使用,并且提供宝贵的意见. 本文仅仅是抛砖引玉,志在引起OpenSSL爱好者对国密的兴趣和热情.由于篇幅所限,许多的话题言而未尽,愿与国密同仁及爱好者共勉之,一同为推动国密事业而努力. [1]OpenSSL Software Foundation. OpenSSL cryptography and SSL/TLS toolkit[OL]. [2018-01-15]. https://www.openssl.org [2]中华人民共和国密码行业标准. GM/T 0024—2014 SSL VPN技术规范[S]. 北京: 中国标准出版社, 2012 [3]中华人民共和国密码行业标准. GM/T 0002—2012 SM4分组密码算法[S]. 北京: 中国标准出版社, 2012 [4]中华人民共和国密码行业标准. GM/T 0004—2012 SM3密码杂凑算法[S]. 北京: 中国标准出版社, 2012 [5]中华人民共和国密码行业标准. 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]中华人民共和国密码行业标准. GM/T 0006—2012 密码应用标识规范[S]. 北京: 中国标准出版社, 2012 [8]中华人民共和国密码行业标准. GM/T 0003—2012 SM2椭圆曲线公钥密码算法 第2部分: 数字签名算法[S]. 北京: 中国标准出版社, 2012 [9]中华人民共和国密码行业标准. GM/T 0003—2012 SM2椭圆曲线公钥密码算法 第4部分: 公钥加密算法[S]. 北京: 中国标准出版社, 2012 [10]中华人民共和国密码行业标准. GM/T 0003—2012 SM2椭圆曲线公钥密码算法 第3部分: 密钥交换协议[S]. 北京: 中国标准出版社, 2012 [11]中华人民共和国密码行业标准. GM/T 0003—2012 SM2椭圆曲线公钥密码算法 第5部分: 参数定义[S]. 北京: 中国标准出版社, 2012 [12]中华人民共和国密码行业标准. GM/T 0009—2012 SM2密码算法使用规范[S]. 北京: 中国标准出版社, 2012 [13]中华人民共和国密码行业标准. GM/T 0014—2012 数字证书认证系统密码协议规范[S]. 北京: 中国标准出版社, 2012 [14]中华人民共和国密码行业标准. GM/T 0015—2012 基于SM2密码算法的数字证书格式规范[S]. 北京: 中国标准出版社, 20121.4 其他国密算法
1.5 国密TLS协议的实现
2 国产密码OpenSSL的应用
2.1 替换国产操作系统中的OpenSSL模块
2.2 学习和推广国密算法
2.3 构建支持SM2证书签发的CA认证中心
2.4 与其他开源软件配套使用
3 结束语