You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

程序关闭时析构函数中OpenSSL EVP_EncryptInit调用失败排查

问题

近期尝试改进崩溃处理器,记录对象销毁时的日志。为每个全局对象使用单例模式,并有函数按正确顺序初始化它们(崩溃处理器为最后一个)。但发现AES加密存在异常:程序正常运行时一切正常,仅在关闭程序、调用所有析构函数时出现问题。

在Application对象的析构函数中调用如下shutdown函数:

void shutdown() {
    dbg_println("Application shutdown");
    secure_string key = "12345678901234567890123456789012";
    secure_string iv = "1234567890123456";
    secure_string out;
    int res = secure_crypto::aes_encrypt(key, iv, "shutdown", out);
    if (res != 0) {
        dbg_println("Application aes_encrypt failed {}", res);
    }
}

Application对象是最后创建的,因此其析构函数最先被调用。

使用的AES加密代码如下:

using EVP_CIPHER_CTX_free_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>;

int secure_crypto::aes_encrypt(const secure_string &key, const secure_string &iv, const secure_string &plainText,
                               secure_string &encryptedText) {
    dbg_println("Encrypting {} with key {} and messageIv {}",
                plainText.size() > 500 ? "..." : secure_crypto::secure_string_to_hex(plainText),
                secure_crypto::secure_string_to_hex(key), secure_crypto::secure_string_to_hex(iv));

    try {
        /*
        * Initialise the decryption operation. IMPORTANT - ensure you use a key
        * and IV size appropriate for your cipher
        * In this example we are using 256-bit AES (i.e. a 256-bit key). The
        * IV size for *most* modes is the same as the block size. For AES this
        * is 128 bits
        */

        if (key.size() != 32 || iv.size() != 16) {
            return -1;
        }

        const auto *keyArr = (const unsigned char *) key.c_str();
        const auto *ivArr = (const unsigned char *) iv.c_str();

        EVP_CIPHER_CTX_free_ptr ctx(EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free);
        secure_vector<unsigned char> keyV(keyArr, keyArr + key.size());
        secure_vector<unsigned char> ivV(ivArr, ivArr + iv.size());

        dbg_println("Encrypting with key {} and iv {} and status: {}", secure_crypto::secure_string_to_hex(keyV),
                    secure_crypto::secure_string_to_hex(ivV), ctx.get() == nullptr ? "null" : "not null");
        int rc = EVP_EncryptInit(ctx.get(), EVP_aes_256_cbc(), keyArr, ivArr);
        if (rc != 1) {
#if LOG_CRYPTO_ERRORS
            printf("err: %s", ERR_error_string(ERR_get_error(), NULL));
            dbg_println("[crypto] Error while aes encrypting: {}", rc);
#endif
            return -2;
        }

        // Recovered text expands upto BLOCK_SIZE
        encryptedText.resize(plainText.size() + BLOCK_SIZE);
        int out_len1 = (int) encryptedText.size();

        rc = EVP_EncryptUpdate(ctx.get(), (unsigned char *) &encryptedText[0], &out_len1,
                               (const unsigned char *) &plainText[0],
                               (int) plainText.size());
        if (rc != 1) {
            return -3;
        }

        int out_len2 = (int) encryptedText.size() - out_len1;
        rc = EVP_EncryptFinal(ctx.get(), (unsigned char *) &encryptedText[0] + out_len1, &out_len2);
        if (rc != 1) {
            return -4;
        }

        // Set cipher text size now that we know it
        encryptedText.resize(out_len1 + out_len2);

        return 0;
    } catch (std::exception &e) {
#if LOG_CRYPTO_ERRORS
        dbg_println("[crypto] Error while aes encrypting: {}", e.what());
#endif
        return -99;
    }
}

程序运行结果如下:

Application shutdown
Encrypting 73687574646f776e with key 3132333435363738393031323334353637383930313233343536373839303132 and messageIv 3132
3334353637383930313233343536
Encrypting with key 3132333435363738393031323334353637383930313233343536373839303132 and iv 3132333435363738393031323334
3536 and status: not null
err: error:00000000:lib(0)::reason(0)
[crypto] Error while aes encrypting: 0
Application aes_encrypt failed -2
分析与解决方案
  • 核心问题:OpenSSL库资源已提前销毁
    程序退出阶段,全局对象的析构顺序与初始化顺序相反。虽然Application最后创建、最先析构,但OpenSSL的全局内部资源(如算法注册表、错误处理模块)可能已经被其他更早销毁的全局对象,或是程序退出时的系统自动清理流程释放了。此时调用EVP_EncryptInit会因依赖的底层资源不存在而失败,ERR_error_string返回空错误信息也印证了这一点——错误处理模块已不可用。

  • 验证方法
    可以在aes_encrypt函数开头添加检查:调用EVP_get_cipherbyname("aes-256-cbc"),如果返回nullptr,说明AES算法模块已被卸载。

  • 可行解决方案

  1. 调整操作时机:不在析构函数中执行加密操作,将日志记录逻辑提前到程序正常退出的主动调用阶段(比如在main函数结束前手动调用shutdown,而非依赖对象析构)。
  2. 手动管理OpenSSL生命周期:如果必须在退出阶段执行加密,需确保OpenSSL资源在加密完成后再销毁。程序启动时调用OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, nullptr)初始化,在所有退出逻辑完成后调用OPENSSL_cleanup()手动清理,避免系统自动提前释放资源。
  3. 调整单例销毁顺序:检查是否有其他全局单例会在Application之前销毁,且负责清理OpenSSL资源。若存在,调整这些单例的销毁顺序,确保OpenSSL资源在Application析构完成后再释放。

内容的提问来源于Stack Exchange,提问作者Kaspek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:27:05