OpenSSL 3 上 RSA 的 EVP_PKEY_encrypt_init 出现分段错误

问题描述 投票:0回答:1

我正在玩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!

encryption openssl rsa
1个回答
0
投票

原始代码中应该修复一些问题。

第一个源于我对 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 的支持。

© www.soinside.com 2019 - 2024. All rights reserved.