素槿
Published on 2025-08-22 / 14 Visits
0

国密SM2 OpenSSL EVP接口例子

简介

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等代码。