如何将 PKCS7_sign 结果转换为 char * 或 std::string
Posted
技术标签:
【中文标题】如何将 PKCS7_sign 结果转换为 char * 或 std::string【英文标题】:How to get PKCS7_sign result into a char * or std::string 【发布时间】:2016-10-30 20:11:00 【问题描述】:我尝试编写一个小邮件过滤器来使用 S/MIME 对邮件进行签名。到目前为止,我已经完成了对邮件进行签名的代码。我使用了 openssl 中的 demos/smime 代码示例来完成这项工作。不幸的是,这些示例演示了如何将输入消息写入输出文件,但我需要将结果作为字符串。
这是我的 Smime 方法:
void Smime::sign()
if (!isLoaded())
return;
// Null-mailer or unknown
if (mailFrom.empty())
return;
auto *client = util::mlfipriv(ctx);
bool signedOrEncrypted = false;
std::vector<std::string> contentType;
contentType.push_back("multipart/signed");
contentType.push_back("multipart/encrypted");
contentType.push_back("application/pkcs7-mime");
if (client->sessionData.count("Content-Type") == 1)
std::string value client->sessionData["Content-Type"];
std::size_t found;
for (int i=0; i<contentType.size(); i++)
found = value.find(contentType.at(i));
if (found != std::string::npos)
signedOrEncrypted = true;
break;
if (signedOrEncrypted)
const char logmsg[] = "Message already signed or encrypted";
syslog(LOG_NOTICE, "%s", logmsg);
return;
/*
* TODO:
* Catch more cases, where an email already could have been encrypted
* or signed elsewhere.
*/
mapfile::Map email mailFrom;
auto cert = fs::path(email.getSmimeFilename<mapfile::Smime::CERT>());
auto key = fs::path(email.getSmimeFilename<mapfile::Smime::KEY>());
if (!fs::exists(cert) && !fs::is_regular(cert))
return;
if (!fs::exists(key) && !fs::is_regular(key))
return;
// Signing starts here
BIO *in = nullptr, *out = nullptr, *tbio = nullptr;
X509 *scert = nullptr;
EVP_PKEY *skey = nullptr;
PKCS7 *p7 = nullptr;
int flags = PKCS7_DETACHED | PKCS7_STREAM;
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
// S/MIME certificate
tbio = BIO_new_file(cert.string().c_str(), "r");
if (!tbio)
std::cerr << "Error: BIO_new_file(Cert) failed" << std::endl;
return;
scert = PEM_read_bio_X509(tbio, nullptr, 0, nullptr);
// S/MIME key
tbio = BIO_new_file(key.string().c_str(), "r");
if (!tbio)
std::cerr << "Error: BIO_new_file(Key) failed" << std::endl;
return;
skey = PEM_read_bio_PrivateKey(tbio, nullptr, 0, nullptr);
if (!scert || !skey)
std::cerr << "Error: Neither cert or key was loaded" << std::endl;
return;
// Loading mail content from temp file
in = BIO_new_file(client->getTempFile().c_str(), "r");
if (!in)
std::cerr << "Error: Unable to load content from temp file"
<< std::endl;
return;
// Signing
p7 = PKCS7_sign(scert, skey, nullptr, in, flags);
if (!p7)
std::cerr << "Error: Message could not be signed" << std::endl;
return;
// Cleanup
PKCS7_free(p7);
X509_free(scert);
EVP_PKEY_free(skey);
BIO_free(in);
BIO_free(out);
BIO_free(tbio);
smimeSigned = true;
由于 openssl 的手册页超过 1600 个,我不知道在哪里查找信息。
我很想使用“p7”并将其写入一个简单的 std::string(或 char *,如果需要)。我编写的 milter 应用程序将获取此字符串并执行 change-body(尚未编写,但这是我的想法)。
有人可以指出我的例程/手册页,或者有一个可以帮助我的代码示例吗?
提前致谢
【问题讨论】:
我不相信你可以把它放在char*
中,因为可能有一个嵌入的NULL
,这会截断结果。
示例代码调用 = BIO_new_file("smout.txt", "w"); ... SMIME_write_PKCS7(out, p7, in, flags) 结果是 base64 编码的。我很确定,只有一两个功能可以完成这项工作。但是哪个? :-)
【参考方案1】:
我很想使用“p7”并将其写入一个简单的 std::string(或 char *,如果需要)。我编写的 milter 应用程序将获取此字符串并执行 change-body(尚未编写,但这是我的想法)。
我不相信你可以把它放在char*
中,因为可能有一个嵌入的NULL
,这会截断结果。
使用 std::string
和 (1) i2d_PKCS7_bio
用于 ASN.1/DER 或 (2) PEM_write_bio_PKCS7
用于 PEM。这个想法是您像往常一样使用该库,将输出写入MEM_BIO
,然后使用BUF_MEM
获取生物的内容。 BUF_MEM
包含一个指向数据及其长度的指针。比如……
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_MEM_BUF_ptr = std::unique_ptr<BUF_MEM, decltype(&::BIO_free)>;
BIO_MEM_ptr bio(BIO_new(BIO_s_mem()), ::BIO_free);
int ret = i2d_PKCS7_bio(bio, p7);
ASSERT(ret == 1);
BIO_MEM_BUF_ptr buff;
BIO_get_mem_ptr(bio.get(), &buff.get());
const BUF_MEM& t = *buff.get();
std::string result((t.data ? t.data : ""), (t.data ? t.length : 0));
如果您使用PEM_write_bio_PKCS7
和char*
,则PEM 编码将缺少终止NULL
。一定要考虑它,因为它不是 C 字符串。另请参阅Non-printable character after generating random n-byte Base64 string,其中讨论了如何在不编码的情况下编写 NULL。
由于 openssl 的手册页超过 1600 个,我不知道在哪里查找信息...
查看子命令的源代码。它向您展示了该库如何使用 API 进行操作。例如,当您使用openssl pkcs7
时,它使用pkcs7
应用程序。
$ cd <openssl src dir>
$ cd apps
$ ls *.c
app_rand.c dsaparam.c openssl.c rehash.c speed.c
apps.c ec.c opt.c req.c spkac.c
asn1pars.c ecparam.c passwd.c rsa.c srp.c
ca.c enc.c pkcs12.c rsautl.c ts.c
ciphers.c engine.c pkcs7.c s_cb.c verify.c
cms.c errstr.c pkcs8.c s_client.c version.c
crl.c gendsa.c pkey.c s_server.c vms_decc_init.c
crl2p7.c genpkey.c pkeyparam.c s_socket.c x509.c
dgst.c genrsa.c pkeyutl.c s_time.c
dhparam.c nseq.c prime.c sess_id.c
dsa.c ocsp.c rand.c smime.c
将unique_ptr
与 dtor 函数一起使用可确保自动清理对象,并有助于保持代码清洁。每当 OpenSSL 与 C++ 交叉路径时,我都会尝试使用它(另一个示例请参见 How to generate RSA private key using openssl)。
这是我使用 OpenSSL 的 C++ 项目之一:
using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>;
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>;
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>;
using DH_ptr = std::unique_ptr<DH, decltype(&::DH_free)>;
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
using DSA_ptr = std::unique_ptr<DSA, decltype(&::DSA_free)>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using FILE_ptr = std::unique_ptr<FILE, decltype(&::fclose)>;
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>;
using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
using ASN1_INTEGER_ptr = std::unique_ptr<ASN1_INTEGER, decltype(&::ASN1_INTEGER_free)>;
using ASN1_TIME_ptr = std::unique_ptr<ASN1_TIME, decltype(&::ASN1_TIME_free)>;
using X509_EXTENSION_ptr = std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)>;
using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>;
using X509_NAME_ENTRY_ptr = std::unique_ptr<X509_NAME_ENTRY, decltype(&::X509_NAME_ENTRY_free)>;
using X509_STORE_ptr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
using X509_LOOKUP_ptr = std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
using X509_STORE_CTX_ptr = std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
【讨论】:
非常感谢。这太棒了。尤其是智能指针的使用。通过阅读你的例子,我学到了很多很棒的东西。明天我会听从你的建议以上是关于如何将 PKCS7_sign 结果转换为 char * 或 std::string的主要内容,如果未能解决你的问题,请参考以下文章
将 uniqueidentifier 值转换为 char 时结果空间不足
任何人都有一个代码将char数组转换为HEX?并回到实际结果?