基于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; }
常见问题排查
文件找不到错误:
- 优先用绝对路径测试公钥文件(比如
/home/user/public.pem),避免相对路径的当前目录问题 - 用
ls -l 公钥路径检查文件权限,确保当前用户有读取权限
- 优先用绝对路径测试公钥文件(比如
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输出的具体错误信息,比如公钥损坏、格式不兼容等
- 检查公钥格式:如果是
加密长度限制:
- RSA OAEP的最大明文长度为
密钥长度/8 - 2*哈希长度 - 2,比如2048位密钥+SHA-1(20字节),最大明文长度为256 - 40 -2 = 214字节。如果要加密大内容,建议先用RSA加密对称密钥,再用对称加密处理数据。
- RSA OAEP的最大明文长度为
内容的提问来源于stack exchange,提问作者Lilvinco
相关产品推荐
相关产品推荐

