【无标题】

《密码系统设计》实验

实验项目

实验序号实验名称实验学时数实验目的实验内容实验类型学生学习预期成果
实验一嵌入式开发基础6掌握Linux系统使用与开发方法Linux命令,OpenSSL(GmSSL)命令与开发验证性1.掌握常见的Linux命令与C语言开发方法;2.掌握OpenSSL(GmSSL)的基本用法与开发;3.掌握常见商用密码算法的使用

参考相关内容,在 Ubuntu或openEuler中(推荐 openEuler)中使⽤OpenSSL库编程实现调⽤SM2(加密解密,签名验签),SM3(摘要计算,HMAC 计算),SM4(加密解密)算法,使⽤Markdown记录详细记录实践过程,每完成⼀项git commit ⼀次。(5’)

SM2
代码
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
EVP_PKEY *load_private_key(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        perror("Unable to open file");
        return NULL;
    }
    EVP_PKEY *private_key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    if (!private_key) {
        ERR_print_errors_fp(stderr);
    }
    fclose(fp);
    return private_key;
}
EVP_PKEY* load_public_key(const char* filename) {
    EVP_PKEY *key = NULL;
    BIO *bio = BIO_new(BIO_s_file());
    if (!bio) {
        fprintf(stderr, "Unable to create file BIO\n");
        return NULL;
    }
    BIO_read_filename(bio, filename);
    key = PEM_read_bio_PUBKEY(bio, NULL, 0, NULL);
    BIO_free(bio);
    if (!key) {
        fprintf(stderr, "Unable to load public key\n");
    }
    return key;
}
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
int sm2_encrypt(const unsigned char *input_file,EVP_PKEY *pub_key,unsigned char **ciphertext, size_t *ciphertext_len) {
    EVP_PKEY_CTX *pctx = NULL;
    int ret = 0;
    // Check for a valid public key
    if (!pub_key) {
        fprintf(stderr, "Invalid public key.\n");
        return 0;
    }
    pctx = EVP_PKEY_CTX_new(pub_key, NULL);
    if (!pctx) {
        fprintf(stderr, "Unable to create EVP_PKEY_CTX.\n");
        ERR_print_errors_fp(stderr);
        return 0;
    }
    unsigned char* buf = NULL;
    size_t buf_len = 0;
    if(read_file(input_file, &buf, &buf_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    if (EVP_PKEY_encrypt_init(pctx) <= 0) {
        fprintf(stderr, "Encryption initialization failed.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    if (EVP_PKEY_encrypt(pctx, NULL, ciphertext_len, buf, buf_len) <= 0) {
        fprintf(stderr, "Failed to determine ciphertext length.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    *ciphertext = OPENSSL_malloc(*ciphertext_len);
    if (!*ciphertext) {
        fprintf(stderr, "Unable to allocate memory for ciphertext.\n");
        goto cleanup;
    }
    if (EVP_PKEY_encrypt(pctx, *ciphertext, ciphertext_len, buf, buf_len) <= 0) {
        fprintf(stderr, "Encryption failed.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    ret = 1;
    free(buf);
    buf = NULL;
cleanup:
    if (!ret && *ciphertext) {
        OPENSSL_free(*ciphertext);
        *ciphertext = NULL;
        *ciphertext_len = 0;
    }
    if (pctx) {
        EVP_PKEY_CTX_free(pctx);
    }
    return ret;
}
int sm2_decrypt(const unsigned char *input_file,EVP_PKEY *priv_pkey,unsigned char **plaintext, size_t *plaintext_len) {
    EVP_PKEY_CTX *pctx = NULL;
    int ret = 0;

    if (!priv_pkey) {
        fprintf(stderr, "Invalid pri key. \n");
        return 0;
    }
    unsigned char* buf = NULL;
    size_t buf_len = 0;
    if(read_file(input_file, &buf, &buf_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    pctx = EVP_PKEY_CTX_new(priv_pkey, NULL);
    if (!pctx) {
        fprintf(stderr, "Unable to create EVP_PKEY_CTX.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    if (EVP_PKEY_decrypt_init(pctx) <= 0) {
        fprintf(stderr, "Decryption initialization failed.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    if (EVP_PKEY_decrypt(pctx, NULL, plaintext_len, buf, buf_len) <= 0) {
        fprintf(stderr, "Failed to determine plaintext length.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    *plaintext = OPENSSL_malloc(*plaintext_len);
    if (!*plaintext) {
        fprintf(stderr, "Unable to allocate memory for plaintext.\n");
        goto cleanup;
    }
    if (EVP_PKEY_decrypt(pctx, *plaintext, plaintext_len, buf, buf_len) <= 0) {
        fprintf(stderr, "Decryption failed.\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    printf("plaintext:%s", *plaintext);
    ret = 1;
cleanup:
    if (!ret && *plaintext) {
        OPENSSL_free(*plaintext);
        *plaintext = NULL;
        *plaintext_len = 0;
    }
    if (pctx) {
        EVP_PKEY_CTX_free(pctx);
    }
    return ret;
}
int generate_sm2_key(const char *priv_key_file, const char *pub_key_file) {
    EC_KEY *ec_key = NULL;
    EVP_PKEY *pkey = NULL;
    FILE *priv_fp = NULL;
    FILE *pub_fp = NULL;
    int ret = 1;
    ec_key = EC_KEY_new_by_curve_name(NID_sm2);
    if (!ec_key) {
        ERR_print_errors_fp(stderr);
        return ret;
    }
    if (!EC_KEY_generate_key(ec_key)) {
        fprintf(stderr, "Failed to generate SM2 key\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    pkey = EVP_PKEY_new();
    if (!pkey || !EVP_PKEY_assign_EC_KEY(pkey, ec_key)) {
        fprintf(stderr, "Failed to assign EC_KEY to EVP_PKEY\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    ec_key = NULL;
    priv_fp = fopen(priv_key_file, "wb");
    if (!priv_fp || !PEM_write_PrivateKey(priv_fp, pkey, NULL, NULL, 0, NULL, NULL)) {
        fprintf(stderr, "Failed to write private key\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    pub_fp = fopen(pub_key_file, "wb");
    if (!pub_fp || !PEM_write_PUBKEY(pub_fp, pkey)) {
        fprintf(stderr, "Failed to write public key\n");
        ERR_print_errors_fp(stderr);
        goto cleanup;
    }
    printf("SM2 key pair generated and saved successfully.\n");
    ret = 0;
cleanup:
    if (pkey) EVP_PKEY_free(pkey);
    if (ec_key) EC_KEY_free(ec_key);
    if (priv_fp) fclose(priv_fp);
    if (pub_fp) fclose(pub_fp);
    return ret;
}
int sm2_sign(const unsigned char *input_file, EVP_PKEY *priv_key, unsigned char **signature, size_t *sig_len) {
    unsigned char *sig = NULL;
    unsigned char *message = NULL;
    size_t len = 0;
    if(read_file(input_file,&message, &len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    int ret = 0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) {
        fprintf(stderr, "Failed to create signing context\n");
        goto end;
    }
    if (EVP_DigestSignInit(ctx, NULL, EVP_sm3(), NULL, priv_key) <= 0) {
        fprintf(stderr, "Failed to init signing\n");
        goto end;
    }
    if (EVP_DigestSignUpdate(ctx, message, len) <= 0) {
        fprintf(stderr, "Failed to update signing\n");
        goto end;
    }
    size_t tmp_sig_len = 0;
    if (EVP_DigestSignFinal(ctx, NULL, &tmp_sig_len) <= 0) {
        fprintf(stderr, "Failed to get signature length\n");
        goto end;
    }
    sig = OPENSSL_malloc(tmp_sig_len);
    if (EVP_DigestSignFinal(ctx, sig, &tmp_sig_len) <= 0) {
        fprintf(stderr, "Failed to sign\n");
        goto end;
    }
    *signature = (char *)sig;
    *sig_len = tmp_sig_len;
    ret = 1;
    end:
    if (!ret) {
        ERR_print_errors_fp(stderr);
        OPENSSL_free(sig);
    }
    EVP_MD_CTX_free(ctx);
    return ret;
}
int sm2_verify(const unsigned char *input_file, EVP_PKEY *pub_key, const unsigned char *signature) {
    int ret = 0;
    unsigned char *message = NULL, *sign = NULL;
    size_t len = 0, sig_len = 0;
    if(read_file(input_file,&message, &len) || read_file(signature, &sign, &sig_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) {
        fprintf(stderr, "Failed to create verification context\n");
        goto end;
    }
    if (EVP_DigestVerifyInit(ctx, NULL, EVP_sm3(), NULL, pub_key) <= 0) {
        fprintf(stderr, "Failed to init verification\n");
        goto end;
    }
    if (EVP_DigestVerifyUpdate(ctx, message, len) <= 0) {
        fprintf(stderr, "Failed to update verification\n");
        goto end;
    }
    if (EVP_DigestVerifyFinal(ctx, (const unsigned char *)sign, sig_len) <= 0) {
        fprintf(stderr, "Failed to verify\n");
        goto end;
    }
    ret = 1;
    end:
    if (!ret) {
        ERR_print_errors_fp(stderr);
    }
    EVP_MD_CTX_free(ctx);
    return ret;
}
void usage(){
    printf("usage: -key\n");
    printf("       -e -kfile -file\n");
    printf("       -d -kfile -file\n");
    printf("       -s -kfile -file\n");
    printf("       -v -kfile -file -sig\n");
}
int check(char * argv[]){
    if(strcmp(argv[4], "-file") != 0){
        usage();
        return 1;
    }
    if (argv[5] == NULL || argv[5][0] == '\0') {
        fprintf(stderr, "No file provided\n");
        return 1;
    }
    return 0;
}
int main(int argc, char * argv[]){
    if(argc == 1){
        usage();
        return 0;
    }
    if(strcmp(argv[1], "-key") == 0){
        const char *priv_key_file = "sm2_private.pem";
        const char *pub_key_file = "sm2_public.pem";
        generate_sm2_key(priv_key_file,pub_key_file);
    }else if(argc <= 5){
        usage();
    }else if(strcmp(argv[1], "-e") == 0 && strcmp(argv[2], "-kfile") == 0){
        EVP_PKEY *pub_key = load_public_key(argv[3]);
        unsigned char * ciphertext = NULL;
        size_t ciphertextlen = 0;
        if(!pub_key){
            fprintf(stderr, "Failed to load keys\n");
            return 1;
        }
        if(check(argv)){
            EVP_PKEY_free(pub_key);
            return 1;
        }
        if (sm2_encrypt((const unsigned char *)argv[5], pub_key, &ciphertext, &ciphertextlen) == 1) {
            printf("SM2 Encryption OK\n");
            const char *output_file = "encrypt.txt";
            FILE* fp_out = fopen(output_file, "wb");
            if (!fp_out) {
                printf("SM2 Save Error\n");
                perror("Error opening output file\n");
                fclose(fp_out);
                return 1;
            }
            fwrite(ciphertext, 1, ciphertextlen, fp_out);
            printf("SM2 Save ok\n");
            fclose(fp_out);
        } else {
            fprintf(stderr, "Encryption failed\n");
             if (ciphertext) {
                OPENSSL_free(ciphertext);
            }
        }
    }else if(strcmp(argv[1], "-d") == 0 && strcmp(argv[2], "-kfile") == 0){
        EVP_PKEY *priv_key = load_private_key(argv[3]);
        unsigned char * plaintext = NULL;
        size_t plaintextlen = 0;
        if(!priv_key){
            fprintf(stderr, "Failed to load keys\n");
            return 1;
        }
        if(check(argv)){
            EVP_PKEY_free(priv_key);
            return 1;
        }
        if (sm2_decrypt((const unsigned char *)argv[5], priv_key, &plaintext, &plaintextlen) == 1) {
            printf("SM2 Decryption OK\n");
            const char *output_file = "decrypt.txt";
            FILE* fp_out = fopen(output_file, "wb");
            if (!fp_out) {
                printf("SM2 Save Error\n");
                perror("Error opening output file\n");
                fclose(fp_out);
                return 1;
            }
            fwrite(plaintext, 1, plaintextlen, fp_out);
            printf("SM2 Save ok\n");
            fclose(fp_out);
        } else {
            fprintf(stderr, "Decryption failed\n");
            if (plaintext) {
                OPENSSL_free(plaintext); 
            }
        }
    }else if(strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-kfile") == 0){
        EVP_PKEY *priv_key = load_private_key(argv[3]);
        unsigned char * signtext = NULL;
        size_t signlen = 0;
        if(!priv_key){
            fprintf(stderr, "Failed to load keys\n");
            return 1;
        }
        if(check(argv)){
            EVP_PKEY_free(priv_key);
            return 1;
        }
        if(sm2_sign((const unsigned char *)argv[5], priv_key, &signtext, &signlen) == 1){
            printf("SM2 signature OK\n");
            const char *output_file = "signature.sig";
            FILE* fp_out = fopen(output_file, "wb");
            if (!fp_out) {
                printf("SM2 Save Error\n");
                perror("Error opening output file\n");
                fclose(fp_out);
                return 1;
            }
            fwrite(signtext, 1, signlen, fp_out);
            printf("SM2 Save ok\n");
            fclose(fp_out);
        }else{
            fprintf(stderr, "Signature failed\n");
            if (signtext) {
                OPENSSL_free(signtext);
            }
        }
    }
    else if(strcmp(argv[1], "-v") == 0 && strcmp(argv[2], "-kfile") == 0){
        EVP_PKEY *pub_key = load_public_key(argv[3]);
        if(!pub_key){
            fprintf(stderr, "Failed to load keys\n");
            return 1;
        }
        if(check(argv)){
            EVP_PKEY_free(pub_key);
            return 1;
        }
        if(argc <= 6 || strcmp(argv[6], "-sig") != 0){
            usage();
            return 0;
        }
        if (argv[7] == NULL || argv[7][0] == '\0') {
            fprintf(stderr, "No signature provided\n");
            return 1;
        }
        if(sm2_verify((const unsigned char *)argv[5], pub_key, (const unsigned char *)argv[7])){
            printf("Signature verified successfully.\n");
        }else{
            printf("Signature verification failed.\n");
        }
        EVP_PKEY_free(pub_key);
    }else{
        usage();
    }
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm2-1 -key
SM2 key pair generated and saved successfully.
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm2-1 -e -kfile sm2_public.pem -file plain.txt 
SM2 Encryption OK
SM2 Save ok
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm2-1 -d -kfile sm2_private.pem -file encrypt.txt 
plaintext:20221327俞鸿若劼
SM2 Decryption OK
SM2 Save ok
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm2-1 -s -kfile sm2_private.pem -file plain.txt
SM2 signature OK
SM2 Save ok
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm2-1 -v -kfile sm2_public.pem -file plain.txt -sig signature.sig
Signature verified successfully.
SM3
Hash
#include <openssl/evp.h>
#include <string.h>
void dumpbuf(const unsigned char *buf, int len) {
    for (int i = 0; i < len; i++) {
        printf("%02x", buf[i]);
    }
    printf("\n");
}
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
int sm3_hash(const unsigned char *input_file, unsigned char *hash, unsigned int *hash_len)
{
    unsigned char *message = NULL;
    size_t len = 0;
    if(read_file(input_file,&message, &len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    EVP_MD_CTX *md_ctx = NULL;
    const EVP_MD *md = NULL;
    int ret = -1;
    md_ctx = EVP_MD_CTX_new();
    if (md_ctx == NULL) {
        return ret;
    }
    md = EVP_sm3();
    if (md == NULL) {
        EVP_MD_CTX_free(md_ctx);
        return ret;
    }
    if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) {
        goto cleanup;
    }
    if (EVP_DigestUpdate(md_ctx, message, len) != 1) {
        goto cleanup;
    }
    if (EVP_DigestFinal_ex(md_ctx, hash, hash_len) != 1) {
        goto cleanup;
    }
    ret = 0;
    cleanup:
    EVP_MD_CTX_free(md_ctx);
    return ret;
}
int main(int argc, char * argv[]){
    unsigned char hash_value[32];
    unsigned int hash_len;
    if(argc < 3){
        printf("usage: -in filename\n");
        return 0;
    }
    if(strcmp(argv[1], "-in") != 0){
        printf("usage: -in filename\n");
        return 0;
    }
    int ret = sm3_hash((const unsigned char *)argv[2], hash_value, &hash_len);
    if (ret != 0) {
        fprintf(stderr, "SM3 Hash failed\n");
        return 1;
    }
    printf("SM3 Hash OK\n");
    const char *output_file = "hash.bin";
    FILE* fp_out = fopen(output_file, "wb");
    if (!fp_out) {
        printf("SM3 Save Error\n");
        perror("Error opening output file\n");
        fclose(fp_out);
        return 1;
    }
    dumpbuf(hash_value, hash_len);
    fwrite(hash_value, 1, hash_len, fp_out);
    printf("SM3 Save ok\n");
    fclose(fp_out);
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm3 -in plain.txt 
SM3 Hash OK
00000000: 79ae c2e8 7f3d 3110 bd84 5866 ce1d 1633  y....=1...Xf...3
00000010: c5af b953 932f 4694 54fd 5a2e 6ca6 03ee  ...S./F.T.Z.l...
SM3 Save ok
hmac
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string.h>
#define BLOCK_LENGTH 64
#define L 32
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
void XOR(unsigned char *out, const unsigned char *in1, const unsigned char *in2, size_t len) {
    for (size_t i = 0; i < len; i++) {
        out[i] = in1[i] ^ in2[i];
    }
}
unsigned char* SM3_HMAC(const unsigned char *key, size_t key_len, const unsigned char *data, size_t data_len, unsigned char *mac, size_t *mac_len) {
    unsigned char key_pad[BLOCK_LENGTH] = {0};
    unsigned char o_key_pad[BLOCK_LENGTH];
    unsigned char inner_hash[L];
    unsigned char final_hash[L];
    if (key_len > BLOCK_LENGTH) {
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx) {
            fprintf(stderr, "Unable to create MD context\n");
            return NULL;
        }
        EVP_DigestInit_ex(ctx, EVP_sm3(), NULL);
        EVP_DigestUpdate(ctx, key, key_len);
        unsigned char hashed_key[L];
        EVP_DigestFinal_ex(ctx, hashed_key, NULL);
        memcpy(key_pad, hashed_key, L);
        EVP_MD_CTX_free(ctx);
    } else {
        memcpy(key_pad, key, key_len);
    }
    memset(o_key_pad, 0x5c, BLOCK_LENGTH);
    memset(key_pad, 0x36, BLOCK_LENGTH);
    XOR(o_key_pad, o_key_pad, key_pad, BLOCK_LENGTH);
    EVP_MD_CTX *inner_ctx = EVP_MD_CTX_new();
    if (!inner_ctx) {
        fprintf(stderr, "Unable to create MD context\n");
        return NULL;
    }
    EVP_DigestInit_ex(inner_ctx, EVP_sm3(), NULL);
    EVP_DigestUpdate(inner_ctx, key_pad, BLOCK_LENGTH);
    EVP_DigestUpdate(inner_ctx, data, data_len);
    EVP_DigestFinal_ex(inner_ctx, inner_hash, NULL);
    EVP_MD_CTX_free(inner_ctx);
    EVP_MD_CTX *outer_ctx = EVP_MD_CTX_new();
    if (!outer_ctx) {
        fprintf(stderr, "Unable to create MD context\n");
        return NULL;
    }
    EVP_DigestInit_ex(outer_ctx, EVP_sm3(), NULL);
    EVP_DigestUpdate(outer_ctx, o_key_pad, BLOCK_LENGTH);
    EVP_DigestUpdate(outer_ctx, inner_hash, L);
    EVP_DigestFinal_ex(outer_ctx, final_hash, NULL);
    EVP_MD_CTX_free(outer_ctx);
    memcpy(mac, final_hash, L);
    *mac_len = L;
    return mac;
}
int main(int argc, char * argv[]) {
    if(argc < 5){
        printf("usage: -kfile -file");
        return 0;
    }
    if(strcmp(argv[1], "-kfile") == 0 && strcmp(argv[3], "-file") == 0){
        unsigned char *key, *data;
        unsigned char mac[L];
        size_t mac_len, key_len, data_len;
        if(read_file((const unsigned char*)argv[2], &key, &key_len) || read_file((const unsigned char *)argv[4], &data, &data_len)){
            fprintf(stderr, "Unable to read file.\n");
            return 0;
        }
        SM3_HMAC(key, key_len, data, data_len, mac, &mac_len);
        printf("HMAC-SM3: ");
        for (size_t i = 0; i < mac_len; i++) {
            printf("%02x", mac[i]);
        }
        printf("\n");

        const char *output_file = "SM3-HMAC.bin";
        FILE* fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("SM3 Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(mac, 1, L, fp_out);
        printf("SM3-HMAC Save ok\n");
        fclose(fp_out);
    }
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ ./sm3-hmac -k'0123456789abcdef0123456789abcdef' -file plain.txt 
HMAC-SM3: 6e034d26659e5a75539253a0881dc9887e1a53dd85d5d667fd1a76f23108fbb2
SM3-HMAC Save ok
SM4
代码
#include "openssl/evp.h"
#include <string.h>
#include <stdio.h>
#define FAILURE -1
#define SUCCESS 0
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
void print_hex(const char *title, const unsigned char *data, int len) {
    printf("%s:", title);
    for (int i = 0; i < len; i++) {
        if (i % 16 == 0)
            printf("\n%04X: ", i);
        printf("%02X ", data[i]);
    }
    printf("\n");
}
int do_encrypt(const unsigned char * input_key, const unsigned char * input_iv, const unsigned char * input_message,const EVP_CIPHER *type, const char *ctype)
{
    unsigned char outbuf[1024];
    int outlen, tmplen; 
    unsigned char *key, *iv, *message;
    size_t key_len, iv_len, message_len;
    if(read_file(input_key, &key, &key_len) || read_file(input_iv, &iv, &iv_len)) {
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    if(key_len != 16 || iv_len != 16) {
        fprintf(stderr, "Error Key or Iv.\n");
        return 0;
    }
    if(read_file(input_message, &message, &message_len)) {
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        printf("Failed to create EVP_CIPHER_CTX\n");
        return FAILURE;
    } 
    if (EVP_EncryptInit_ex(ctx, type, NULL, key, iv) != 1) {
        printf("EVP_EncryptInit_ex failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    }  
    if (EVP_EncryptUpdate(ctx, outbuf, &outlen, (unsigned char*)message, message_len) != 1) {
        printf("EVP_EncryptUpdate failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    } 
    if (EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen) != 1) {
        printf("EVP_EncryptFinal_ex failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    }
    outlen += tmplen; 
    EVP_CIPHER_CTX_free(ctx); 
    char filename[64];
    sprintf(filename, "./cipher_%s.dat", ctype);
    FILE *out = fopen(filename, "wb+");
    if (!out) {
        printf("Cannot open file: %s\n", filename);
        return FAILURE;
    }
    fwrite(outbuf, 1, outlen, out);
    fflush(out);
    fclose(out);
    printf("%s encryption successful. Ciphertext length: %d bytes\n", ctype, outlen);
    print_hex("Ciphertext", outbuf, outlen);
    return SUCCESS;
}
int do_decrypt(const unsigned char * input_key, const unsigned char * input_iv, const unsigned char * input_message,const EVP_CIPHER *type, const char *ctype)
{
    unsigned char inbuf[1024] = { 0 };
    unsigned char outbuf[1024] = { 0 };
    int outlen, inlen, tmplen;
    unsigned char *key, *iv, *message;
    size_t key_len, iv_len, message_len;
    if(read_file(input_key, &key, &key_len) || read_file(input_iv, &iv, &iv_len)) {
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    if(key_len != 16 || iv_len != 16) {
        fprintf(stderr, "Error Key or Iv.\n");
        return 0;
    }
    if(read_file(input_message, &message, &message_len)) {
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        printf("Failed to create EVP_CIPHER_CTX\n");
        return FAILURE;
    } 
    if (EVP_DecryptInit_ex(ctx, type, NULL, key, iv) != 1) {
        printf("EVP_DecryptInit_ex failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    } 
    if (EVP_DecryptUpdate(ctx, outbuf, &outlen, message, message_len) != 1) {
        printf("EVP_DecryptUpdate failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    }  
    if (EVP_DecryptFinal_ex(ctx, outbuf + outlen, &tmplen) != 1) {
        printf("EVP_DecryptFinal_ex failed for %s\n", ctype);
        EVP_CIPHER_CTX_free(ctx);
        return FAILURE;
    }
    outlen += tmplen; 
    EVP_CIPHER_CTX_free(ctx);
    printf("%s decryption result: \n%.*s\n", ctype, outlen, outbuf);
    print_hex("Decrypted plaintext (hex)", outbuf, outlen);
    return SUCCESS;
}

int main(int argc, char *argv[])
{
    OpenSSL_add_all_algorithms();
    const EVP_CIPHER *sm4_cbc = EVP_sm4_cbc();
    if (!sm4_cbc) {
        printf("Cannot get SM4-CBC cipher\n");
        return FAILURE;
    }

    if(argc < 8){
        printf("usage: -e/-d -kfile -iv -file");
        return 0;
    }
    if(strcmp(argv[1], "-e") == 0){
        if(strcmp(argv[2], "-kfile") == 0 && strcmp(argv[4], "-iv") == 0 && strcmp(argv[6], "-file") == 0)
            if (do_encrypt((unsigned char*)argv[3], (unsigned char*)argv[5],(unsigned char*)argv[7],sm4_cbc, "sm4-cbc") != SUCCESS) {
                printf("SM4-CBC encryption failed\n");
                EVP_cleanup();
                return FAILURE;
            }
    }else if(strcmp(argv[1], "-d") == 0){
        if(strcmp(argv[2], "-kfile") == 0 && strcmp(argv[4], "-iv") == 0 && strcmp(argv[6], "-file") == 0){
            if (do_decrypt((unsigned char*)argv[3], (unsigned char*)argv[5],(unsigned char*)argv[7],sm4_cbc, "sm4-cbc") != SUCCESS) {
                printf("SM4-CBC decryption failed\n");
                EVP_cleanup();
                return FAILURE;
            }
        }
    }else{
        printf("usage: -e/-d -kfile -iv -file");
        return 0;
    } 
    EVP_cleanup();
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm4$ ./sm4 -e -kfile key.bin -iv iv.bin -file plain.txt 
sm4-cbc encryption successful. Ciphertext length: 16 bytes
Ciphertext:
0000: F7 9A 8F C5 65 49 CE 3D 2D 63 63 C4 CC BE C5 A5
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm4$ ./sm4 -d -kfile key.bin -iv iv.bin -file cipher_sm4-cbc.dat 
sm4-cbc decryption result:
20221327
Decrypted plaintext (hex):
0000: 32 30 32 32 31 34 32 34 0A

参考相关内容,在 Ubuntu或openEuler中(推荐 openEuler)中使⽤GmSSL库编程实现调⽤SM2(加密解密,签名验签),SM3(摘要计算,HMAC 计算),SM4(加密解密)算法,使⽤Markdown记录详细记录实践过程,每完成⼀项git commit ⼀次。(5’)

SM2
代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gmssl/sm2.h>
#include <gmssl/sm3.h>
#include <gmssl/error.h>
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
void print_hex(const char *label, const uint8_t *data, size_t len) {
    printf("%s: ", label);
    for(size_t i = 0; i < len; i++) {
        printf("%02X", data[i]);
    }
    printf("\n");
}

int gmssl_sm2_encrypt(const unsigned char * input_file, SM2_KEY* key){
    unsigned char *plaintext = NULL;
    size_t plaintext_len = 0;
    if(read_file(input_file,&plaintext, &plaintext_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    };
    uint8_t ciphertext[SM2_MAX_CIPHERTEXT_SIZE];
    size_t ciphertext_len = sizeof(ciphertext);
    int ret = sm2_encrypt(key, (const uint8_t *)plaintext, plaintext_len, ciphertext, &ciphertext_len);
    if (ret != 1) {
        fprintf(stderr, "SM2 加密失败\n");
        return EXIT_FAILURE;
    }
    printf("加密成功。\n");
    print_hex("密文", ciphertext, ciphertext_len);
    const char *output_file = "encrypt.txt";
    FILE* fp_out = fopen(output_file, "wb");
    if (!fp_out) {
        perror("Error opening output file\n");
        fclose(fp_out);
        return 1;
    }
    fwrite(ciphertext, 1, ciphertext_len, fp_out);
    printf("Save ok\n");
    fclose(fp_out);
}
int gmssl_sm2_decrypt(const unsigned char * input_file, SM2_KEY* key){
    unsigned char *ciphertext = NULL;
    size_t ciphertext_len = 0;
    if(read_file(input_file,&ciphertext, &ciphertext_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    };
    uint8_t decrypted[SM2_MAX_PLAINTEXT_SIZE];
    size_t decrypted_len = sizeof(decrypted);
    printf("解密密文...\n");
    int ret = sm2_decrypt(key, (const uint8_t *)ciphertext, ciphertext_len, decrypted, &decrypted_len);
    if (ret != 1) {
        fprintf(stderr, "SM2 解密失败\n");
        return EXIT_FAILURE;
    }
    printf("解密成功。\n");
    decrypted[decrypted_len] = '\0';
    printf("解密后的明文: %s\n", decrypted);
    const char *output_file = "decrypt.txt";
    FILE* fp_out = fopen(output_file, "wb");
    if (!fp_out) {
        perror("Error opening output file\n");
        fclose(fp_out);
        return 1;
    }
    fwrite(decrypted, 1, decrypted_len, fp_out);
    printf("Save ok\n");
    fclose(fp_out);
}
int gmssl_sm2_sign(const unsigned char * input_file, SM2_KEY* key){
    char *message = NULL;
    size_t message_len = 0;
    if(read_file(input_file,&message, &message_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    };
    SM2_SIGN_CTX sign_ctx;
    sm2_sign_init(&sign_ctx, key, SM2_DEFAULT_ID, SM2_DEFAULT_ID_LENGTH);
    int ret = sm2_sign_update(&sign_ctx, (const uint8_t *)message, message_len);
    if (ret != 1) {
        fprintf(stderr, "SM2 签名更新失败\n");
        return EXIT_FAILURE;
    }
    uint8_t signature[SM2_MAX_SIGNATURE_SIZE];
    size_t signature_len = sizeof(signature);
    ret = sm2_sign_finish(&sign_ctx, signature, &signature_len);
    if (ret != 1) {
        fprintf(stderr, "SM2 签名失败\n");
        return EXIT_FAILURE;
    }
    printf("签名成功。\n");
    print_hex("签名", signature, signature_len);
    const char *output_file = "signature.sig";
    FILE* fp_out = fopen(output_file, "wb");
    if (!fp_out) {
        perror("Error opening output file\n");
        fclose(fp_out);
        return 1;
    }
    fwrite(signature, 1, signature_len, fp_out);
    printf("Save ok\n");
    fclose(fp_out);
}
int gmssl_sm2_verify(const unsigned char * input_file, const unsigned char * input_sign, SM2_KEY* key){
    unsigned char *message = NULL;
    size_t message_len = 0;
    if(read_file(input_file,&message, &message_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    uint8_t *signature = NULL;
    size_t signature_len = 0;
    if(read_file(input_sign,&signature, &signature_len)){
        fprintf(stderr, "Unable to read file.\n");
        return 0;
    }
    printf("%s\n", message);
    print_hex("签名",signature,signature_len);
    SM2_SIGN_CTX verify_ctx;
    sm2_verify_init(&verify_ctx, key, SM2_DEFAULT_ID, SM2_DEFAULT_ID_LENGTH);
    int ret = sm2_verify_update(&verify_ctx, (const uint8_t *)message, message_len);
    if (ret != 1) {
        fprintf(stderr, "SM2 签名验证更新失败\n");
        return EXIT_FAILURE;
    }

    ret = sm2_verify_finish(&verify_ctx, signature, signature_len);
    if (ret == 1) {
        printf("签名验证成功。\n");
    } else {
        printf("签名验证失败。\n");
        return EXIT_FAILURE;
    }
}
int generate_sm2_key(){
    int ret;
    SM2_KEY key;
    printf("生成 SM2 密钥对...\n");
    ret = sm2_key_generate(&key);
    if (ret != 1) {
        fprintf(stderr, "SM2 密钥生成失败\n");
        return 1;
    }
    printf("SM2 密钥生成成功。\n");
    FILE *priv_fp = fopen("private_key.bin", "wb");
    if (priv_fp == NULL) {
        fprintf(stderr, "打开私钥文件失败\n");
        return EXIT_FAILURE;
    }
    FILE *pub_fp = fopen("public_key.bin", "wb");
    if (pub_fp == NULL) {
        fprintf(stderr, "打开公钥文件失败\n");
        return EXIT_FAILURE;
    }
    uint8_t prikey[32];
    sm2_z256_to_bytes(key.private_key, prikey);
    uint8_t octets[65];
    octets[0] = SM2_point_uncompressed;
    (void)sm2_z256_point_to_bytes(&key.public_key, octets + 1);
    fwrite(prikey, 1, sizeof(prikey), priv_fp);
    fclose(priv_fp);
    fwrite(octets, 1, sizeof(octets), pub_fp);
    fclose(pub_fp);
    sm2_key_print(stdout, 0, 4, "SM2", &key);
    return 0;
}
int load_pri_key(const unsigned char* pri, SM2_KEY* mykey){
    if (mykey == NULL) {
        fprintf(stderr, "提供的SM2_KEY结构指针无效\n");
        return 1;
    }
    FILE *priv_fp = fopen(pri, "rb");
    if (priv_fp == NULL) {
        fprintf(stderr, "打开私钥文件失败\n");
        return 1;
    }
    uint8_t prikey[32];
    size_t len;
    len = fread(prikey, 1, sizeof(prikey), priv_fp);
    if (len != sizeof(prikey)) {
        fprintf(stderr, "读取私钥数据失败\n");
        fclose(priv_fp);
        return 1;
    }
    fclose(priv_fp);
    sm2_z256_from_bytes(mykey->private_key, prikey);
    return 0;
}
int load_pub_key(const unsigned char* pub, SM2_KEY* mykey){
    if (mykey == NULL) {
        fprintf(stderr, "提供的SM2_KEY结构指针无效\n");
        return 1;
    }
    FILE *pub_fp = fopen(pub, "rb");
    if (pub_fp == NULL) {
        fprintf(stderr, "打开公钥文件失败\n");
        return 1;
    }
    uint8_t octets[65];
    size_t len;
    len = fread(octets, 1, sizeof(octets), pub_fp);
    if (len != sizeof(octets)) {
        fprintf(stderr, "读取公钥数据失败\n");
        fclose(pub_fp);
        return 1;
    }
    fclose(pub_fp);
    if (sm2_z256_point_from_bytes(&mykey->public_key, octets + 1) != 1) {
        fprintf(stderr, "密钥数据加载失败\n");
        return 1;
    }
    return 0;
}
int load_key(const char* pri_file, const char* pub_file, SM2_KEY* mykey) {
    load_pub_key(pub_file, mykey);
    load_pri_key(pri_file, mykey);
    return 0;
}

void usage(){
    printf("usage: -key\n");
    printf("       -e -kfile -file\n");
    printf("       -d -kfile -file\n");
    printf("       -s -pri -pub -file\n");
    printf("       -v -kfile -file -signature\n");
}
int check(char * argv[]){
    if(strcmp(argv[4], "-file") != 0){
        usage();
        return 1;
    }
    if (argv[5] == NULL || argv[5][0] == '\0') {
        fprintf(stderr, "No file provided\n");
        return 1;
    }
    return 0;
}
int main(int argc, char* argv[]) {
    SM2_KEY mykey;
    if(argc == 1){
        usage();
        return 0;
    }
    if(strcmp(argv[1], "-key") == 0){
        generate_sm2_key();
    }else if(argc <= 5){
        usage();
    }else if(strcmp(argv[1], "-e") == 0 && strcmp(argv[2], "-kfile") == 0){
        if(load_pub_key((const unsigned char*)argv[3], &mykey)){
            fprintf(stderr, "密钥数据加载失败\n");
            return 1;
        }
        if(check(argv)){
            usage();
            return 1;
        }
        gmssl_sm2_encrypt((const unsigned char*)argv[5], &mykey);
    }else if(strcmp(argv[1], "-d") == 0 && strcmp(argv[2], "-kfile") == 0){
        if(load_pri_key((const unsigned char*)argv[3], &mykey)){
            fprintf(stderr, "密钥数据加载失败\n");
            return 1;
        }
        if(check(argv)){
            usage();
            return 1;
        }
        gmssl_sm2_decrypt((const unsigned char*)argv[5], &mykey);
    }else if(strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-pri") == 0 && strcmp(argv[4], "-pub") == 0){
        if(load_pri_key((const unsigned char*)argv[3], &mykey) || load_pub_key((const unsigned char*)argv[5], &mykey)){
            fprintf(stderr, "密钥数据加载失败\n");
            return 1;
        }
        if(strcmp(argv[6], "-file") != 0 || argv[7] == NULL){
            usage();
            return 1;
        }
        gmssl_sm2_sign((const unsigned char*)argv[7], &mykey);
    }
    else if(strcmp(argv[1], "-v") == 0 && strcmp(argv[2], "-kfile") == 0){
        //load_pri_key("private_key.bin", &mykey);
        if(load_pub_key((const unsigned char*)argv[3], &mykey)){
            fprintf(stderr, "密钥数据加载失败\n");
            return 1;
        }
        if(check(argv) || strcmp(argv[6], "-signature") != 0 || argv[7] == NULL){
            usage();
            return 1;
        }
        gmssl_sm2_verify((const unsigned char*)argv[5], (const unsigned char*) argv[7],&mykey);
    }else {
        usage();
    }
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm2$ '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_encrypt' '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_pk.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm2/just.txt' '/home/liuzujing/experiment-1-from-4-to-6/sm2/enc.bin' 
Encryption successful, output written to /home/liuzujing/experiment-1-from-4-to-6/sm2/enc.bin
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm2$ '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_decrypt' '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_pk.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm2/enc.bin' '/home/liuzujing/experiment-1-from-4-to-6/sm2/message.txt' 
Decryption successful, output written to /home/liuzujing/experiment-1-from-4-to-6/sm2/message.txt
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm2$ '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_sign' '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_sk.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm2/just.txt' '/home/liuzujing/experiment-1-from-4-to-6/sm2/a.sig' 
Signature generated and saved to /home/liuzujing/experiment-1-from-4-to-6/sm2/a.sig
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm2$ '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_verify' '/home/liuzujing/experiment-1-from-4-to-6/sm2/gm_sm2_pk.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm2/just.txt' '/home/liuzujing/experiment-1-from-4-to-6/sm2/a.sig' 
Verify success
SM3
hash
#include <stdio.h>
#include <string.h>
#include <gmssl/sm3.h>
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
int main(int argc, char* argv[]) {
    if(argc < 3){
        printf("usage: -in filename\n");
        return 0;
    }
    if(strcmp(argv[1], "-in") != 0){
        printf("usage: -in filename\n");
        return 0;
    }
    unsigned char hash[SM3_DIGEST_SIZE];
    SM3_CTX ctx;
    sm3_init(&ctx);
    sm3_update(&ctx, (const unsigned char*)argv[2], strlen(argv[2]));
    sm3_finish(&ctx, hash);
    printf("SM3 hash: ");
    for (int i = 0; i < SM3_DIGEST_SIZE; i++) {
        printf("%02x", hash[i]);
    }
    printf("\n");
    const char *output_file = "hash.bin";
    FILE* fp_out = fopen(output_file, "wb");
    if (!fp_out) {
        printf("SM3 Save Error\n");
        perror("Error opening output file\n");
        fclose(fp_out);
        return 1;
    }
    fwrite(hash, 1, SM3_DIGEST_SIZE, fp_out);
    printf("SM3 Save ok\n");
    fclose(fp_out);
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ xxd '/home/liuzujing/experiment-1-from-4-to-6/sm3/hash.bin' 
00000000: 79ae c2e8 7f3d 3110 bd84 5866 ce1d 1633  y....=1...Xf...3
00000010: c5af b953 932f 4694 54fd 5a2e 6ca6 03ee  ...S./F.T.Z.l...
hmac
// sm3.c
#include <string.h>
#include <stdint.h>
#include <stdio.h>
typedef struct {
    uint32_t total[2];     // 消息总长度(以位为单位)
    uint32_t state[8];     // 哈希状态
    unsigned char buffer[64]; // 数据缓冲区

    // 用于 HMAC
    unsigned char ipad[64];
    unsigned char opad[64];
} sm3_context;

/*
 * 32-bit integer manipulation macros (big endian)
 */
#ifndef GET_UINT32_BE
#define GET_UINT32_BE(n,b,i)                             \
    {                                                       \
        (n) = ((uint32_t)(b)[(i)    ] << 24)             \
            | ((uint32_t)(b)[(i) + 1] << 16)             \
            | ((uint32_t)(b)[(i) + 2] <<  8)             \
            | ((uint32_t)(b)[(i) + 3]       );            \
    }
#endif

#ifndef PUT_UINT32_BE
#define PUT_UINT32_BE(n,b,i)                             \
    {                                                       \
        (b)[(i)    ] = (unsigned char) ( (n) >> 24 );     \
        (b)[(i) + 1] = (unsigned char) ( (n) >> 16 );     \
        (b)[(i) + 2] = (unsigned char) ( (n) >>  8 );     \
        (b)[(i) + 3] = (unsigned char) ( (n)       );     \
    }
#endif

/*
 * SM3 context setup
 */
void sm3_starts(sm3_context *ctx)
{
    ctx->total[0] = 0;
    ctx->total[1] = 0;

    ctx->state[0] = 0x7380166F;
    ctx->state[1] = 0x4914B2B9;
    ctx->state[2] = 0x172442D7;
    ctx->state[3] = 0xDA8A0600;
    ctx->state[4] = 0xA96F30BC;
    ctx->state[5] = 0x163138AA;
    ctx->state[6] = 0xE38DEE4D;
    ctx->state[7] = 0xB0FB0E4E;
}

static void sm3_process(sm3_context *ctx, const unsigned char data[64])
{
    uint32_t SS1, SS2, TT1, TT2, W[68], W1[64];
    uint32_t A, B, C, D, E, F, G, H;
    uint32_t T[64];
    uint32_t Temp1, Temp2, Temp3, Temp4, Temp5;
    int j;
#ifdef _DEBUG
    int i;
#endif

    for (j = 0; j < 16; j++)
        T[j] = 0x79CC4519;
    for (j = 16; j < 64; j++)
        T[j] = 0x7A879D8A;

    for (j = 0; j < 16; j++)
    GET_UINT32_BE(W[j], data, j * 4);

#ifdef _DEBUG
    printf("Message with padding:\n");
    for (i = 0; i < 8; i++)
        printf("%08x ", W[i]);
    printf("\n");
    for (i = 8; i < 16; i++)
        printf("%08x ", W[i]);
    printf("\n");
#endif

#define FF0(x,y,z) ( (x) ^ (y) ^ (z))
#define FF1(x,y,z) (((x) & (y)) | ( (x) & (z)) | ( (y) & (z)))

#define GG0(x,y,z) ( (x) ^ (y) ^ (z))
#define GG1(x,y,z) (((x) & (y)) | ( (~(x)) & (z)) )

#define  SHL(x,n) (((x) & 0xFFFFFFFF) << (n))
#define ROTL(x,n) (SHL((x),n) | ((x) >> (32 - (n))))

#define P0(x) ((x) ^  ROTL((x),9) ^ ROTL((x),17))
#define P1(x) ((x) ^  ROTL((x),15) ^ ROTL((x),23))

    for (j = 16; j < 68; j++)
    {
        Temp1 = W[j - 16] ^ W[j - 9];
        Temp2 = ROTL(W[j - 3], 15);
        Temp3 = Temp1 ^ Temp2;
        Temp4 = P1(Temp3);
        Temp5 = ROTL(W[j - 13], 7) ^ W[j - 6];
        W[j] = Temp4 ^ Temp5;
    }

#ifdef _DEBUG
    printf("Expanding message W0-67:\n");
    for (i = 0; i < 68; i++)
    {
        printf("%08x ", W[i]);
        if (((i + 1) % 8) == 0) printf("\n");
    }
    printf("\n");
#endif

    for (j = 0; j < 64; j++)
    {
        W1[j] = W[j] ^ W[j + 4];
    }

#ifdef _DEBUG
    printf("Expanding message W'0-63:\n");
    for (i = 0; i < 64; i++)
    {
        printf("%08x ", W1[i]);
        if (((i + 1) % 8) == 0) printf("\n");
    }
    printf("\n");
#endif

    A = ctx->state[0];
    B = ctx->state[1];
    C = ctx->state[2];
    D = ctx->state[3];
    E = ctx->state[4];
    F = ctx->state[5];
    G = ctx->state[6];
    H = ctx->state[7];
#ifdef _DEBUG
    printf("j     A       B        C         D         E        F        G       H\n");
    printf("   %08x %08x %08x %08x %08x %08x %08x %08x\n", A, B, C, D, E, F, G, H);
#endif

    for (j = 0; j < 16; j++)
    {
        SS1 = ROTL((ROTL(A, 12) + E + ROTL(T[j], j)), 7);
        SS2 = SS1 ^ ROTL(A, 12);
        TT1 = FF0(A, B, C) + D + SS2 + W1[j];
        TT2 = GG0(E, F, G) + H + SS1 + W[j];
        D = C;
        C = ROTL(B, 9);
        B = A;
        A = TT1;
        H = G;
        G = ROTL(F, 19);
        F = E;
        E = P0(TT2);
#ifdef _DEBUG
        printf("%02d %08x %08x %08x %08x %08x %08x %08x %08x\n", j, A, B, C, D, E, F, G, H);
#endif
    }

    for (j = 16; j < 64; j++)
    {
        SS1 = ROTL((ROTL(A, 12) + E + ROTL(T[j], j)), 7);
        SS2 = SS1 ^ ROTL(A, 12);
        TT1 = FF1(A, B, C) + D + SS2 + W1[j];
        TT2 = GG1(E, F, G) + H + SS1 + W[j];
        D = C;
        C = ROTL(B, 9);
        B = A;
        A = TT1;
        H = G;
        G = ROTL(F, 19);
        F = E;
        E = P0(TT2);
#ifdef _DEBUG
        printf("%02d %08x %08x %08x %08x %08x %08x %08x %08x\n", j, A, B, C, D, E, F, G, H);
#endif
    }

    ctx->state[0] ^= A;
    ctx->state[1] ^= B;
    ctx->state[2] ^= C;
    ctx->state[3] ^= D;
    ctx->state[4] ^= E;
    ctx->state[5] ^= F;
    ctx->state[6] ^= G;
    ctx->state[7] ^= H;
#ifdef _DEBUG
    printf("   %08x %08x %08x %08x %08x %08x %08x %08x\n", ctx->state[0], ctx->state[1], ctx->state[2],
        ctx->state[3], ctx->state[4], ctx->state[5], ctx->state[6], ctx->state[7]);
#endif
}

/*
 * SM3 process buffer
 */
void sm3_update(sm3_context *ctx, const unsigned char *input, int ilen)
{
    int fill;
    uint32_t left;

    if (ilen <= 0)
        return;

    left = ctx->total[0] & 0x3F;
    fill = 64 - left;

    ctx->total[0] += ilen;
    ctx->total[0] &= 0xFFFFFFFF;

    if (ctx->total[0] < (uint32_t)ilen)
        ctx->total[1]++;

    if (left && ilen >= fill)
    {
        memcpy(ctx->buffer + left, input, fill);
        sm3_process(ctx, ctx->buffer);
        input += fill;
        ilen -= fill;
        left = 0;
    }

    while (ilen >= 64)
    {
        sm3_process(ctx, input);
        input += 64;
        ilen -= 64;
    }

    if (ilen > 0)
    {
        memcpy(ctx->buffer + left, input, ilen);
    }
}

static const unsigned char sm3_padding[64] =
        {
                0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        };

/*
 * SM3 final digest
 */
void sm3_finish(sm3_context *ctx, unsigned char output[32])
{
    uint32_t last, padn;
    uint32_t high, low;
    unsigned char msglen[8];

    high = (ctx->total[0] >> 29) | (ctx->total[1] << 3);
    low = (ctx->total[0] << 3);

    PUT_UINT32_BE(high, msglen, 0);
    PUT_UINT32_BE(low, msglen, 4);

    last = ctx->total[0] & 0x3F;
    padn = (last < 56) ? (56 - last) : (120 - last);

    sm3_update(ctx, sm3_padding, padn);
    sm3_update(ctx, msglen, 8);

    PUT_UINT32_BE(ctx->state[0], output, 0);
    PUT_UINT32_BE(ctx->state[1], output, 4);
    PUT_UINT32_BE(ctx->state[2], output, 8);
    PUT_UINT32_BE(ctx->state[3], output, 12);
    PUT_UINT32_BE(ctx->state[4], output, 16);
    PUT_UINT32_BE(ctx->state[5], output, 20);
    PUT_UINT32_BE(ctx->state[6], output, 24);
    PUT_UINT32_BE(ctx->state[7], output, 28);
}

/*
 * output = SM3( input buffer )
 */
void sm3(const unsigned char *input, int ilen, unsigned char output[32])
{
    sm3_context ctx;

    sm3_starts(&ctx);
    sm3_update(&ctx, input, ilen);
    sm3_finish(&ctx, output);

    memset(&ctx, 0, sizeof(sm3_context));
}

/*
 * output = SM3( file contents )
 */
int sm3_file(const char *path, unsigned char output[32])
{
    FILE *f;
    size_t n;
    sm3_context ctx;
    unsigned char buf[1024];

    if ((f = fopen(path, "rb")) == NULL)
        return 1;

    sm3_starts(&ctx);

    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
        sm3_update(&ctx, buf, n);

    sm3_finish(&ctx, output);

    memset(&ctx, 0, sizeof(sm3_context));

    if (ferror(f) != 0)
    {
        fclose(f);
        return 2;
    }

    fclose(f);
    return 0;
}

/*
 * SM3 HMAC context setup
 */
void sm3_hmac_starts(sm3_context *ctx, const unsigned char *key, int keylen)
{
    int i;
    unsigned char sum[32];

    if (keylen > 64)
    {
        sm3(key, keylen, sum);
        keylen = 32;
        //key = sum;
        key = (const unsigned char*)sum;
    }

    memset(ctx->ipad, 0x36, 64);
    memset(ctx->opad, 0x5C, 64);

    for (i = 0; i < keylen; i++)
    {
        ctx->ipad[i] ^= key[i];
        ctx->opad[i] ^= key[i];
    }

    sm3_starts(ctx);
    sm3_update(ctx, ctx->ipad, 64);

    memset(sum, 0, sizeof(sum));
}

/*
 * SM3 HMAC process buffer
 */
void sm3_hmac_update(sm3_context *ctx, const unsigned char *input, int ilen)
{
    sm3_update(ctx, input, ilen);
}

/*
 * SM3 HMAC final digest
 */
void sm3_hmac_finish(sm3_context *ctx, unsigned char output[32])
{
    int hlen = 32;
    unsigned char tmpbuf[32];

    sm3_finish(ctx, tmpbuf);
    sm3_starts(ctx);
    sm3_update(ctx, ctx->opad, 64);
    sm3_update(ctx, tmpbuf, hlen);
    sm3_finish(ctx, output);

    memset(tmpbuf, 0, sizeof(tmpbuf));
}

/*
 * output = HMAC-SM3( hmac key, input buffer )
 */
void sm3_hmac(const unsigned char *key, int keylen,
              const unsigned char *input, int ilen,
              unsigned char output[32])
{
    sm3_context ctx;

    sm3_hmac_starts(&ctx, key, keylen);
    sm3_hmac_update(&ctx, input, ilen);
    sm3_hmac_finish(&ctx, output);

    memset(&ctx, 0, sizeof(sm3_context));
}
// 打印缓冲区为十六进制格式
void dumpbuf(const unsigned char *buf, int len) {
    for (int i = 0; i < len; i++) {
        printf("%02x", buf[i]);
        if (((i + 1) % 4) == 0) printf(" ");
    }
    printf("\n");
}
int read_file(const unsigned char *input_file, unsigned char ** text, size_t* len){
    FILE* fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    // 获取文件大小并分配内存
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}
int main(int argc, char *argv[])
{
    if(argc < 5){
        printf("usage: -kfile -file\n");
        return 0;
    }
    if(strcmp(argv[1], "-kfile") == 0 && strcmp(argv[3], "-file") == 0){
        const unsigned char *input = NULL;
        const unsigned char *key = NULL;
        size_t  keylen = 0, ilen = 0;
        if(read_file((const unsigned char*)argv[2], &key, &keylen) || read_file((const unsigned char *)argv[4], &input, &ilen)){
            fprintf(stderr, "Unable to read file.\n");
            return 0;
        }
        unsigned char output[32];
        printf("Message: %s\n", input);
        sm3_hmac(key, keylen, input, ilen, output);
        printf("HMAC:   ");
        dumpbuf(output, 32);

        const char *output_file = "SM3-HMAC.bin";
        FILE* fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("SM3 Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(output, 1, 32, fp_out);
        printf("SM3-HMAC Save ok\n");
        fclose(fp_out);
    }
    return 0;
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm3$ '/home/liuzujing/experiment-1-from-4-to-6/sm3/sm3_hmac' '0123456789abcdef0123456789abcdef'
'/home/liuzujing/experiment-1-from-4-to-6/sm3/just.txt' '/home/liuzujing/experiment-1-from-4-to-6/sm3/hash.bin' 
6e034d26659e5a75539253a0881dc9887e1a53dd85d5d667fd1a76f23108fbb2
SM4
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "gmssl/sm4.h"
#include "gmssl/rand.h"

void usage() {
    printf("usage:    -key\n");
    printf("          -iv\n");
    printf("          -e/-d -key -iv -file\n");
    return;
}

int read_file(const unsigned char *input_file, unsigned char **text, size_t *len) {
    FILE *fp_in = fopen(input_file, "rb");
    if (!fp_in) {
        perror("Error opening input file");
        return 1;
    }
    // 获取文件大小并分配内存
    fseek(fp_in, 0, SEEK_END);
    *len = ftell(fp_in);
    fseek(fp_in, 0, SEEK_SET);
    *text = malloc(*len);
    if (!*text) {
        perror("Memory allocation failed");
        return 0;
    }
    fread(*text, 1, *len, fp_in);
    fclose(fp_in);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc == 1) {
        usage();
        return;
    }
    if (strcmp(argv[1], "-key") == 0) {
        uint8_t key[SM4_KEY_SIZE];
        if (rand_bytes(key, SM4_KEY_SIZE) != 1) {
            fprintf(stderr, "随机密钥生成失败。\n");
            return EXIT_FAILURE;
        }
        printf("随机生成的密钥: ");
        for (size_t i = 0; i < SM4_KEY_SIZE; i++) {
            printf("%02X ", key[i]);
        }
        printf("\n");
        const char *output_file = "key.bin";
        FILE *fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(key, 1, SM4_KEY_SIZE, fp_out);
        printf("key Save ok\n");
        fclose(fp_out);
    } else if (strcmp(argv[1], "-iv") == 0) {
        uint8_t iv[SM4_BLOCK_SIZE];
        if (rand_bytes(iv, SM4_BLOCK_SIZE) != 1) {
            fprintf(stderr, "随机IV生成失败。\n");
            return EXIT_FAILURE;
        }
        // 打印生成的IV
        printf("随机生成的IV: ");
        for (size_t i = 0; i < SM4_BLOCK_SIZE; i++) {
            printf("%02X ", iv[i]);
        }
        printf("\n");
        const char *output_file = "iv.bin";
        FILE *fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(iv, 1, SM4_BLOCK_SIZE, fp_out);
        printf("iv Save ok\n");
        fclose(fp_out);
    } else if (strcmp(argv[1], "-e") == 0) {
        if (argc < 8) {
            usage();
            return 0;
        }
        if (strcmp(argv[2], "-key") != 0 || strcmp(argv[4], "-iv") != 0 || strcmp(argv[6], "-file") != 0) {
            usage();
            return 0;
        }
        unsigned char *plaintext = NULL, *key = NULL, *iv = NULL;
        size_t plaintext_len = 0, key_len = 0, iv_len = 0;
        if (read_file((const unsigned char *) argv[3], &key, &key_len) ||
            read_file((const unsigned char *) argv[5], &iv, &iv_len) ||
            read_file((const unsigned char *) argv[7], &plaintext, &plaintext_len)) {
            fprintf(stderr, "Unable to read file.\n");
            return 0;
        }
        if(key_len != SM4_KEY_SIZE || iv_len != SM4_BLOCK_SIZE){
            printf("key or iv error\n");
            return 0;
        }
        SM4_KEY enc_key;
        sm4_set_encrypt_key(&enc_key, key);
        size_t max_ciphertext_len = plaintext_len + SM4_BLOCK_SIZE;
        uint8_t *ciphertext = malloc(max_ciphertext_len);
        if (ciphertext == NULL) {
            fprintf(stderr, "内存分配失败。\n");
            return EXIT_FAILURE;
        }
        size_t outlen;
        int ret;
        // 加密带填充的明文
        ret = sm4_cbc_padding_encrypt(&enc_key, (uint8_t *) iv, (uint8_t *)plaintext, plaintext_len, ciphertext, &outlen);
        if (ret != 1) {
            fprintf(stderr, "加密失败。\n");
            free(ciphertext);
            return EXIT_FAILURE;
        }
        // 输出密文(十六进制格式)
        printf("密文 (%zu 字节): ", outlen);
        for (size_t i = 0; i < outlen; i++) {
            printf("%02X ", ciphertext[i]);
        }
        printf("\n");
        const char *output_file = "ciphertext.txt";
        FILE *fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(ciphertext, 1, outlen, fp_out);
        printf("Save ok\n");
        fclose(fp_out);
        free(ciphertext);
    } else if (strcmp(argv[1], "-d") == 0) {
        unsigned char *ciphertext = NULL, *key = NULL, *iv = NULL;
        size_t ciphertext_len = 0, key_len = 0, iv_len = 0;
        if (argc < 8) {
            usage();
            return 0;
        }
        if (strcmp(argv[2], "-key") != 0 || strcmp(argv[4], "-iv") != 0 || strcmp(argv[6], "-file") != 0) {
            usage();
            return 0;
        }
        if (read_file((const unsigned char *) argv[3], &key, &key_len) ||
            read_file((const unsigned char *) argv[5], &iv, &iv_len) ||
            read_file((const unsigned char *) argv[7], &ciphertext, &ciphertext_len)) {
            fprintf(stderr, "Unable to read file.\n");
            return 0;
        }
        if(key_len != SM4_KEY_SIZE || iv_len != SM4_BLOCK_SIZE){
            printf("key or iv error\n");
            return 0;
        }
        SM4_KEY dec_key;
        sm4_set_decrypt_key(&dec_key, key);
        size_t max_ciphertext_len = ciphertext_len;
        uint8_t *decryptedtext = malloc(max_ciphertext_len + 1); // +1用于终止符
        if (decryptedtext == NULL) {
            fprintf(stderr, "内存分配失败。\n");
            free(ciphertext);
            free(key);
            free(iv);
            return EXIT_FAILURE;
        }
        size_t outlen;
        // 解密带填充的密文
        int ret = sm4_cbc_padding_decrypt(&dec_key, (uint8_t *) iv, (uint8_t *) ciphertext, max_ciphertext_len,
                                          decryptedtext, &outlen);
        if (ret != 1) {
            fprintf(stderr, "解密失败。\n");
            free(ciphertext);
            free(decryptedtext);
            return EXIT_FAILURE;
        }
        // 确保解密后的文本是以空字符结尾的字符串
        decryptedtext[outlen] = '\0';
        // 打印解密后的明文(以十六进制格式)
        printf("解密后的明文 (%zu 字节): ", outlen);
        for (size_t i = 0; i < outlen; i++) {
            printf("%02X ", decryptedtext[i]);
        }
        printf("\n");
        const char *output_file = "decryptedtext.txt";
        FILE *fp_out = fopen(output_file, "wb");
        if (!fp_out) {
            printf("Save Error\n");
            perror("Error opening output file\n");
            fclose(fp_out);
            return 1;
        }
        fwrite(decryptedtext, 1, outlen, fp_out);
        printf("Save ok\n");
        fclose(fp_out);
        free(decryptedtext);
    } else {
        usage();
        return EXIT_SUCCESS;
    }
}
运行结果
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm4$ '/home/liuzujing/experiment-1-from-4-to-6/sm4/gm_sm4_en_decrypt' encrypt '/home/liuzujing/experiment-1-from-4-to-6/sm4/sm4_key.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm4/just.txt' '/home/liuzujing/experiment-1-from-4-to-6/sm4/sm4_kc.bin' 
Encryption completed successfully.
liuzujing@liuzujing-VirtualBox:~/experiment-1-from-4-to-6/sm4$ '/home/liuzujing/experiment-1-from-4-to-6/sm4/gm_sm4_en_decrypt' decrypt '/home/liuzujing/experiment-1-from-4-to-6/sm4/sm4_key.pem' '/home/liuzujing/experiment-1-from-4-to-6/sm4/sm4_kc.bin' '/home/liuzujing/experiment-1-from-4-to-6/sm4/message.txt' 
Decryption completed successfully.

两⼈⼀组,在 Ubuntu或openEuler中(推荐 openEuler)中使⽤GmSSL编程实现带签名的数字信封协议。使⽤GmSSL库时,Bob发送,Alice接收。Ailice,Bob在实验中要替换为⾃⼰的8位学号+姓名。 使⽤Markdown记录详细记录实践过程,每完成⼀项git commit ⼀次。(5分)

接收到Bob的数字信封:

liuzujing@liuzujing-VirtualBox:~$ ls 1327sm2.der  1324_c.sig  lzjsm4.cbc   lzjsm4.cbc3  gmssl_1324_pub.pem  key.bin  sm2.pem     sm2.sig 1324_c.cbc3  lzj.sm3     lzjsm4.cbc2  lzj.txt iv.bin  sm2.der  sm2pub.pem 三个文件:1327sm2.der  1324_c.sig 1324_c.cbc3

Alice:

liuzujing@liuzujing-VirtualBox:~$ gmssl sm2verify -pubkey gmssl_1324_pub.pem -sig 1324_c.sig -in 1324_c.cbc3  verify : success liuzujing@liuzujing-VirtualBox:~$ gmssl sm2decrypt -key sm2.pem -pass 1327 -in 1327sm2.der  -out gm_k.bin liuzujing@liuzujing-VirtualBox:~$ gmssl sm4_cbc -decrypt -key $(xxd -p -c 32 gm_k.bin) -iv $(xxd -p -c 32 iv.bin) -in 1324_c.cbc3 -out plain.txt liuzujing@liuzujing-VirtualBox:~$ cat plain.txt  20221324ty
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值