我正在玩openssl 3(C++),我正在尝试编写一个简单的程序来读取一些字节,动态创建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;
// Create context
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) {
cleanup(ctx, pkey, encrypted, decrypted);
return 1;
}
// Init key generator
if (EVP_PKEY_keygen_init(ctx) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 2;
}
// Configure key generation
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KEY_BITS) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 3;
}
// Create keys
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);
// Init encryption context.
if (EVP_PKEY_encrypt_init(ctx) <= 0) { // <-SEGFAULT LINE
cleanup(ctx, pkey, encrypted, decrypted);
return 5;
}
// Defines padding
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 6;
}
// Evaluating size for encrypted buffer and encrypting
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;
// Evaluating decryptingsize, allocating buffer and decrypting
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
)
主要参考资料:
旁注:在写问题时,我注意到我在某个地方遗漏了
EPV_PKEY_decrypt_init
......但是如果我可以加密,我相信我可以理解其余部分(探索的乐趣)。
我收到的评论很中肯。上下文应该是轻量级的并且寿命应该很短(我错过了这个概念,谢谢@maarten-bodewes)。我无法使用相同的上下文来生成密钥以及加密和解密。最好为每个操作使用新的上下文。在某些情况下上下文可以重用,但我确实需要更深入地了解上下文的生命周期并测试这个概念。
特别是,我遵循@topaco的建议,在代码生成和加密后添加了以下代码,使用
pkey
:
cleanup(ctx, nullptr, nullptr, nullptr);
ctx = nullptr;
ctx = EVP_PKEY_CTX_new(pkey, nullptr);
if (!ctx) {
cleanup(ctx, nullptr, nullptr, nullptr);
return 1000;
}
修改解决了分段错误问题。
此外,正如我已经注意到并由@topaco重新建议的那样,我必须初始化上下文的解密:
if (EVP_PKEY_decrypt_init(ctx) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 5;
}
代码可以编译,但仍然无法运行。解密的字符串与原始消息确实不同,所以即使标题中的问题似乎已经解决,我仍然错过了一些东西。
作为旁注:@Topaco 的建议(在解密中直接重用用于加密的相同上下文)有效(解密的文本仍然不正确,但我认为这是一个单独的问题)。
重要提示:此链接是黄金链接:https://github.com/danbev/learning-opensslOpenSSL 示例的完整存储库!谢谢@topaco!
原始代码中应该修复一些问题。
第一个源于我对 OpenSSL 上下文概念的误解。
EVP_PKEY_(encrypt|decrypt)_init
调用之后必须重置填充,因为这些调用似乎会重置该选项。工作代码需要:
在密钥生成后销毁上下文并创建一个新的用于加密,使用以下代码片段:
cleanup(ctx, nullptr, nullptr, nullptr);
ctx = nullptr;
ctx = EVP_PKEY_CTX_new(pkey, nullptr);
if (!ctx) {
cleanup(ctx, nullptr, nullptr, nullptr);
return 1000;
}
在完成输入消息的加密后,也可以可选地包含该片段。否则,可以使用相同的上下文进行加密和解密(解密后必须销毁)。
解密上下文的初始化必须始终包含填充的配置。似乎
_init
函数使原始选项无效:
if (EVP_PKEY_decrypt_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;
}
通过这两个片段,代码可以按预期工作。特别感谢@Topaco 和@maarten-bodewes 的支持。