【密码学实战】国密SM2算法介绍及加解密/签名代码实现示例

引言

在信息安全领域,密码算法是数据保护的核心基石。2010 年,中国国家密码管理局发布了 SM2 椭圆曲线公钥密码算法,作为国产密码标准的核心成员,它凭借高效安全的特性,逐步替代 RSA 等国际算法,广泛应用于金融、政务、物联网等关键领域。SM2 的诞生不仅是技术突破,更是国家信息安全战略的重要布局。其基于椭圆曲线密码学(ECC)的设计,在同等安全强度下,密钥长度仅为 RSA 的 1/10,运算效率提升数倍。SM2 已成为 ISO/IEC 国际标准,并深度融入国产密码生态体系,为构建安全可靠的网络安全架构提供了坚实支撑。

一、核心功能

SM2算法提供了三大核心功能:非对称加密、数字签名和密钥交换,其设计目标是通过椭圆曲线的数学特性,实现高效且安全的数据保护。

1. 密钥生成

SM2的密钥对由私钥和公钥组成:

  • 私钥:一个256位的随机整数 dd,满足 1≤d≤n−11≤d≤n−1,其中 nn 是椭圆曲线基点 GG 的阶。
  • 公钥:通过椭圆曲线点乘运算生成,即 P=[d]GP=[d]G,结果是一个椭圆曲线上的点 (x,y)(x,y),通常以非压缩格式 04∣∣x∣∣y04∣∣x∣∣y 存储(65字节)。

2. 加密与解密

SM2的加密过程采用混合加密体系,结合非对称加密和对称加密的优点:

  • 加密 :生成密文 C=C1∣∣C2∣∣C3C=C1∣∣C2∣∣C3,其中:
    • C1C1:随机数 kk 与基点 GG 的点乘结果 [k]G[k]G,用于生成临时共享密钥。
    • C2C2:明文 MM 通过对称加密(如SM3派生的密钥流)生成的密文。
    • C3C3:通过SM3哈希算法计算的完整性校验值 Hash(x2∣∣M∣∣y2)Hash(x2∣∣M∣∣y2),防止数据篡改。
  • 解密:接收方通过私钥 dd 计算共享密钥,解密 C2C2 并验证 C3C3。

3. 数字签名与验证

SM2的签名机制基于SM3哈希算法和椭圆曲线数学特性:

  • 签名生成:私钥 dd 和随机数 kk 生成签名 (r,s)(r,s),其中 rr 是 [k]G[k]G 的 xx 坐标模 nn,ss 是 (1+d)−1⋅(k−r⋅d)mod  n(1+d)−1⋅(k−r⋅d)modn。
  • 签名验证:通过公钥 PP 和签名参数 (r,s)(r,s) 验证等式 u1⋅G+u2⋅Pu1⋅G+u2⋅P 的 xx 坐标是否等于 rr。

4. 密钥交换

SM2通过椭圆曲线Diffie-Hellman(ECDH)变体实现密钥交换:

  • 客户端生成临时密钥对 (dC,QC)(dC,QC),服务端使用证书中的公钥 QSQS 与 dCdC 计算共享密钥,双方通过KDF生成会话密钥。

二、数学原理

SM2算法的安全性建立在**椭圆曲线离散对数问题(ECDLP)**的困难性上。其数学基础包括以下核心参数:

1. 椭圆曲线方程

SM2采用素数域上的椭圆曲线,方程为:

y2=x3+ax+bmod  py2=x3+ax+bmodp

其中:

  • pp:256位大素数,定义有限域 FpFp。
  • aa、bb:曲线系数,满足 4a3+27b2≠0mod  p4a3+27b2=0modp。
  • GG:基点,是曲线上的一个固定点,作为生成元。
  • nn:基点 GG 的阶,为大素数。

2. 密钥生成与点乘运算

  • 私钥生成:随机选择 d∈[1,n−1]d∈[1,n−1]。
  • 公钥生成:计算 P=[d]GP=[d]G,即基点 GG 与私钥 dd 的点乘结果。

3. 安全性分析

SM2的256位密钥长度提供约128比特的安全强度,相当于3072位RSA的安全水平。其优势在于:

  • 密钥长度短:运算效率高,适合资源受限设备(如智能卡、嵌入式系统)。
  • 抗攻击性强:基于ECDLP问题,目前无多项式时间算法可破解。

三、加解密及数字签名开源代码实现示例

1. SM2加解密代码示例:

