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

国密SM4 OpenSSL EVP接口例子

简介

SM4算法是一个分组加密算法。该算法的分组长度为128比特,密钥长度为128比特。加密算法与密钥扩展算法都采用32轮非线性迭代结构。其密文长度等于明文长度。

加密一段数据

EVP提供的接口可以在一个函数中根据不同的参数来执行加密或加密计算。其主要接口:

int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
                      const unsigned char *key, const unsigned char *iv, int enc);
int EVP_Cipher(EVP_CIPHER_CTX *c, unsigned char *out, const unsigned char *in,
               unsigned int inl);

一个简单的例子:

// sm4_str.c
#include <errno.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

/**
 * @brief 加密或解密数据
 *
 * @param in 输入数据
 * @param inl 输入数据的长度
 * @param out 输出数据
 * @param do_encrypt 1-加密,0-解密
 * @return void 
 */
void do_crypt(const unsigned char* in, const unsigned int inlen,
             unsigned char* out, int do_encrypt, const unsigned char* key)
{
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_CipherInit_ex(ctx, EVP_sm4_cbc(), NULL, key, NULL, do_encrypt);
    EVP_Cipher(ctx, out, in, inlen);
    EVP_CIPHER_CTX_free(ctx);
    return;
}

int main(int argc, char const* argv[])
{
    unsigned char key[] = {0, 1, 2,  3,  4,  5,  6,  7,
                           8, 9, 10, 11, 12, 13, 14, 15};
    const char* str     = "hello world";
    printf("origin data= %s\n", str);

    // encrypt
    unsigned char buf[BUFSIZ] = {0};
    do_crypt((const unsigned char*)str, strlen(str), buf, 1, key);
    printf("after encrypt str(hex)= ");
    for (int i = 0; i < 12; i++) {
        printf("%2X", buf[i]);
    }

    // decrypt
    unsigned char bufout[BUFSIZ] = {0};
    do_crypt(buf, strlen((const char*)buf), bufout, 0, key);
    printf("\nafter decrypt data= %s\n", bufout);
    return 0;
}

编译执行:

$ gcc -g -Wall sm4_str.c -lcrypto -std=c11 -o sm4_str
$ ./sm4_str 
origin data= hello world
after encrypt str(hex)= 8CA754D2A26F6FC638C1953F
after decrypt data= hello world

对大数据进行加密1

OpenSSL还提供了一组接口用于对不能一次性完成加密的大数据进行加密。

int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
                      const unsigned char *key, const unsigned char *iv, int enc);
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                     const unsigned char *in, int inl);
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

一个简单的例子:

// sm4_file.c
#include <openssl/evp.h>
#include <stdio.h>

static unsigned char key[] = {0, 1, 2,  3,  4,  5,  6,  7,
                              8, 9, 10, 11, 12, 13, 14, 15};

/**
 * @brief 加密或解密文件中的数据
 * 
 * @param in 输入文件
 * @param out 输出文件
 * @param do_encrypt 1-加密,0-解密
 * @return int 成功返回0,否则返回-1
 */
int do_crypt(FILE* in, FILE* out, int do_encrypt)
{
    unsigned char inbuf[BUFSIZ], outbuf[BUFSIZ + EVP_MAX_BLOCK_LENGTH];
    int outlen;
    size_t inlen;
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    // 指定SM4初始化ctx
    EVP_CipherInit_ex(ctx, EVP_sm4_cbc(), NULL, key, NULL, do_encrypt);

    while ((inlen = fread(inbuf, 1, BUFSIZ, in)) > 0) {
        if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
            EVP_CIPHER_CTX_free(ctx); return 1;
        }
        fwrite(outbuf, 1, outlen, out);
    }
    if (!EVP_CipherFinal_ex(ctx, outbuf, &outlen)) {
        EVP_CIPHER_CTX_free(ctx); return 1;
    }
    fwrite(outbuf, 1, outlen, out);

    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

int main(int argc, char const* argv[])
{
    if (argc != 2) {
        printf("usage: %s <filename>\n", argv[0]);
        return 1;
    }

    char* encry_file_neme = "/tmp/encry_file";
    char* decry_file_name = "/tmp/decry_file";
    FILE* orig_file  = fopen(argv[1], "r");
    FILE* encry_file = fopen(encry_file_neme, "w");
  
    // encrypt
    do_crypt(orig_file, encry_file, 1);

    fclose(orig_file);
    fclose(encry_file);
    encry_file       = fopen(encry_file_neme, "r");
    FILE* decry_file = fopen(decry_file_name, "w");

    // decrypt
    do_crypt(encry_file, decry_file, 0);
    fclose(encry_file);
    fclose(decry_file);

    printf("sm4 do_encry finish. see encrypt file:%s and decrypt file:%s.\n",
           encry_file_neme, decry_file_name);
    return 0;
}

