SM2国密密钥对加密格式

目录

1、参考链接

1、什么是国密密钥对加密 

2、国密加密密钥对用途

3、加解密sm2密钥对信封

3.1 sm2密钥信封解密

        3.1.1 通过tassl来解密密钥对信封

       3.1.2 myssl.com

        3.1.3 CFCA证书管理工具

3.2 sm2密钥信封加密

3.2.1 tassl编码实现信封加密


1、参考链接

sm2格式数字信封加解密详解sm2格式数字信封加解密详解_06082a811ccf5501822d-CSDN博客

密码行业标准化技术委员会
    http://www.gmbz.org.cn/main/bzlb.html
SM2密码算法使用规范
    http://www.gmbz.org.cn/main/viewfile/2018011001400692565.html
SM2密码算法应用分析
   https://blog.csdn.net/arlaichin/article/details/23708155utm_source=itdadao&utm_medium=referral
技术科普 | 国密算法在Ultrain区块链中的运用 
    https://blog.csdn.net/Tramp_1/article/details/111603396
 

1、什么是国密密钥对加密 

         国密密钥对加密格式定义在《GMT 0009-2012 SM2密码算法使用规范》文档中,具体的规范格式如下:

     在SM2密钥对传递时,需要对SM2密钥对进行加密保护。具体的保护方法为:

  1. 产生一个对称密钥;

  2. 按对称密码算法标识指定的算法对SM2私钥进行加密,得到私钥的密文。 若对称算法为分组算法,则其运算模式为ECB;

  3. 使用外部SM2公钥加密对称密钥得到对称密钥密文;

  4. 将私钥密文、对称密钥密文封装到密钥对保护数据中。

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
  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值