cpp 复制代码
/*
 * This file is part of the openHiTLS project.
 *
 * openHiTLS is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "crypt_eal_pkey.h" // Header file of the interfaces for asymmetric encryption and decryption.
#include "bsl_sal.h"
#include "bsl_err.h"
#include "crypt_algid.h"
#include "crypt_errno.h"
#include "crypt_eal_rand.h"
#include "crypt_eal_init.h"
#include "crypt_types.h"

void *StdMalloc(uint32_t len) {
    return malloc((uint32_t)len);
}
void PrintLastError(void) {
    const char *file = NULL;
    uint32_t line = 0;
    BSL_ERR_GetLastErrorFileLine(&file, &line);
    printf("failed at file %s at line %d\n", file, line);
}

int main(void) {
    int32_t ret;
    BSL_ERR_Init();  // Initialize the error code module.
    /**
     * Before calling the algorithm APIs,
     * call the BSL_SAL_CallBack_Ctrl function to register the malloc and free functions.
     * Execute this step only once. If the memory allocation ability of Linux is available,
     * the two functions can be registered using Linux by default.
    */
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_MALLOC, StdMalloc);
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_FREE, free);
    ret = CRYPT_EAL_Init(CRYPT_EAL_INIT_CPU | CRYPT_EAL_INIT_PROVIDER);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }
    CRYPT_EAL_PkeyCtx *pkey = NULL;
    pkey = CRYPT_EAL_PkeyNewCtx(CRYPT_PKEY_SM2);
    if (pkey == NULL) {
        PrintLastError();
        goto EXIT;
    }

    // Initialize the random number.
    ret = CRYPT_EAL_ProviderRandInitCtx(NULL, CRYPT_RAND_SHA256, "provider=default", NULL, 0, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("RandInit: error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Generate a key pair.
    ret = CRYPT_EAL_PkeyGen(pkey);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_PkeyGen: error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Data to be encrypted.
    char *data = "test enc data";
    uint32_t dataLen = 12;
    uint8_t ecrypt[125] = {0};
    uint32_t ecryptLen = 125;
    uint8_t dcrypt[125] = {0};
    uint32_t dcryptLen = 125;
    // Encrypt data.
    ret = CRYPT_EAL_PkeyEncrypt(pkey, data, dataLen, ecrypt, &ecryptLen);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_PkeyEncrypt: error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Decrypt data.
    ret = CRYPT_EAL_PkeyDecrypt(pkey, ecrypt, ecryptLen, dcrypt, &dcryptLen);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_PkeyDecrypt: error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    if (memcmp(dcrypt, data, dataLen) == 0) {
        printf("encrypt and decrypt success\n");
    } else {
        ret = -1;
    }
EXIT:
    // Release the context memory.
    CRYPT_EAL_PkeyFreeCtx(pkey);
    CRYPT_EAL_RandDeinit();
    BSL_ERR_DeInit();
    return ret;
}

2. SM2签名代码示例:

cpp 复制代码
/*
 * This file is part of the openHiTLS project.
 *
 * openHiTLS is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "crypt_eal_pkey.h" // Header file for signature verification.
#include "bsl_sal.h"
#include "bsl_err.h"
#include "crypt_algid.h"
#include "crypt_errno.h"
#include "crypt_eal_rand.h"
#include "crypt_eal_init.h"

void *StdMalloc(uint32_t len) {
    return malloc((size_t)len);
}

void PrintLastError(void) {
    const char *file = NULL;
    uint32_t line = 0;
    BSL_ERR_GetLastErrorFileLine(&file, &line);// Obtain the name and number of lines of the error file.
    printf("failed at file %s at line %d\n", file, line);
}

int main(void)
{
    int ret;
    uint8_t userId[32] = {0};
    uint8_t key[32] = {0};
    uint8_t msg[32] = {0};
    uint8_t signBuf[100] = {0};
    uint32_t signLen = sizeof(signBuf);
    CRYPT_EAL_PkeyPrv prv = {0};
    CRYPT_EAL_PkeyPub pub = {0};
    CRYPT_EAL_PkeyCtx *ctx = NULL;

    BSL_ERR_Init(); // Initialize the error code module.
    /**
     * Before calling the algorithm APIs,
     * call the BSL_SAL_CallBack_Ctrl function to register the malloc and free functions.
     * Execute this step only once. If the memory allocation ability of Linux is available,
     * the two functions can be registered using Linux by default.
    */
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_MALLOC, StdMalloc);
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_FREE, free);
    ret = CRYPT_EAL_Init(CRYPT_EAL_INIT_CPU | CRYPT_EAL_INIT_PROVIDER);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        goto EXIT;
    }

    ctx = CRYPT_EAL_PkeyNewCtx(CRYPT_PKEY_SM2);
    if (ctx == NULL) {
        goto EXIT;
    }

    // Set a user ID.
    ret = CRYPT_EAL_PkeyCtrl(ctx, CRYPT_CTRL_SET_SM2_USER_ID, userId, sizeof(userId));
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Initialize the random number.
    ret = CRYPT_EAL_ProviderRandInitCtx(NULL, CRYPT_RAND_SHA256, "provider=default", NULL, 0, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Generate a key pair.
    ret = CRYPT_EAL_PkeyGen(ctx);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Sign.
    ret = CRYPT_EAL_PkeySign(ctx, CRYPT_MD_SM3, msg, sizeof(msg), signBuf, &signLen);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    // Verify the signature.
    ret = CRYPT_EAL_PkeyVerify(ctx, CRYPT_MD_SM3, msg, sizeof(msg), signBuf, signLen);
    if (ret != CRYPT_SUCCESS) {
        printf("error code is %x\n", ret);
        PrintLastError();
        goto EXIT;
    }

    printf("pass \n");

EXIT:
    // Release the context memory.
    CRYPT_EAL_PkeyFreeCtx(ctx);
    CRYPT_EAL_RandDeinit();
    BSL_ERR_DeInit();
    return ret;
}

【免费下载openHiTLS开源代码】

openHiTLS旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!

项目地址:https://gitcode.com/openHiTLS/openhitls