编译执行:

$ ./sm4_file sm4_file.c 
sm4 do_encry finish. see encrypt file:/tmp/encry_file and decrypt file:/tmp/decry_file.

然后就可以在 /tmp/encry_file/tmp/decry_file查看加密的文件和解密后的文件。

对大数据进行加密2

OpenSSL还提供了另一组接口,将加密和解密运算分开。

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
                       const unsigned char *key, const unsigned char *iv);
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl,
                      const unsigned char *in, int inl);
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
                       const unsigned char *key, const unsigned char *iv);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                      const unsigned char *in, int inl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

一个简单的例子:

// sm4_file1.c
#include <errno.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

#define BUFSIZE 20

/**
 * @brief 对文件内容进行加密
 *
 * @param fp 要对里面的内容进行加密的文件指针
 * @param out 加密后的数据输出
 * @param outlen 加密后的数据长度
 * @param key 秘钥,可以为NULL
 * @return int 函数返回值
 */
int do_encrypt(FILE* fp, unsigned char* out, int* outlen,
               const unsigned char* key)
{
    int tmplen          = 0;
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_sm4_ecb(), NULL, key, NULL);

    unsigned char buf[BUFSIZE] = {0};
    size_t nread               = 0;
    while ((nread = fread(buf, 1, BUFSIZE, fp)) > 0) {
        EVP_EncryptUpdate(ctx, out + tmplen, outlen, buf, nread);
        tmplen += (*outlen);
    }
    EVP_EncryptFinal_ex(ctx, out + tmplen, outlen);
    *outlen += tmplen;
    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

/**
 * @brief 对文件的加密内容进行解密
 *
 * @param fp 文件指针
 * @param out 解密后数据
 * @param outlen 解密后的数据长度
 * @param key 秘钥,可以为NULL
 * @return int 函数返回值
 */
int do_decrypt(FILE* fp, unsigned char* out, int* outlen,
               const unsigned char* key)
{
    int tmplen          = 0;
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_DecryptInit_ex(ctx, EVP_sm4_ecb(), NULL, key, NULL);

    unsigned char buf[BUFSIZE] = {0};
    size_t nread               = 0;
    while ((nread = fread(buf, 1, BUFSIZE, fp)) > 0) {
        EVP_DecryptUpdate(ctx, out + tmplen, outlen, buf, nread);
        tmplen += (*outlen);
    }
    EVP_DecryptFinal_ex(ctx, out + tmplen, outlen);
    *outlen += tmplen;
    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

int main(int argc, char const* argv[])
{
    unsigned char key[] = {0, 1, 2,  3,  4,  5,  6,  7,
                           8, 9, 10, 11, 12, 13, 14, 15};
    int ret             = 0;
    int outlen          = 0;
    if (argc < 2) {
        printf("usage: %s <file name>\n", argv[0]);
        return 1;
    }
    FILE* fp = fopen(argv[1], "r");
    // encrypt
    unsigned char out[BUFSIZ];
    ret = do_encrypt(fp, out, &outlen, key);
    fclose(fp);
    const char* enc_file = "/tmp/sm4_enc_file.txt";
    const char* dec_file = "/tmp/sm4_dec_file.txt";
    FILE* enc_fp         = fopen(enc_file, "w");
    fwrite(out, 1, outlen, enc_fp);
    fclose(enc_fp);
    printf("encrypt data write in %s\n", enc_file);

    // decrypt
    enc_fp = fopen(enc_file, "r");
    ret    = do_decrypt(fp, out, &outlen, key);
    fclose(enc_fp);

    FILE* dec_fp = fopen(dec_file, "w");
    fwrite(out, 1, outlen, dec_fp);
    fclose(dec_fp);
    printf("decrypt data write in %s\n", dec_file);
    return ret;
}

编译运行:

$ gcc -g -Wall sm4_file1.c -lcrypto -std=c11 -o sm4_file1
$ ./sm4_file1 sm4_file1.c 
encrypt data write in /tmp/sm4_enc_file.txt
decrypt data write in /tmp/sm4_dec_file.txt

然后可以在 /tmp/sm4_enc_file.txt/tmp/sm4_dec_file.txt查看加密和解密的内容。

其他

相关代码位于gitee仓库,还包括其他SM2,SM3, SM4,数字信封,RSA,MD5,SHA等代码。