简介
SM2是一个非对称加密算法。主要用于加解密,数字签名,秘钥协商。私钥长度为256位,公钥是SM2椭圆曲线上的一个点,由 (x, y)
两个分量表示,每个分量长度为256位。
SM2加解密
SM2加解密是由公钥进行加密,私钥进行解密。由于SM2加解密运算速度较慢,一般不用SM2进行大数据两加解密,而是由SM4来进行。而且OpenSSL也没有提供相关的EVP接口。
OpenSSL提供了简单易用的非对称加解密接口:
int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen,
const unsigned char *in, size_t inlen);
int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen,
const unsigned char *in, size_t inlen);
一个简单的使用例子:
// sm2_enc.c
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <string.h>
/**
* @brief 生成SM2公钥和私钥文件
*
* @param pri_file 私钥文件名
* @param pub_file 公钥文件名
* @return int 成功返回0,否则返回-1
*/
int gen_key(const char* pri_file, const char* pub_file);
/**
* @brief 从文件中读取秘钥。
*
* @param key_file 公钥或私钥文件名
* @param type 0读取公钥,1读取私钥
* @return EVP_PKEY* 成功返回相应的秘钥,失败返回NULL
*/
EVP_PKEY* read_key_bio(const char* key_file, const int type);
/**
* @brief 加密数据
*
* @param key 加密公钥
* @param out 加密密文
* @param in 要加密的数据
* @param inlen 数据长度
* @return size_t 密文长度
*/
size_t do_encrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
size_t inlen);
/**
* @brief 解密数据
*
* @param key 解密私钥
* @param out 解密后的数据
* @param in 要解密的数据
* @param inlen 数据长度
* @return size_t 解密后的数据长度
*/
size_t do_decrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
size_t inlen);
int main(int argc, char const* argv[])
{
size_t ret;
const char* pub_file = "/tmp/pub_key.pem";
const char* pri_file = "/tmp/pri_key.pem";
// 生成公钥和私钥并写入文件中
if (gen_key(pri_file, pub_file)) {
printf("gen key failed.");
exit(1);
}
// 读取公钥和私钥
EVP_PKEY* pub_key = read_key_bio(pub_file, 0);
EVP_PKEY* pri_key = read_key_bio(pri_file, 1);
unsigned char data[] = "hello world !";
unsigned char enc_txt[BUFSIZ] = {0};
unsigned char dec_txt[BUFSIZ] = {0};
printf("data= %s\n", data);
// 公钥加密
ret = do_encrypt(pub_key, enc_txt, data, strlen((const char*)data));
printf("ret=%ld, enc= ", ret);
for (size_t i = 0; i < ret; i++){
printf("%2X", enc_txt[i]);
}
// 私钥解密
ret = do_decrypt(pri_key, dec_txt, enc_txt, ret);
dec_txt[ret] = 0;
printf("\nret=%ld, dec= %s\n", ret, dec_txt);
EVP_PKEY_free(pub_key);
EVP_PKEY_free(pri_key);
return 0;
}
int gen_key(const char* pri_file, const char* pub_file)
{
EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_sm2);
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(eckey, group);
BIO* param = BIO_new_file("/tmp/param.cache", "w");
PEM_write_bio_ECPKParameters(param, group);
EC_KEY_generate_key(eckey);
BIO* prikey = BIO_new_file(pri_file, "w");
BIO* pubkey = BIO_new_file(pub_file, "w");
PEM_write_bio_ECPrivateKey(prikey, eckey, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_EC_PUBKEY(pubkey, eckey);
BIO_free(param);
BIO_free(prikey);
BIO_free(pubkey);
return 0;
}
EVP_PKEY* read_key_bio(const char* key_file, const int type)
{
BIO* bio = BIO_new_file(key_file, "r");
EVP_PKEY* key = EVP_PKEY_new();
if (0 == type) {
key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
} else {
key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
}
EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2);
BIO_free(bio);
return key;
}
size_t do_encrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
size_t inlen)
{
size_t ret = 0;
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, NULL);
EVP_PKEY_encrypt_init(ctx);
EVP_PKEY_encrypt(ctx, out, &ret, in, inlen);
EVP_PKEY_CTX_free(ctx);
return ret;
}
size_t do_decrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
size_t inlen)
{
size_t ret = inlen;
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, NULL);
EVP_PKEY_decrypt_init(ctx);
EVP_PKEY_decrypt(ctx, out, &ret, in, inlen);
EVP_PKEY_CTX_free(ctx);
return ret;
}
编译运行:
$ gcc -g -Wall sm2_enc.c -lcrypto -std=c11 -o sm2_enc
$ ./sm2_enc
data= hello world !
ret=119, enc= 3075 2205A65B5F2 0DD12FE82 22F1D67EE488B675A593292F48964 0CC54D4BF4086F3 220 0ABF7B25F3E583C 6CC7FDB E6615F5B81D1A7C 1254FEDC520D177D9175E E 42057F9D8333C47DA 71A869F4C9191 46088E9BF9F2F 1 8B3E85E8E6C1E6161AD 4 DA91DBF551184C1F8465D 1469D
ret=13, dec= hello world !
SM2数据签名1
SM2主要的作用就是保证不可抵赖性,使用私钥进行签名,公钥进行验证。
OpenSSL提供了简单易用的数据签名和验证接口:
int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type,
ENGINE *e, EVP_PKEY *pkey);
int EVP_DigestSignFinal(EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen);
int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type,
ENGINE *e, EVP_PKEY *pkey);
int EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, size_t siglen,
const unsigned char *tbs, size_t tbslen);
一个简单的例子:
// sm2_ds_str.c
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
unsigned char sm2_id[] = "614837785@qq.com";
unsigned int sm2_id_len = sizeof(sm2_id);
/**
* @brief 生成SM2公钥和私钥文件
*
* @param pri_file 私钥文件名
* @param pub_file 公钥文件名
* @return int 成功返回0,否则返回-1
*/
int gen_key(const char* pri_file, const char* pub_file);
/**
* @brief 从文件中读取秘钥。
*
* @param key_file 公钥或私钥文件名
* @param type 0读取公钥,1读取私钥
* @return EVP_PKEY* 成功返回相应的秘钥,失败返回NULL
*/
EVP_PKEY* read_key_bio(const char* key_file, const int type);
int ds_sign(EVP_PKEY* pkey, const unsigned char* message,
const size_t message_len, unsigned char* sig, size_t* sig_len);
int ds_verify(EVP_PKEY* pkey, const unsigned char* message,
const size_t message_len, unsigned char* sig, size_t sig_len);
int main(int argc, char const* argv[])
{
unsigned char message[] = "hello world !";
size_t message_len = sizeof(message);
unsigned char* sig = malloc(BUFSIZ);
size_t sig_len = 0;
const char* pub_file = "/tmp/pub-key_sm2.pem";
const char* pri_file = "/tmp/pri-key_sm2.pem";
// 生成密钥对
gen_key(pri_file, pub_file);
// 读取公钥和私钥
EVP_PKEY* pub_key = read_key_bio(pub_file, 0);
EVP_PKEY* pri_key = read_key_bio(pri_file, 1);
ds_sign(pri_key, message, message_len, sig, &sig_len);
ds_verify(pub_key, message, message_len, sig, sig_len);
EVP_PKEY_free(pri_key);
EVP_PKEY_free(pub_key);
free(sig);
return 0;
}
int ds_sign(EVP_PKEY* pkey, const unsigned char* message,
const size_t message_len, unsigned char* sig, size_t* sig_len)
{
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
EVP_PKEY_CTX* sctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_CTX_set1_id(sctx, sm2_id, sm2_id_len);
EVP_MD_CTX_set_pkey_ctx(md_ctx, sctx);
EVP_DigestSignInit(md_ctx, NULL, EVP_sm3(), NULL, pkey);
EVP_DigestSign(md_ctx, NULL, sig_len, message, message_len);
sig = (unsigned char*)realloc(sig, *sig_len);
EVP_DigestSign(md_ctx, sig, sig_len, message, message_len);
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_CTX_free(sctx);
return 0;
}
int ds_verify(EVP_PKEY* pkey, const unsigned char* message,
const size_t message_len, unsigned char* sig, size_t sig_len)
{
EVP_MD_CTX* md_ctx_verify = EVP_MD_CTX_new();
EVP_PKEY_CTX* sctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_CTX_set1_id(sctx, sm2_id, sm2_id_len);
EVP_MD_CTX_set_pkey_ctx(md_ctx_verify, sctx);
EVP_DigestVerifyInit(md_ctx_verify, NULL, EVP_sm3(), NULL, pkey);
if ((EVP_DigestVerify(md_ctx_verify, sig, sig_len, message, message_len)) !=
1) {
printf("Verify SM2 signature failed!\n");
} else {
printf("Verify SM2 signature succeeded!\n");
}
EVP_PKEY_CTX_free(sctx);
EVP_MD_CTX_free(md_ctx_verify);
return 0;
}
int gen_key(const char* pri_file, const char* pub_file)
{
EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_sm2);
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(eckey, group);
BIO* param = BIO_new_file("/tmp/param.cache", "w");
PEM_write_bio_ECPKParameters(param, group);
EC_KEY_generate_key(eckey);
BIO* prikey = BIO_new_file(pri_file, "w");
BIO* pubkey = BIO_new_file(pub_file, "w");
PEM_write_bio_ECPrivateKey(prikey, eckey, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_EC_PUBKEY(pubkey, eckey);
BIO_free(param);
BIO_free(prikey);
BIO_free(pubkey);
return 0;
}
EVP_PKEY* read_key_bio(const char* key_file, const int type)
{
BIO* bio = BIO_new_file(key_file, "r");
EVP_PKEY* key = EVP_PKEY_new();
if (0 == type) {
key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
} else {
key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
}
EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2);
BIO_free(bio);
return key;
}
编译运行:
$ gcc -g -Wall sm2_ds_str.c -lcrypto -std=c11 -o sm2_ds_str
$ ./sm2_ds_str
Verify SM2 signature succeeded!
SM2数据签名2
以下这种形式主要是对较大的数据进行签名,OpenSSL提供了简单易用的接口:
int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type,
ENGINE *e, EVP_PKEY *pkey);
# define EVP_DigestSignUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
int EVP_DigestSignFinal(EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen);
int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type,
ENGINE *e, EVP_PKEY *pkey);
# define EVP_DigestVerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sig, size_t siglen);
可以看到,其中的update运算只是对摘要运算的一个定义,摘要运算相关的函数定义可以看上篇关于SM3的文章。
一个简单的例子:
// sm2_ds_file.c
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char sm2_id[] = "614837785@qq.com";
unsigned int sm2_id_len = sizeof(sm2_id);
/**
* @brief 生成SM2公钥和私钥文件
*
* @param pri_file 私钥文件名
* @param pub_file 公钥文件名
* @return int 成功返回0,否则返回-1
*/
int gen_key(const char* pri_file, const char* pub_file);
/**
* @brief 从文件中读取秘钥。
*
* @param key_file 公钥或私钥文件名
* @param type 0读取公钥,1读取私钥
* @return EVP_PKEY* 成功返回相应的秘钥,失败返回NULL
*/
EVP_PKEY* read_key_bio(const char* key_file, const int type);
/**
* @brief 对数据进行签名
*
* @param pkey 签名私钥
* @param message 数据
* @param message_len 数据长度
* @param sig 签名值
* @param sig_len 签名值长度
* @return int 0
*/
int ds_sign(EVP_PKEY* pkey, FILE* fp, unsigned char* sig, size_t* sig_len);
/**
* @brief 对签名数据进行验证
*
* @param pkey 公钥验证
* @param message 签名数据
* @param message_len 签名数据长度
* @param sig 签名值
* @param sig_len 签名值
* @return int 0
*/
int ds_verify(EVP_PKEY* pkey, FILE* fp, unsigned char* sig, size_t sig_len);
int main(int argc, char const* argv[])
{
if (argc < 2) {
printf("usage: %s <file name>\n", argv[0]);
return 0;
}
FILE* fp = fopen(argv[1], "r");
unsigned char* sig = malloc(BUFSIZ);
size_t sig_len = 0;
const char* pub_file = "/tmp/pub-key_sm2.pem";
const char* pri_file = "/tmp/pri-key_sm2.pem";
// 生成密钥对
gen_key(pri_file, pub_file);
// 读取公钥和私钥
EVP_PKEY* pub_key = read_key_bio(pub_file, 0);
EVP_PKEY* pri_key = read_key_bio(pri_file, 1);
ds_sign(pri_key, fp, sig, &sig_len);
ds_verify(pub_key, fp, sig, sig_len);
EVP_PKEY_free(pri_key);
EVP_PKEY_free(pub_key);
if (sig) {
free(sig);
}
fclose(fp);
return 0;
}
int gen_key(const char* pri_file, const char* pub_file)
{
EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_sm2);
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(eckey, group);
BIO* param = BIO_new_file("/tmp/param.cache", "w");
PEM_write_bio_ECPKParameters(param, group);
EC_KEY_generate_key(eckey);
BIO* prikey = BIO_new_file(pri_file, "w");
BIO* pubkey = BIO_new_file(pub_file, "w");
PEM_write_bio_ECPrivateKey(prikey, eckey, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_EC_PUBKEY(pubkey, eckey);
BIO_free(param);
BIO_free(prikey);
BIO_free(pubkey);
return 0;
}
EVP_PKEY* read_key_bio(const char* key_file, const int type)
{
BIO* bio = BIO_new_file(key_file, "r");
EVP_PKEY* key = EVP_PKEY_new();
if (0 == type) {
key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
} else {
key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
}
EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2);
BIO_free(bio);
return key;
}
int ds_sign(EVP_PKEY* pkey, FILE* fp, unsigned char* sig, size_t* sig_len)
{
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
EVP_PKEY_CTX* sctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_CTX_set1_id(sctx, sm2_id, sm2_id_len);
EVP_MD_CTX_set_pkey_ctx(md_ctx, sctx);
EVP_DigestSignInit(md_ctx, NULL, EVP_sm3(), NULL, pkey);
size_t nread = 0;
unsigned char buf[BUFSIZ] = {0};
while ((nread = fread(buf, 1, BUFSIZ, fp)) > 0) {
EVP_DigestSignUpdate(md_ctx, buf, nread);
}
fseek(fp, 0, SEEK_SET);
EVP_DigestSignFinal(md_ctx, NULL, sig_len);
sig = (unsigned char*)realloc(sig, *sig_len);
EVP_DigestSignFinal(md_ctx, sig, sig_len);
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_CTX_free(sctx);
return 0;
}
int ds_verify(EVP_PKEY* pkey, FILE* fp, unsigned char* sig, size_t sig_len)
{
EVP_MD_CTX* md_ctx_verify = EVP_MD_CTX_new();
EVP_PKEY_CTX* sctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_CTX_set1_id(sctx, sm2_id, sm2_id_len);
EVP_MD_CTX_set_pkey_ctx(md_ctx_verify, sctx);
EVP_DigestVerifyInit(md_ctx_verify, NULL, EVP_sm3(), NULL, pkey);
size_t nread = 0;
unsigned char buf[BUFSIZ] = {0};
while ((nread = fread(buf, 1, BUFSIZ, fp)) > 0) {
EVP_DigestVerifyUpdate(md_ctx_verify, buf, nread);
}
fseek(fp, 0, SEEK_SET);
if ((EVP_DigestVerifyFinal(md_ctx_verify, sig, sig_len)) != 1) {
printf("Verify SM2 signature failed!\n");
} else {
printf("Verify SM2 signature succeeded!\n");
}
EVP_PKEY_CTX_free(sctx);
EVP_MD_CTX_free(md_ctx_verify);
return 0;
}
编译执行:
$ gcc -g -Wall sm2_ds_file.c -lcrypto -std=c11 -o sm2_ds_file
$ ./sm2_ds_file sm2_ds_file.c
Verify SM2 signature succeeded!
其他
相关代码位于gitee仓库,还包括其他SM2,SM3, SM4,数字信封,RSA,MD5,SHA等代码。