尝试在 OpenSSL 中使用 EVP 函数时出现分段错误
Segmentation fault when trying to use EVP functions in OpenSSL
我正在尝试使用 RSA 及其高级信封函数通过 OpenSSL 进行 public 加密。但是,我似乎无法理解它们,并且遇到了分段错误。我项目中的这段压缩代码重现了这个问题:
#include <iostream>
#include <string>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rand.h>
int main()
{
EVP_CIPHER_CTX *rsaCtx;
rsaCtx = new EVP_CIPHER_CTX;
unsigned char *ek;
size_t ekl;
unsigned char *iv;
size_t ivl;
EVP_PKEY *keypair;
keypair = NULL;
EVP_CIPHER_CTX_init(rsaCtx);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
EVP_PKEY_keygen(ctx, &keypair);
EVP_PKEY_CTX_free(ctx);
ek = new unsigned char[EVP_PKEY_size(keypair)];
iv = new unsigned char[EVP_MAX_IV_LENGTH];
ivl = EVP_MAX_IV_LENGTH;
std::string cipherText;
std::string plainText = "A STRING";
size_t encMsgLen = 0;
size_t blockLen = 0;
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, (int*)ekl, iv, &keypair, 1);
EVP_SealUpdate(rsaCtx, (unsigned char*)cipherText.c_str() + encMsgLen, (int*)&blockLen, (const unsigned char*)plainText.c_str(), (int)plainText.size() + 1);
encMsgLen += blockLen;
EVP_SealFinal(rsaCtx, (unsigned char*)cipherText.c_str() + encMsgLen, (int*)&blockLen);
encMsgLen += blockLen;
EVP_CIPHER_CTX_cleanup(rsaCtx);
EVP_PKEY_free(keypair);
delete[] ek;
delete[] iv;
delete rsaCtx;
std::cout << cipherText;
return 0;
}
我在 EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, (int*)ekl, iv, &keypair, 1);
行遇到分段错误
我做错了什么?
ekl
是 size_t
,而您正在将其转换为 (int*)
。
The actual size of each encrypted secret key is written to the array
ekl.
您只是传递一个键,因此传递单个整数的地址就足够了,但您应该传递该整数的地址,例如:
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, reinterpret_cast<int*>(&ekl), iv, &keypair, 1);
或者,只需首先将 ekl
声明为 int
,就可以避免转换:
int ekl;
//...
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, &ekl, iv, &keypair, 1);
我很惊讶你的编译器没有警告你使用未初始化的局部变量。
更新:除了分段错误之外,这段代码还有一些问题。
您正在将缓冲区从空 std::string
(cipherText
) 传递到 EVP_SealUpdate
和 EVP_SealFinal
。这通常不会起作用,如果缓冲区中没有足够的空间,可能会崩溃或损坏内存。
您应该为输出声明一个大小合适的缓冲区,也许是 std::vector<unsigned char> cipherText(bufferSize);
,并传递 &cipherText[0]
以获得指向第一个元素的指针。
cipherText
中的数据不是人类可读的字符串,它是二进制数据,std::cout
不适合显示它。
一些更一般的注释:
- 避免在 C++ 中进行 C 风格的强制转换,并在可能的情况下编写代码,这样您根本不需要强制转换(例如,将整数声明为
int
而不是 size_t
,如果是这样的话API 正在期待)。
- 尽可能避免使用
new
和 delete
进行显式内存管理,例如通过使用 std::vector<unsigned char>
作为缓冲区。
我建议再看看这些函数的文档,或者网上的其他一些使用它们的例子。此外,编写一些执行解密步骤的代码,以便您可以测试纯文本是否正确往返。
在 EVP_SealX 函数中使用太小的缓冲区可能会在代码中看似无关的部分产生令人抓狂的后果。这是我的经验。
设置保护措施以确保密码缓冲区与总明文输入加上可能的填充开销一样大将降低风险。
我正在尝试使用 RSA 及其高级信封函数通过 OpenSSL 进行 public 加密。但是,我似乎无法理解它们,并且遇到了分段错误。我项目中的这段压缩代码重现了这个问题:
#include <iostream>
#include <string>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rand.h>
int main()
{
EVP_CIPHER_CTX *rsaCtx;
rsaCtx = new EVP_CIPHER_CTX;
unsigned char *ek;
size_t ekl;
unsigned char *iv;
size_t ivl;
EVP_PKEY *keypair;
keypair = NULL;
EVP_CIPHER_CTX_init(rsaCtx);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
EVP_PKEY_keygen(ctx, &keypair);
EVP_PKEY_CTX_free(ctx);
ek = new unsigned char[EVP_PKEY_size(keypair)];
iv = new unsigned char[EVP_MAX_IV_LENGTH];
ivl = EVP_MAX_IV_LENGTH;
std::string cipherText;
std::string plainText = "A STRING";
size_t encMsgLen = 0;
size_t blockLen = 0;
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, (int*)ekl, iv, &keypair, 1);
EVP_SealUpdate(rsaCtx, (unsigned char*)cipherText.c_str() + encMsgLen, (int*)&blockLen, (const unsigned char*)plainText.c_str(), (int)plainText.size() + 1);
encMsgLen += blockLen;
EVP_SealFinal(rsaCtx, (unsigned char*)cipherText.c_str() + encMsgLen, (int*)&blockLen);
encMsgLen += blockLen;
EVP_CIPHER_CTX_cleanup(rsaCtx);
EVP_PKEY_free(keypair);
delete[] ek;
delete[] iv;
delete rsaCtx;
std::cout << cipherText;
return 0;
}
我在 EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, (int*)ekl, iv, &keypair, 1);
我做错了什么?
ekl
是 size_t
,而您正在将其转换为 (int*)
。
The actual size of each encrypted secret key is written to the array ekl.
您只是传递一个键,因此传递单个整数的地址就足够了,但您应该传递该整数的地址,例如:
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, reinterpret_cast<int*>(&ekl), iv, &keypair, 1);
或者,只需首先将 ekl
声明为 int
,就可以避免转换:
int ekl;
//...
EVP_SealInit(rsaCtx, EVP_aes_256_cbc(), &ek, &ekl, iv, &keypair, 1);
我很惊讶你的编译器没有警告你使用未初始化的局部变量。
更新:除了分段错误之外,这段代码还有一些问题。
您正在将缓冲区从空 std::string
(cipherText
) 传递到 EVP_SealUpdate
和 EVP_SealFinal
。这通常不会起作用,如果缓冲区中没有足够的空间,可能会崩溃或损坏内存。
您应该为输出声明一个大小合适的缓冲区,也许是 std::vector<unsigned char> cipherText(bufferSize);
,并传递 &cipherText[0]
以获得指向第一个元素的指针。
cipherText
中的数据不是人类可读的字符串,它是二进制数据,std::cout
不适合显示它。
一些更一般的注释:
- 避免在 C++ 中进行 C 风格的强制转换,并在可能的情况下编写代码,这样您根本不需要强制转换(例如,将整数声明为
int
而不是size_t
,如果是这样的话API 正在期待)。 - 尽可能避免使用
new
和delete
进行显式内存管理,例如通过使用std::vector<unsigned char>
作为缓冲区。
我建议再看看这些函数的文档,或者网上的其他一些使用它们的例子。此外,编写一些执行解密步骤的代码,以便您可以测试纯文本是否正确往返。
在 EVP_SealX 函数中使用太小的缓冲区可能会在代码中看似无关的部分产生令人抓狂的后果。这是我的经验。
设置保护措施以确保密码缓冲区与总明文输入加上可能的填充开销一样大将降低风险。