You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于OpenSSL 3的C语言RSA公钥加密实现及问题排查

解决RSA OAEP加密的公钥加载与实现问题

核心错误点:EVP_PKEY_new_raw_public_key的误用

EVP_PKEY_new_raw_public_key 是用来加载原始二进制格式的公钥数据(比如RSA的模数n、公钥指数e的裸二进制),根本不是用来加载PEM格式的公钥——这就是你调用它返回NULL,后续EVP_PKEY_encrypt_init因空指针触发段错误的直接原因。

正确的PEM公钥加载方式(兼容OpenSSL 3.x/1.1.x)

不管是哪个版本的OpenSSL,加载PEM格式公钥都应该用PEM_read_bio_PUBKEY(通用公钥加载接口,支持RSA、EC等多种密钥类型)。你最初用PEM_read_bio_RSA_PUBKEY失败,大概率是因为公钥是PKCS#8格式(开头为-----BEGIN PUBLIC KEY-----),而该函数仅支持PKCS#1格式的RSA公钥(开头为-----BEGIN RSA PUBLIC KEY-----)。

从文件加载PEM公钥代码

#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/err.h>

EVP_PKEY* load_pem_public_key(const char* pem_path) {
    FILE* fp = fopen(pem_path, "r");
    if (!fp) {
        perror("打开公钥文件失败");
        return NULL;
    }

    // BIO_new_fp的BIO_NOCLOSE标记避免重复关闭文件
    EVP_PKEY* pkey = PEM_read_bio_PUBKEY(BIO_new_fp(fp, BIO_NOCLOSE), NULL, NULL, NULL);
    fclose(fp);

    if (!pkey) {
        fprintf(stderr, "加载公钥失败:");
        ERR_print_errors_fp(stderr); // 打印具体错误信息
        return NULL;
    }
    return pkey;
}

从内存字符串加载PEM公钥代码

如果公钥是存在内存中的字符串,改用BIO_new_mem_buf:

