目录
1、参考链接
sm2格式数字信封加解密详解sm2格式数字信封加解密详解_06082a811ccf5501822d-优快云博客
密码行业标准化技术委员会
http://www.gmbz.org.cn/main/bzlb.html
SM2密码算法使用规范
http://www.gmbz.org.cn/main/viewfile/2018011001400692565.html
SM2密码算法应用分析
https://blog.youkuaiyun.com/arlaichin/article/details/23708155utm_source=itdadao&utm_medium=referral
技术科普 | 国密算法在Ultrain区块链中的运用
https://blog.youkuaiyun.com/Tramp_1/article/details/111603396
1、什么是国密密钥对加密
国密密钥对加密格式定义在《GMT 0009-2012 SM2密码算法使用规范》文档中,具体的规范格式如下:
在SM2密钥对传递时,需要对SM2密钥对进行加密保护。具体的保护方法为:
-
产生一个对称密钥;
-
按对称密码算法标识指定的算法对SM2私钥进行加密,得到私钥的密文。 若对称算法为分组算法,则其运算模式为ECB;
-
使用外部SM2公钥加密对称密钥得到对称密钥密文;
-
将私钥密文、对称密钥密文封装到密钥对保护数据中。
SM2密钥对的保护数据格式的ASN.1定义为:
SM2EnvelopedKey ::= SEQUENCE{
symAlgID AlgorithmIdentifier, -- 对称密码算法标识
symEncryptedKey SM2Cipher, -- 对称密钥密文
Sm2PublicKey SM2PublicKey, -- SM2公钥
Sm2EncryptedPrivateKey BIT STRING -- SM2私钥密文
}
2、国密加密密钥对用途
国密密钥对加密是国内对传输加密密钥的一种保护措施,了解过国密双证书的知道,国密加密证书通常由CA机构来颁发,CA机构通过规定的国密加密对格式对加密证书私钥加密后,颁发给申请者。
CFCA(中国金融认证中心)就是通过此种方式来颁发国密加密证书私钥的,可以通过自己生成SM2证书请求和SM2签名私钥,向CFCA申请服务器测试双证书,此时得到的密钥就是加密后的密钥。
可以通过ASN1dump来查看颁发的加密私钥的格式;
3、加解密sm2密钥对信封
3.1 sm2密钥信封解密
1、p7(pem格式)转二进制
2、解析二进制,得到对称算法ID,对称算法密钥密文,加密证书公钥,加密证书私钥密文二进制
3、对称算法ID转具体算法名称
二进制字符串ID转换为OID(1.2.156.10197.1.104.1)进行匹配
4、签名私钥解密,得到对称算法密钥
使用sm2算法解密对称密钥密文,得到对称密钥明文
5、对称算法解密,得到加密私钥有效数据,32字节
使用sm4_ecb算法和对称密钥明文,解密私钥32字节密文得到32字节明文
6、拼接公钥,私钥得到完整加密私钥der二进制格式,有两种方式(此处拼接方法是我自己创造的,有什么其他好方法欢迎共享)
(1)"30770201010420"+32字节私钥+"a00a06082a811ccf5501822da144034200"+65字节公钥 (2)"308187020100301306072A8648CE3D020106082A811CCF5501822D046D306B0201010420"+32字节私钥+"A144034200"+65字节公钥
7、转换der得到pem格式文件
————————————————
3.1.1 通过tassl来解密密钥对信封
a)查看文件是否为DER格式,如若不是,需转换成DER格式,可通过openssl asn1parse 命令来进行转换;
openssl asn1parse -in file_path -out file_path
b)通过tassl中的openssl pkcs7来解密sm2密钥对信封,命令行中传入的文件必须时二进制文件,否则会解析失败;
openssl pkcs7 -in file_path -inform DER -GMT0009 -in_sign_key file_path -enc_key_print
注意:通过此种方式获取的sm2私钥有些问题,需要去通过修改pkcs7.c中的源码,才能获取正确的sm2私钥,否则通过此命令行解析出来的私钥,在ssl加载私钥时会失败。
3.1.2 myssl.com
通过私钥加解密 (myssl.com)该网站也可以完成sm2密钥对信封的解密;
3.1.3 CFCA证书管理工具
通过CFCA提供的证书管理工具,也可以完成SM2密钥对信封解密;
3.2 sm2密钥信封加密
1、将加密证书私钥转换为der格式(二进制)
2、设置对称算法ID,公钥有效数据部分,私钥有效数据部分
对称算法ID默认为0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x68, 0x01(即sm4_ecb,1.2.156.10197.1.104.1)
公钥数据前缀为0xa1, 0x44, 0x03, 0x42, 0x00,截取65字节明文
私钥数据前缀为0x02, 0x01, 0x01, 0x04, 0x20,截取32字节明文
3、创建对称密钥,加密私钥有效数据部分
使用sm4_ecb算法和创建的128位随机密钥,加密私钥32字节得到32字节密文
4、使用签名公钥加密对称密钥,密文转换为二进制
使用sm2算法加密128位对称密钥,得到对称密钥密文(此处我使用了openssl已实现的sm2算法加密,因此不需要按照国标文档里深入底层计算预处理结果Za)
5、将对称算法ID,对称密钥密文,公钥,私钥密文转换为信封格式数据(此处可以参考openssl内部代码实现结构体和i2d格式转换)
6、将der二进制信封转换为pem格式(base64),输出
————————————————
3.2.1 tassl编码实现信封加密
tassl中的命令行并不支持生成sm2密钥对信封,但是可以通过其中的源码自己编写程序生成sm2数字信封;
#include <stdio.h>
#include "openssl/sm2.h"
#include "openssl/ossl_typ.h"
#include "openssl/bio.h"
#include "openssl/x509.h"
#include "openssl/ec.h"
#include "openssl/evp.h"
#include "crypto/asn1.h"
#include <string.h>
#include "crypto/sm2.h"
#include "crypto/sm2err.h"
#include "crypto/ec.h" /* ecdh_KDF_X9_63() */
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/pem.h>
#include <unistd.h>
#define KEY_IV_LENTH 16
#define error_print()\
do { fprintf(stderr,"%s:%d:%s():\n", __FILE__,__LINE__,__func__);} while(0)
struct SM2_Ciphertext_st {
BIGNUM *C1x;
BIGNUM *C1y;
ASN1_OCTET_STRING *C3;
ASN1_OCTET_STRING *C2;
};
struct SM2_Enveloped_Key_st {
X509_ALGOR *symAlgID;
SM2_Ciphertext *symEncryptedKey;
ASN1_BIT_STRING *Sm2PublicKey;
ASN1_BIT_STRING *Sm2EncryptedPrivateKey;
};
#if 0
ASN1_SEQUENCE(SM2_Enveloped_Key) = {
ASN1_SIMPLE(SM2_Enveloped_Key, symAlgID, X509_ALGOR),
ASN1_SIMPLE(SM2_Enveloped_Key, symEncryptedKey, SM2_Ciphertext),
ASN1_SIMPLE(SM2_Enveloped_Key, Sm2PublicKey, ASN1_BIT_STRING),
ASN1_SIMPLE(SM2_Enveloped_Key, Sm2EncryptedPrivateKey, ASN1_BIT_STRING),
} ASN1_SEQUENCE_END(SM2_Enveloped_Key)
IMPLEMENT_ASN1_FUNCTIONS(SM2_Enveloped_Key)
#endif
struct X509_pubkey_st {
X509_ALGOR *algor;
ASN1_BIT_STRING *public_key;
EVP_PKEY *pkey;
};
enum{
SM4_ECB,
SM4_CBC
};
struct SM4_cipher_st{
int cipher_type;
unsigned char key[17];
unsigned char iv[17];
};
#define SM4_OID_OLD "\x2a\x81\x1c\xcf\x55\x01\x68"
#define SM4_OID_OLD_LEN 7
int SM2_Enveloped_Key_dataEncode(EVP_PKEY *pkey_enc, EVP_PKEY *pkey_sig_pub, struct SM4_cipher_st *sm4_cipher, unsigned char *ciphertext_buf, size_t *ciphertext_len)
{
struct SM2_Enveloped_Key_st sm2envelopedkey;
struct SM2_Ciphertext_st *sm2_ctext = NULL;
ASN1_OBJECT *obj = NULL;
EVP_CIPHER_CTX *ctx = NULL;
ASN1_TYPE *parameter = NULL;
X509_ALGOR *symAlgID = NULL;
unsigned char outbuf[1024] = {0};
int outlen = 0;
EVP_PKEY_CTX *cctx = NULL;
char ciphertext_buffer[1024] = {0};
unsigned char *priv = NULL, *pub = NULL;
size_t privlen = 0, publen = 0;
uint8_t ciphertext[128];
size_t ctext_len = sizeof(ciphertext);
int ciphertext_leni = 0;
EC_KEY *ec_key = NULL;
EC_KEY *ec_pubkey = NULL;
EVP_ENCODE_CTX *basectx=EVP_ENCODE_CTX_new();
int taillen = 0;
int baselen = 0;
char base64[1024] = {0};
symAlgID = OPENSSL_malloc(sizeof(struct X509_algor_st));
if(symAlgID == NULL){
error_print();
goto err;
}
//set sm4 OID
obj = ASN1_OBJECT_new();
if(obj == NULL){
error_print();
goto err;
}
obj->data = OPENSSL_malloc(SM4_OID_OLD_LEN);
if(obj->data == NULL){
error_print();
goto err;
}
memcpy((void *)obj->data, SM4_OID_OLD, SM4_OID_OLD_LEN);
obj->length = 7;
obj->sn = NULL;
obj->ln = NULL;
obj->flags |= V_ASN1_OBJECT;
obj->nid = 0;
symAlgID->algorithm = obj;
//sm4 encrypt prikey
if((ctx = EVP_CIPHER_CTX_new()) == NULL){
error_print();
goto err;
}
EVP_CIPHER_CTX_init(ctx);
EVP_CipherInit_ex(ctx, sm4_cipher->cipher_type == SM4_CBC ? EVP_sm4_cbc() : EVP_sm4_ecb(), NULL, sm4_cipher->key, sm4_cipher->iv, 1);
if(sm4_cipher->cipher_type == SM4_CBC){
//set sm4 param
parameter = ASN1_TYPE_new();
if(EVP_CIPHER_param_to_asn1(ctx, parameter) <= 0){
error_print();
goto err;
}
symAlgID->parameter = parameter;
}
ec_key = EVP_PKEY_get1_EC_KEY(pkey_enc);
if(ec_key == NULL){
error_print();
goto err;
}
if (EC_KEY_get0_private_key(ec_key) != NULL) {
privlen = EC_KEY_priv2buf(ec_key, &priv);
if (privlen == 0)
goto err;
}
if(!EVP_CipherUpdate(ctx, outbuf, &outlen, priv, privlen)){
error_print();
goto err;
}
#if 0
if(!EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen)){
error_print();
goto err;
}
outlen += tmplen;
#endif
if ((cctx = EVP_PKEY_CTX_new(pkey_sig_pub, NULL)) == NULL){
error_print();
goto err;
}
if (EVP_PKEY_encrypt_init(cctx) <= 0){
error_print();
goto err;
}
if (EVP_PKEY_encrypt(cctx, ciphertext, &ctext_len, sm4_cipher->key, strlen(sm4_cipher->key)) <= 0){
error_print();
goto err;
}
char *cipher_text = ciphertext;
sm2_ctext = d2i_SM2_Ciphertext(NULL, (const unsigned char **)&cipher_text, ctext_len);
if (sm2_ctext == NULL) {
error_print();
goto err;
}
sm2envelopedkey.symAlgID = symAlgID;
sm2envelopedkey.symEncryptedKey = sm2_ctext;
#if 1
ec_pubkey = EVP_PKEY_get1_EC_KEY(pkey_enc);
if(ec_pubkey == NULL){
error_print();
goto err;
}
if (EC_KEY_get0_public_key(ec_pubkey) != NULL) {
publen = EC_KEY_key2buf(ec_pubkey, EC_KEY_get_conv_form(ec_pubkey), &pub, NULL);
if (publen == 0)
goto err;
}
sm2envelopedkey.Sm2PublicKey = ASN1_BIT_STRING_new();
ASN1_BIT_STRING_set(sm2envelopedkey.Sm2PublicKey, pub, publen);
sm2envelopedkey.Sm2EncryptedPrivateKey = ASN1_BIT_STRING_new();
ASN1_BIT_STRING_set(sm2envelopedkey.Sm2EncryptedPrivateKey, outbuf, outlen);
#endif
cipher_text = ciphertext_buffer;
ciphertext_leni = i2d_SM2_Enveloped_Key(&sm2envelopedkey, (unsigned char **)&cipher_text);
/* Ensure cast to size_t is safe */
if (ciphertext_leni < 0) {
error_print();
goto err;
}
EVP_EncodeInit(basectx);
if (EVP_EncodeUpdate(basectx, base64, &baselen,
ciphertext_buffer, ciphertext_leni) < 0) {
error_print();
goto err;
}
EVP_EncodeFinal(basectx, base64 + baselen, &taillen);
baselen += taillen;
if(baselen == 0){
error_print();
goto err;
}
*ciphertext_len = (size_t)baselen;
strncpy(ciphertext_buf, base64, baselen);
err:
if(basectx)
EVP_ENCODE_CTX_free(basectx);
if(ctx)
EVP_CIPHER_CTX_cleanup(ctx);
if(sm2_ctext)
SM2_Ciphertext_free(sm2_ctext);
if(symAlgID)
OPENSSL_free(symAlgID);
if(obj->data)
OPENSSL_free((void *)obj->data);
if(obj)
ASN1_OBJECT_free(obj);
if(cctx)
EVP_PKEY_CTX_free(cctx);
if(parameter)
ASN1_TYPE_free(parameter);
if(priv)
OPENSSL_clear_free(priv, privlen);
if(pub)
OPENSSL_free(pub);
if(ec_pubkey)
EC_KEY_free(ec_pubkey);
if(ec_key)
EC_KEY_free(ec_key);
return -1;
}
void printf_help()
{
printf("usage:\n");
printf("sm2_enveloped_key -e file -C SM4_ecb -c file -K 1234567812345678 -o file\n");
printf("-h :help\n");
printf("-e :SM2 encrypt key file path\n");
printf("-C :SM4 cipher, please input sm4_ecb or sm4_cbc\n");
printf("-c :SM2 sign cert file path\n");
printf("-k :SM2 sign pubkey file path\n");
printf("-K :SM4 cipher key(16 bytes)\n");
printf("-I :SM4 cipher IV(16 bytes), only uesd for sm4_cbc\n");
printf("-o :SM2 enveloped file path\n");
}
int main(int argc, char *argv[])
{
BIO *bio = NULL;
BIO *biob = NULL;
BIO *bioc = NULL;
BIO *bioo = NULL;
BIO *biopub = NULL;
EVP_PKEY *pkey_enc = NULL;
EVP_PKEY *pkey_sign = NULL;
struct SM4_cipher_st sm4_cipher;
unsigned char buf[2048] = {0};
char enc_key_file[1024] = {0};
char sign_cert_file[1024] = {0};
char sign_key_file[1024] = {0};
char out[1024] = {0};
X509 *cert = NULL;
size_t buf_len = 0;
if(argc == 1){
printf_help();
return 0;
}
int opt;
while((opt=getopt(argc,argv,"he:C:c:k:K:I:o:"))!=-1)
{
switch(opt)
{
case 'h':
printf_help();
return 0;
case 'e':
if(strlen(optarg) > sizeof(enc_key_file) - 1){
printf("SM2 encrypt key file path is too length.\n");
return 0;
}
snprintf(enc_key_file, sizeof(enc_key_file), "%s", optarg);
break;
case 'C':
if(!strcmp(optarg, "sm4_ecb")){
sm4_cipher.cipher_type = SM4_ECB;
}else if(!strcmp(optarg, "sm4_cbc")){
sm4_cipher.cipher_type = SM4_CBC;
}else{
printf("Unknow cipher.\n");
return 0;
}
break;
case 'c':
if(strlen(optarg) > sizeof(sign_cert_file) - 1){
printf("SM2 sign cert file path is too length.\n");
return 0;
}
snprintf(sign_cert_file, sizeof(sign_cert_file), "%s", optarg);
break;
case 'k':
if(strlen(optarg) > sizeof(sign_key_file) - 1){
printf("SM2 sign key file path is too length.\n");
return 0;
}
snprintf(sign_key_file, sizeof(sign_key_file), "%s", optarg);
break;
case 'K':
if(strlen(optarg) != KEY_IV_LENTH){
printf("SM4 cipher key length is wrong.\n");
return 0;
}
snprintf(sm4_cipher.key, sizeof(sm4_cipher.key), "%s", optarg);
break;
case 'I':
if(strlen(optarg) != KEY_IV_LENTH){
printf("SM4 cipher IV length is wrong.\n");
return 0;
}
snprintf(sm4_cipher.iv, sizeof(sm4_cipher.iv), "%s", optarg);
break;
case 'o':
if(strlen(optarg) > sizeof(out) - 1){
printf("SM2 enveloped file path is too length.\n");
return 0;
}
snprintf(out, sizeof(out), "%s", optarg);
break;
default:
printf("unknown option\n");
break;
}
}
bio = BIO_new_file(enc_key_file, "rb");
if(bio == NULL){
printf("Invalid SM2 encrypt key file path.\n");
goto err;
}
pkey_enc = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL);
if(pkey_enc == NULL){
printf("Invalid SM2 encrypt key file format.\n");
goto err;
}
if(sign_key_file[0]){
biob = BIO_new_file(sign_key_file, "r");
if(biob == NULL){
printf("Invalid SM2 sign key file path.\n");
goto err;
}
pkey_sign = PEM_read_bio_PUBKEY(biob, NULL, NULL, NULL);
if(pkey_sign == NULL){
printf("Invalid SM2 sign key file format.\n");
goto err;
}
}else if(sign_cert_file[0]){
bioc = BIO_new_file(sign_cert_file, "r");
if(bioc == NULL){
printf("Invalid SM2 sign cert file path.\n");
goto err;
}
cert = PEM_read_bio_X509_AUX(bioc, NULL, NULL, NULL);
if(cert == NULL){
printf("Invalid SM2 sign cert file format.\n");
goto err;
}
pkey_sign = X509_get_pubkey(cert);
biopub = BIO_new(BIO_s_mem());
PEM_write_bio_PUBKEY(biopub, pkey_sign);
pkey_sign = PEM_read_bio_PUBKEY(biopub, NULL, NULL, NULL);
if(pkey_sign == NULL){
printf("Invalid SM2 sign key file format.\n");
goto err;
}
}else{
printf("Need to config sign cert file or sign key file.\n");
goto err;
}
if(sm4_cipher.cipher_type == SM4_CBC && !sm4_cipher.iv[0]){
printf("Cipher SM4_CBC need to set iv.\n");
goto err;
}
SM2_Enveloped_Key_dataEncode(pkey_enc, pkey_sign, &sm4_cipher, buf, &buf_len);
bioo = BIO_new_file(out, "wb");
BIO_write(bioo, buf, buf_len);
err:
if(bio)
BIO_free(bio);
if(biob)
BIO_free(biob);
if(bioc)
BIO_free(bioc);
if(bioo)
BIO_free(bioo);
if(pkey_enc)
EVP_PKEY_free(pkey_enc);
if(pkey_sign)
EVP_PKEY_free(pkey_sign);
if(cert)
X509_free(cert);
return 0;
}
以上是通过tassl的源码编写的一个生成sm2密钥对信封的小程序,进行编译后生成的可执行文件,可以通过此小程序生成密钥信封之后,用3.1 sm2密钥信封解密提到的方式进行验证解密后私钥是否正确;
[lll@MiWiFi-R4CM-srv cert]$ ./sm2enveloped -h
usage:
sm2_enveloped_key -e file -C SM4_ecb -c file -K 1234567812345678 -o file
-h :help
-e :SM2 encrypt key file path
-C :SM4 cipher, please input sm4_ecb or sm4_cbc
-c :SM2 sign cert file path
-k :SM2 sign pubkey file path
-K :SM4 cipher key(16 bytes)
-I :SM4 cipher IV(16 bytes), only uesd for sm4_cbc
-o :SM2 enveloped file path
[lll@MiWiFi-R4CM-srv cert]$ ./sm2enveloped -e ./sm2_enc.key -C sm4_ecb -c ./sm2_sign.crt -K 1234567812345678 -o sm2_enc_enveloped