OpenSSL 3中RSA的EVP_PKEY_encrypt_init调用触发段错误
OpenSSL 3 RSA加密解密程序问题排查
问题概述
我在C++环境下使用OpenSSL 3开发一个简单程序,功能包括读取字节数据、动态生成RSA密钥对、公钥加密及解密。但调用EVP_PKEY_encrypt_init时触发段错误,通过gdb调试发现main函数返回5,始终无法解决问题。我主要参考OpenSSL Man3文档编写代码,但对整体实现逻辑仍有困惑,确定代码存在错误但无法定位,希望得到帮助。
最小可复现代码(MVCE)
#define OPENSSL_NO_DEPRECATED #include <iostream> #include <cstring> #include <cstdio> #include <openssl/evp.h> #include <openssl/rsa.h> #include <openssl/err.h> constexpr int KEY_BITS = 4096; void cleanup(EVP_PKEY_CTX * ctx, EVP_PKEY * pkey, unsigned char * encrypted, unsigned char * decrypted) { ERR_print_errors_fp(stderr); if (ctx) { EVP_PKEY_CTX_free(ctx); } if (pkey) { EVP_PKEY_free(pkey); } if (encrypted) { OPENSSL_free(encrypted); } if (decrypted) { OPENSSL_free(decrypted); } } int main(int argc, const char ** argv) { EVP_PKEY_CTX * ctx = NULL; EVP_PKEY * pkey = NULL; if (argc != 2) { return 101; } std::cout << "MESSAGE: " << argv[1] << std::endl; const size_t inlen = strlen(argv[1]); size_t outlen = 0; size_t doutlen = 0; unsigned char * encrypted = NULL; unsigned char * decrypted = NULL; // 创建上下文 ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx) { cleanup(ctx, pkey, encrypted, decrypted); return 1; } // 初始化密钥生成器 if (EVP_PKEY_keygen_init(ctx) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 2; } // 配置密钥生成参数 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KEY_BITS) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 3; } // 生成密钥对 if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 4; } // std::cout << "PRIVATE KEY:" << std::endl; // EVP_PKEY_print_private_fp(stdout, pkey, 0, NULL); // std::cout << "PUBLIC KEY:" << std::endl; // EVP_PKEY_print_public_fp(stdout, pkey, 0, NULL); // 初始化加密上下文 if (EVP_PKEY_encrypt_init(ctx) <= 0) { // <- 触发段错误的行 cleanup(ctx, pkey, encrypted, decrypted); return 5; } // 设置填充模式 if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 6; } // 计算加密缓冲区大小并执行加密 if (EVP_PKEY_encrypt(ctx, NULL, &outlen, (const unsigned char *)argv[1], inlen) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 7; } encrypted = (unsigned char *)OPENSSL_malloc(outlen); if (!encrypted) { cleanup(ctx, pkey, encrypted, decrypted); return 8; } if (EVP_PKEY_encrypt(ctx, encrypted, &outlen, (const unsigned char *)argv[1], inlen) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 9; } std::cout << "ENCRYPTED: " << (char *)encrypted << std::endl; // 计算解密缓冲区大小、分配内存并执行解密 if (EVP_PKEY_decrypt(ctx, NULL, &doutlen, encrypted, outlen) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 17; } decrypted = (unsigned char *)OPENSSL_malloc(doutlen); if (!decrypted) { cleanup(ctx, pkey, encrypted, decrypted); return 18; } if (EVP_PKEY_decrypt(ctx, decrypted, &doutlen, encrypted, outlen) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 19; } std::cout << "DECRYPTED: " << (char *)decrypted << std::endl; cleanup(ctx, pkey, encrypted, decrypted); return 0; }
编译命令
g++ -g -Wextra -Werror test.cc $(pkg-config --cflags openssl) $(pkg-config --libs openssl) -o test; ./test sndlkwndl; echo $?
OpenSSL版本信息
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) built on: Wed Jan 31 18:43:23 2024 UTC platform: debian-amd64 options: bn(64,64) compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -ffile-prefix-map=/build/openssl-l59e1T/openssl-3.0.2=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_TLS_SECURITY_LEVEL=2 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_BUILDING_OPENSSL -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2 OPENSSLDIR: "/usr/lib/ssl" ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-3" MODULESDIR: "/usr/lib/x86_64-linux-gnu/ossl-modules" Seeding source: os-specific CPUINFO: OPENSSL_ia32cap=0x7ffaf3bfffebffff:0x40000000029c6fbf
(通过Ubuntu 22.04LTS仓库的libssl-dev安装)
参考文档
- OpenSSL 3.0官方EVP_PKEY_CTX_new文档
- OpenSSL 1.1.1官方EVP_PKEY_decrypt文档
- OpenSSL 1.1.1官方EVP_PKEY_encrypt文档
补充说明
编写问题时发现代码缺少EVP_PKEY_decrypt_init调用,但当前先专注解决加密阶段的段错误问题。
更新:段错误修复与新问题
根据建议,了解到OpenSSL上下文设计为轻量且生命周期短,不能复用同一上下文完成密钥生成、加密和解密操作,需为每个操作创建新上下文。按照以下代码修改后,段错误问题已解决:
cleanup(ctx, nullptr, nullptr, nullptr); ctx = nullptr; ctx = EVP_PKEY_CTX_new(pkey, nullptr); if (!ctx) { cleanup(ctx, nullptr, nullptr, nullptr); return 1000; }
此外,补充了EVP_PKEY_decrypt_init的调用:
if (EVP_PKEY_decrypt_init(ctx) <= 0) { cleanup(ctx, pkey, encrypted, decrypted); return 5; }
目前代码可正常编译,但解密结果与原消息不符,仍存在问题。另外,尝试复用加密上下文进行解密的方案也可运行,但解密结果仍不正确。
重要提示:推荐学习OpenSSL官方示例代码仓库的内容。
内容的提问来源于stack exchange,提问作者Matteo Ragni
相关产品推荐
相关产品推荐