EVP_PKEY* load_pem_public_key_from_mem(const char* pem_data, size_t pem_len) {
    BIO* bio = BIO_new_mem_buf(pem_data, pem_len);
    if (!bio) {
        ERR_print_errors_fp(stderr);
        return NULL;
    }

    EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
    BIO_free(bio);

    if (!pkey) {
        fprintf(stderr, "加载公钥失败:");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    return pkey;
}

完整RSA OAEP(SHA-1)加密流程实现

包含JSON序列化(示例用轻量库cJSON,可替换为其他序列化方式)、加密、Base64编码的完整步骤,适配OpenSSL 3.x和1.1.x:

编译依赖

编译时需链接OpenSSL和cJSON库:

gcc your_code.c -o rsa_encrypt -lcrypto -lcjson

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <cjson/cJSON.h>

// 加载PEM公钥(文件版)
EVP_PKEY* load_pem_public_key(const char* pem_path) {
    FILE* fp = fopen(pem_path, "r");
    if (!fp) {
        perror("打开公钥文件失败");
        return NULL;
    }

    EVP_PKEY* pkey = PEM_read_bio_PUBKEY(BIO_new_fp(fp, BIO_NOCLOSE), NULL, NULL, NULL);
    fclose(fp);

    if (!pkey) {
        fprintf(stderr, "加载公钥失败:");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    return pkey;
}

// 将字符串序列化为JSON
char* serialize_to_json(const char* input_str) {
    cJSON* root = cJSON_CreateObject();
    if (!root) {
        fprintf(stderr, "创建JSON对象失败\n");
        return NULL;
    }

    if (!cJSON_AddStringToObject(root, "data", input_str)) {
        cJSON_Delete(root);
        fprintf(stderr, "添加字符串到JSON失败\n");
        return NULL;
    }

    char* json_str = cJSON_Print(root);
    cJSON_Delete(root);
    return json_str;
}

// Base64编码(OpenSSL BIO实现)
char* base64_encode(const unsigned char* data, size_t len) {
    BIO* bio = BIO_new(BIO_s_mem());
    BIO* b64 = BIO_new(BIO_f_base64());
    bio = BIO_push(b64, bio);

    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 不生成换行符
    BIO_write(bio, data, len);
    BIO_flush(bio);

    BUF_MEM* buf_mem;
    BIO_get_mem_ptr(bio, &buf_mem);
    char* encoded = malloc(buf_mem->length + 1);
    memcpy(encoded, buf_mem->data, buf_mem->length);
    encoded[buf_mem->length] = '\0';

    BIO_free_all(bio);
    return encoded;
}

// RSA OAEP SHA-1加密
unsigned char* rsa_oaep_encrypt(EVP_PKEY* pkey, const unsigned char* plaintext, size_t plaintext_len, size_t* out_len) {
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        return NULL;
    }

    if (EVP_PKEY_encrypt_init(ctx) <= 0) {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }

    // 设置OAEP填充与SHA-1哈希
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha1()) <= 0) {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }

    // 获取加密后数据长度
    if (EVP_PKEY_encrypt(ctx, NULL, out_len, plaintext, plaintext_len) <= 0) {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }

    unsigned char* ciphertext = malloc(*out_len);
    if (!ciphertext) {
        fprintf(stderr, "分配加密内存失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }

    if (EVP_PKEY_encrypt(ctx, ciphertext, out_len, plaintext, plaintext_len) <= 0) {
        ERR_print_errors_fp(stderr);
        free(ciphertext);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }

    EVP_PKEY_CTX_free(ctx);
    return ciphertext;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "用法:%s <公钥文件路径> <输入字符串>\n", argv[0]);
        return 1;
    }

    // 初始化OpenSSL(兼容3.x和1.1.x)
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
#else
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
#endif

    // 1. 加载公钥
    EVP_PKEY* pkey = load_pem_public_key(argv[1]);
    if (!pkey) {
        return 1;
    }

    // 2. 序列化输入为JSON
    char* json_str = serialize_to_json(argv[2]);
    if (!json_str) {
        EVP_PKEY_free(pkey);
        return 1;
    }

    // 3. RSA OAEP加密
    size_t cipher_len;
    unsigned char* ciphertext = rsa_oaep_encrypt(pkey, (unsigned char*)json_str, strlen(json_str), &cipher_len);
    free(json_str);
    if (!ciphertext) {
        EVP_PKEY_free(pkey);
        return 1;
    }

    // 4. Base64编码
    char* base64_str = base64_encode(ciphertext, cipher_len);
    free(ciphertext);
    if (!base64_str) {
        EVP_PKEY_free(pkey);
        return 1;
    }

    printf("加密结果(Base64):%s\n", base64_str);

    // 释放资源
    free(base64_str);
    EVP_PKEY_free(pkey);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OPENSSL_cleanup();
#else
    EVP_cleanup();
#endif
    ERR_free_strings();

    return 0;
}

常见问题排查

  1. 文件找不到错误:

    • 优先用绝对路径测试公钥文件(比如/home/user/public.pem),避免相对路径的当前目录问题
    • 用ls -l 公钥路径检查文件权限,确保当前用户有读取权限
  2. PEM公钥加载失败:

    • 检查公钥格式:如果是-----BEGIN RSA PUBLIC KEY-----开头的PKCS#1格式,可改用PEM_read_bio_RSA_PUBKEY加载,或用openssl rsa -pubin -in old.pem -out new.pem转换为PKCS#8格式
    • 一定要看ERR_print_errors_fp输出的具体错误信息,比如公钥损坏、格式不兼容等
  3. 加密长度限制:

    • RSA OAEP的最大明文长度为密钥长度/8 - 2*哈希长度 - 2,比如2048位密钥+SHA-1(20字节),最大明文长度为256 - 40 -2 = 214字节。如果要加密大内容,建议先用RSA加密对称密钥,再用对称加密处理数据。

内容的提问来源于stack exchange,提问作者Lilvinco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:14:55