如何在 C 中使用 ECC 证书和 OpenSSL 以编程方式创建加密的 PKCS#7?

How to programmatically create encrypted PKCS#7 using ECC certificate and OpenSSL in C?

执行此操作的 OpenSSL 控制台命令是:

openssl cms -encrypt -aes-256-cbc -in plain-original.txt -outform der -out encrypted.p7 -recip certificate.pem -keyopt ecdh_kdf_md:sha256

假设 certificate.pem 是 EC 类型。 我正在尝试使用 CMS_encrypt 函数(这适用于 RSA)

STACK_OF(X509)* stackOfX509 = sk_X509_new_null();
X509* x509Cert = d2i_X509(NULL, &(cert->buff), cert->buffLen);
sk_X509_push(stackOfX509, x509Cert);
CMS_ContentInfo* pkcs7EnvelopedData = CMS_ContentInfo_new();
BIO* dataBIOToEncrypt = BIO_new_mem_buf(data2Encrypt->buff, data2Encrypt->buffLen);

pkcs7EnvelopedData = CMS_encrypt(stackOfX509, dataBIOToEncrypt, cipher, CMS_BINARY);

这当然不行。我不知道如何通过 keyopt。我正在寻找这个有一段时间了。发现即 EVP_PKEY_CTX_ctrl_str(ctx, "ecdh_kdf_md", "sha256"); 但这需要我有 EVP_PKEY_CTX.

在我开始挖掘 OpenSSL 源代码以确定他们是如何做到这一点之前,也许有人可以给我任何提示?

---更新---

好的,我现在知道我确实过于简化了我的第一次尝试。我希望越来越近:

pkcs7EnvelopedData = CMS_encrypt(NULL, dataBIOToEncrypt, cipher, CMS_BINARY | CMS_PARTIAL);
CMS_RecipientInfo *ri = CMS_add1_recipient_cert(pkcs7EnvelopedData, x509Cert, CMS_BINARY | CMS_PARTIAL | CMS_KEY_PARAM);
EVP_PKEY_CTX *pctx = CMS_RecipientInfo_get0_pkey_ctx(ri);
EVP_PKEY_CTX_ctrl_str(pctx, "ecdh_kdf_md", "sha256");
EVP_CIPHER_CTX *wctx = CMS_RecipientInfo_kari_get0_ctx(ri);
EVP_EncryptInit_ex(wctx, EVP_aes_256_wrap(), NULL, NULL, NULL);
CMS_final(pkcs7EnvelopedData, dataBIOToEncrypt, NULL, CMS_BINARY | CMS_PARTIAL);

如果您分解 openssl 应用程序 cms.c 源文件来生成您的命令,您将得到:

bool CMS_encrypt_example(char const* in_file_name, char const* out_file_name, char const* recip_cert_path )
{
    // openssl cms -encrypt -aes-256-cbc -in plain-original.txt -outform der -out encrypted.p7 -recip certificate.pem -keyopt ecdh_kdf_md:sha256

    auto in = make_handle(BIO_new_file(in_file_name, "rb"), BIO_free);
    if(!in) return false;

    auto file = make_handle(BIO_new_file(recip_cert_path, "r"), BIO_free);
    auto cert = PEM_read_bio_X509(file.get(), nullptr, nullptr, nullptr);
    if(!cert) return false;

    auto flags = CMS_PARTIAL;
    auto const content_info = make_handle(CMS_encrypt(nullptr, in.get(), EVP_aes_256_cbc(), flags), CMS_ContentInfo_free);
    if(!content_info) return false;

    auto* ri = CMS_add1_recipient_cert(content_info.get(), cert, flags | CMS_KEY_PARAM);
    if(ri == nullptr) return false;

    auto* pctx = CMS_RecipientInfo_get0_pkey_ctx(ri);
    if (EVP_PKEY_CTX_ctrl_str(pctx, "ecdh_kdf_md", "sha256") <= 0) return false;

    if(!CMS_final(content_info.get(), in.get(), nullptr, flags)) return false;

    auto const outfile = make_handle(BIO_new_file(out_file_name, "wb"), BIO_free);
    if(!outfile) return false;
    if(i2d_CMS_bio_stream(outfile.get(), content_info.get(), in.get(), flags) == 0) return false;

    return true;
}

你必须原谅我使用 C++,因为你要求使用 C,但上面的示例为我编译并根据你的示例 openssl 命令生成正确的输出。

与您的代码唯一真正的区别是它调用 i2d_CMS_bio_stream 来编写输出,而不是您尝试使用 CMS_final 文档说该参数仅用于分离数据和你不是在这里做的。

您的代码还使用了 CMS_BINARY 选项,这会将 -binary 选项添加到您的 openssl 命令。