openssl_pkcs7_encrypt

(PHP 4 >= 4.0.6, PHP 5, PHP 7, PHP 8)

openssl_pkcs7_encrypt加密 S/MIME 消息

描述

openssl_pkcs7_encrypt(
    string $input_filename,
    string $output_filename,
    OpenSSLCertificate|array|string $certificate,
    ?array $headers,
    int $flags = 0,
    int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC
): bool

openssl_pkcs7_encrypt() 使用 RC2 40 位密码加密名为 input_filename 的文件的内容,以便只有由 certificate 指定的预期收件人才能读取。

参数

input_filename

output_filename

certificate

单个 X.509 证书或 X.509 证书数组。

headers

headers 是一个包含在数据加密后将附加到数据的标头的数组。

headers 可以是按标头名称键值的关联数组,也可以是索引数组,其中每个元素包含一个标头行。

flags

flags 用于指定影响编码过程的选项 - 请参见 PKCS7 常量

cipher_algo

密码常量 之一。

返回值

成功时返回 true,失败时返回 false

变更日志

版本 描述
8.1.0 默认密码算法 (cipher_algo) 现在是 AES-128-CBC (OPENSSL_CIPHER_AES_128_CBC)。以前,使用的是 PKCS7/CMS (OPENSSL_CIPHER_RC2_40)。
8.0.0 certificate 现在接受 OpenSSLCertificate 实例;以前,接受的是类型为 OpenSSL X.509 CSR资源

示例

示例 #1 openssl_pkcs7_encrypt() 示例

<?php
// 您要加密并发送给您秘密特工的消息
// 在战场上,被称为夜鹰。您拥有他的证书
// 在文件 nighthawk.pem 中
$data = <<<EOD
夜鹰,

绝密,仅供您阅读!

敌人正在逼近!早上 8:30 在咖啡馆与我会面
领取你的假护照!

总部
EOD;

// 加载密钥
$key = file_get_contents("nighthawk.pem");

// 将消息保存到文件
$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);

// 加密它
if (openssl_pkcs7_encrypt("msg.txt", "enc.txt", $key,
array(
"To" => "[email protected]", // 键控语法
"From: HQ <[email protected]>", // 索引语法
"Subject" => "Eyes only"))) {
// 消息已加密 - 发送它!
exec(ini_get("sendmail_path") . " < enc.txt");
}
?>

添加说明

用户贡献说明 15 条说明

ungdi at hotmail dot com
19 年前
在关于对电子邮件本身进行签名或加密的众多讨论中,没有真正讨论过对电子邮件进行签名和加密的痛苦。

*** 您首先做什么?先签名后加密?还是先加密后签名?

根据 RFC 2311,您可以先加密后签名,也可以先签名后加密。但是,这取决于您为其编程的客户端。根据我的经验,在 Outlook 2000 中,它更喜欢先加密后签名。而在 Outlook 2003 中,则是先签名后加密。通常,您想要先签名后加密,因为从普通邮件的角度来看,这似乎更合乎逻辑。您先签署一封信,然后将其放入信封中。某些客户端如果您以它不喜欢的顺序进行操作,则会抱怨,因此您可能需要对此进行尝试。

*** 对签名和加密进行操作的示例。

当您执行第一个函数时,请不要在 headers 数组参数中放入任何标头,您希望将其放入要执行的第二个函数中。如果您在第一个函数中放入了标头,则第二个函数将将其从邮件服务器中隐藏。您不希望这样。在这里,我将先签名后加密。

<?
// 设置邮件标头。
$headers = array("To" => "[email protected]",
"From" => "[email protected]",
"Subject" => "一条已签名和加密的消息。");

// 首先签名消息
openssl_pkcs7_sign("msg.txt","signed.txt",
"signing_cert.pem",array("private_key.pem",
"password"),array());

// 获取公钥证书。
$pubkey = file_get_contents("cert.pem");

// 加密消息,现在放入标头。
openssl_pkcs7_encrypt("signed.txt", "enc.txt",
$pubkey,$headers,0,1);

$data = file_get_contents("enc.txt");

// 将标头和正文分开,以便与邮件函数一起使用
// 不幸的是,但这是必需的,否则我们将有两组标头
// 并且电子邮件客户端无法解码附件
$parts = explode("\n\n", $data, 2);

// 发送邮件(Headers 参数中的标头将覆盖
// 为 To 和 Subject 参数生成的标头)
mail($mail, $subject, $parts[1], $parts[0]);
?>

请注意,如果您使用一个函数从磁盘中获取数据以便在您程序中的另一个函数中使用,请记住,您可能使用了 explode("\n\n",$data,2) 函数,该函数可能删除了标头和消息内容之间的空格。

当您将签名后的消息输入加密部分时,您必须记住,行距也必须作为消息正文的一部分输入!如果您计划先签名再加密,请不要将签名输出的标头作为标头数组参数的一部分输入加密程序!签名的输出应作为要加密的消息正文的一部分保留。(如果您正在执行加密再签名的反向操作,也是如此。)签署和加密功能的示例被整合到一个可重复使用的例程中,然后被调用以签名和加密消息。

*** 签名和加密的示例,通过例程函数执行,以实现代码的可重复使用性。

这是错误的!
<?
// 数组的 [0] 包含消息标头。数组的 [1] 包含消息的签名正文。
$signedOutputArray = signMessage($inputMessage,$headers);

// 数组的 [0] 包含消息的标头和签名。
// 数组的 [1] 包含加密的消息正文,不包含签名标头。
$signedAndEncryptedArray = encryptMessage($signedOutputArray[1],
$signedOutputArray[0]);

mail($emailAddr,$subject,$signedAndEncryptedArray[1],
$signedAndEncryptedArray[0]);
?>

这是正确的!
<?
// 数组的 [0] 包含签名的标头。
// 数组的 [1] 包含消息的签名正文。
$signedOutputArray = signMessage($inputMessage,array());

// 数组的 [0] 包含消息的标头。
// 数组的 [1] 包含已签名消息及其签名标头的加密内容。
$signedAndEncryptedArray =
encryptMessage($signedOutputArray[0] . "\n\n" . $signedOutputArray[1],$headers);

mail($emailAddr,$subject,$signedAndEncryptedArray[1],
$signedAndEncryptedArray[0]);
?>
匿名
12 年前
为了扩展人们已经完成的工作,下面是一个函数,它接收发件人地址、电子邮件/公钥数组、主题和消息,并使用相应的公钥发送加密的消息。

由于我们正在发送加密的消息,因此假设我们发送的内容实际上是重要的。因此,用于发送消息的文件会立即被粉碎。

$recipients = Array("[email protected]"=>file_get_contents("cert.pem"));
$body = 'secret text';
sendSignedMail("[email protected]", $recipients, "Test Message", $body);

// 收件人是一个电子邮件地址=>密钥的数组
function sendSignedMail($from, $recepients, $subject, $body){
foreach($recepients AS $email=>$key){
$tfn_in = tempnam("/tmp", "b");
$tfn_out = tempnam("/tmp", "e");

$handle = fopen($tfn_in, "w");
fwrite($handle, $body);
fclose($handle);

openssl_pkcs7_encrypt($tfn_in, $tfn_out, $key,
array("To" => $email,
"From" => $from,
"Subject" => $subject), 0);
$data = file_get_contents($tfn_out);

// 粉碎文件,因为这是敏感数据。
$handle = popen("/usr/bin/shred -n 3 -u $tfn_in", 'r');
pclose($handle);
$handle = popen("/usr/bin/shred -n 3 -u $tfn_out", 'r');
pclose($handle);


$parts = explode("\n\n", $data, 2);// 修复邮件函数中的标头

mail($email, $subject, $parts[1], $parts[0]);

}

}
1matqo1 at azet dot sk
14 年前
对于所有花费大量时间尝试加密 multipart/alternative 电子邮件但没有成功的人

1.) 将完整的电子邮件(标头和正文)放入文件中进行加密,如 koen 的示例所示
<?php
$body
= file_get_contents("body.txt");
$msg = $enc_header.$body;
file_put_contents("msg.txt", $msg);
?>

2.) 发送到 openssl_pkcs7_encrypt 的标头数组不能包含某些标头,它会发生冲突/重复,一些客户端会出现问题 - 例如,Thunderbird 不会显示您的电子邮件正文。对我来说,这些标头可以正常工作:“Subject”、“To”、“From”、“Reply-To”、“Date”、“Return-Receipt”、“Message-ID”、“CC”、“X-Priority”、“X-Mailer”

还有一件事 - 如果您的加密公钥不起作用,请检查您是否发送了带有密钥的证书,而不仅仅是纯密钥(必须是证书)

祝大家好运,由于文档数量少且有时令人困惑,这有点困难...
Matthias Barkhausen
4 年前
要成功地对明文电子邮件进行签名和加密,以便发送给 Outlook 和 iOS 邮件,并能够被它们读取,您需要先在没有标头的情况下进行签名并使用 PKCS7_TEXT 常量,然后使用标头和 cipherid 3DES 进行加密,最后使用 sendmail/ssmtp 发送,并使用 -t 选项。

使用 3DES 的原因是 iOS(或苹果公司总体上)不再接受 openssl-pkcs7-encrypt 使用的 RC40 默认加密。Outlook 可以解密这种加密,但 iOS 邮件只是说“此邮件没有内容”和“安装包含您身份的配置文件...”

$headers = array("To" => ...,
"From" => ...,
"Subject" => ...);

openssl_pkcs7_sign("msg.txt","signed.txt", $mMyCertFileToIncludeForRecipient,array($mMyPrivKeyFileAsPEM, "passphrase"),array(),PKCS7_TEXT);

openssl_pkcs7_encrypt("signed.txt","enc.txt",$pubkeyContents,$headers,0,OPENSSL_CIPHER_3DES);

shell_exec("ssmtp -t < enc.txt") #sendmail 使用相同的语法
Ryan Schaffner
12 年前
折腾了一个小时左右,我发现这可能对某些人有所帮助。当您导出证书时,请确保它是一个 base-64 证书,因为 DER 编码的证书无法被这些函数读取。Windows 默认使用 DER。它必须是 base-64 证书,这些函数才能正常工作。
koen dot thomeer at pubmed dot be
16 年前
在前面的示例中,解密后的消息无法被“流行的”邮件客户端读取。这些邮件客户端还需要加密部分中的标头。

我还注意到,在前面的示例中,有些标头是重复的(“To:”和“Subject:”没有被 mail() 中的 Headers 参数覆盖)。在 $headers_msg 中取消设置“To:”和“Subject:”也可以解决这个问题。

body.txt 是包含邮件正文的文件。
publickey.cer 是包含公钥证书的文件。

<?php
// 设置邮件标头。
$headers = array("From" => "[email protected]", "To" => "[email protected]", "Subject" => "Encrypted mail readable with most clients", "X-Mailer" => "PHP/".phpversion());

// 获取公钥证书。
$pubkey = file_get_contents("publickey.cer");

// 加密部分的标头
$eol = "\r\n";
$enc_header .= "From: ".$headers['From'].$eol;
$enc_header .= "To: ".$headers['To'].$eol;
$enc_header .= "Subject: ".$headers['Subject'].$eol;
$enc_header .= "Content-Type: text/plain; format=flowed; charset=\"iso-8859-1\"; reply-type=original".$eol;
$enc_header .= "Content-Transfer-Encoding: 7bit".$eol;
$enc_header .= "\n";

// 为加密的消息添加标头
$body = file_get_contents("body.txt");
$msg = $enc_header.$body;
file_put_contents("msg.txt", $msg);

// 删除一些用于 mail() 的重复标头
$headers_msg = $headers;
unset(
$headers_msg['To'], $headers_msg['Subject']);

// 加密消息
openssl_pkcs7_encrypt("msg.txt", "enc.txt",$pubkey,$headers_msg,0,1);

// 分隔用于 mail() 的标头和正文
$data = file_get_contents("enc.txt");
$parts = explode("\n\n", $data, 2);

// 发送邮件
mail($headers['To'], $headers['Subject'], $parts[1], $parts[0]);
?>
ungdi AT hotmail DOT com
20 年前
从 PHP 5.0.0 开始,您可以选择 64 位 RC2 加密或 128 位 RC2 加密。

新的函数描述现在应该是
bool openssl_pkcs7_encrypt ( string infile, string outfile, mixed recipcerts, array headers [, int flags] [,int cipher])

其中 cipher 的整数值为 0 或 1。0 = 64 位,1 = 128 位。
richardaburton at hotmail dot com
20 年前
使用 sendmail 并不太便携,而且看起来很愚蠢,因为 PHP 有 mail 函数可以完成这项工作。问题是如何使用 mail 函数发送这封电子邮件,因为它已经包含了标头?

如果您提取 openssl_pkcs7_encrypt 生成的文件的

该解决方案非常简单,但我想了一会儿才想到,所以在这里分享。加载文件内容后,将标题与正文分开。然后将标题作为 `additional_headers` 参数传递给 `mail` 函数,并将电子邮件正文作为 `mail` 函数的 `message` 参数传递。

您需要指定 `to` 和 `subject` 参数,但这些参数将在最终电子邮件(传递给收件人)中被来自真实加密电子邮件的参数覆盖。

<?php

$pubkey
= file_get_contents("cert.pem");

openssl_pkcs7_encrypt("msg.txt", "enc.txt", $pubkey,
array(
"To" => "[email protected]",
"From" => "HQ <[email protected]>",
"Subject" => "Eyes only"), 0)

$data = file_get_contents("enc.txt");

// 分离标题和正文,以便使用 mail 函数
// 遗憾的是必须这样做,否则我们会得到两组标题
// 并且电子邮件客户端不会解码附件
$parts = explode("\n\n", $data, 2);

// 发送邮件(Headers 参数中的标题将覆盖为 To 和 Subject 参数生成的标题)
mail($mail, $subject, $parts[1], $parts[0]);

?>

Richard。
richardaburton at hotmail dot com
20 年前
如果您不喜欢只使用 RC2/40bit 的想法,可以随时重新编译 php_openssl 扩展。只需在扩展的 openssl.c 源文件中搜索 EVP_rc2_40_cbc() 调用,该调用选择此密码。替换调用以选择另一个更好的密码,例如 EVP_rc2_cbc()(RC2/128bit)或 EVP_des_ede3_cbc()(三重 DES)。

我修补了源代码以允许将密码选择为额外的参数,但我从 CVS 获取了最新的源代码以提交补丁,并且它似乎已经完成了工作,所以看起来我们很快就会看到这个功能。

Richard。
匿名
21 年前
考虑到 RC2/40bit 已经被证明容易受到暴力破解攻击(例如,参见 www.distributed.net),只使用它有点可怕。

以下是一种允许更强加密(128 位)的替代方法。这在 Solaris 8 上有效,但可以通过删除“-rand”参数及其随机文件名来适应 Linux 等系统。

-------------代码片段-------------------

$execstring= "echo \"" . $yourbodytext . "\" | /usr/local/ssl/bin/openssl smime -encrypt -rc2-128 -rand /usr/local/apache/yoursecuredir/randomfile -text -to " . $recipient . " -from [email protected] -subject \"" . $subject . "\" /usr/local/apache/yoursecuredir/usercerts/someuser.pem | /usr/lib/sendmail -t";

exec($execstring,$returndata,$resultcode);

-------------代码片段-------------------

它需要用户证书的 .pem 格式。假设您已经从商业 CA(例如 www.thawte.com)获得了证书,那么从您的浏览器中导出它 WITHOUT --REPEAT-- WITHOUT 其私钥并将其复制到 PHP/Web 服务器上非常简单。导出过程是特定于浏览器的,但假设您使用的是 MS Internet Explorer,则需要选择菜单工具 -> 互联网选项 -> 内容 -> 证书 -> (突出显示您的证书列表)-> 导出向导。

导出的文件可能以 DER 编码的二进制格式
命名为“whatever.CER”,您需要将其转换为隐私增强消息 (PEM) 格式。通常,您现在将文件传输到 *nix 机器,执行此转换的命令(例如)为

/usr/local/ssl/bin/openssl x509 -inform DER -outform PEM -in someuser.cer -out someuser.pem

在修改此代码时,当然,始终要确保传递到系统调用中的值($recipient 等)以干净的方式获取,以避免信任用户提供的数据。
bob at bobscheffler dot com
19 年前
不值得。说实话,这听起来是个好主意,但它太麻烦了。我使用 asp 这样做过,但还没有找到使用 PGP 这样做的方法(或者说,我还没有真正尝试过)。使用 asp 花了我一整天的时间(不得不做一些技巧才能让它正常工作)。祝好--
glyn at tomkins dot net
21 年前
我一直努力让 openssl_pkcs7_encrypt() 按照我的需要工作,但通过我的坚持,我最终成功了。我遇到的问题是由于我对 S/MIME、MIME 和电子邮件标题的理解非常不完整。

首先,手册中的示例和 msisolak 在上面提供的修正也略有错误。让我用通俗的语言解释一下问题是如何产生的...

电子邮件的结构如下

headers
空行
内容

对于 S/MIME,消息被加密(包括标题),并且其他标题围绕加密的消息进行包装,如下所示

S/MIME 标题
空行
加密的 MIME 标题
加密的空行
加密的内容

如果您在要加密的内容中没有标题,那么第一条空行之前的任何内容都被视为标题,并且不会出现在消息内容中。

openssl_pkcs7_encrypt() 创建整个加密部分,因此如果您在 infile 中不包含任何标题,那么您会发现,除非您将文件的首行为空行,否则您的部分消息可能会被视为标题。

openssl_pkcs7_encrypt() 对 "From" S/MIME 标题的编码方式也有错误。

因此,示例要完全正确地工作,需要读取如下内容

// 您要加密并发送给您的秘密特工的消息
// 在现场,称为 nighthawk。您有他的证书
// 在 nighthawk.pem 文件中
//
// 注意,第一行必须为空,因为我在邮件的加密部分中没有提供任何标题。
$data = <<<EOD

Nighthawk,

绝密,仅供您阅览!

敌人正在逼近!上午 8:30 在咖啡馆与我见面
领取您的假护照!

总部
EOD;

// 加载密钥
$key = implode("", file("my.pem"));

// 将消息保存到文件
$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);

// 对其进行加密
if (openssl_pkcs7_encrypt("msg.txt", "enc.txt", $key,
array("To" => "[email protected]", // 带键的语法
"From" => "HQ <[email protected]>", // 带索引的语法
"Subject" => "Eyes only")))
{
// 消息已加密 - 发送它!
exec(ini_get("sendmail_path") . " < enc.txt");
}

现在,我想进行一些其他改进。我的电子邮件包含 html 内容,必须在电子邮件客户端中显示时进行格式化,并且我还想使用临时文件名,以允许多个用户同时使用该脚本。最后,我想删除这些临时文件。

以下是示例最终实现这些附加改进的方式

<?

// 您要加密并发送给您的秘密特工的消息
// 在现场,称为 nighthawk。您有他的证书
// 在 my.pem 文件中
//
// 注意 Content-type 标题后的空行
//
$data = <<<EOD
MIME-Version: 1.0
Content-type: text/html; charset=iso-8859-1

<html>
<b>Nighthawk</b>,
<h1>绝密,仅供您阅览!</h1>
<p>敌人正在逼近!上午 8:30 在咖啡馆与我见面
领取您的假护照!</p>
<p>总部</p>
</html>
EOD;

// 加载密钥
$key = implode("", file("my.pem"));

// 生成一个唯一的临时文件名。使用 .txt 表示明文版本,使用 .enc 表示加密版本。
$clearfile = tempnam("temp","email") . ".txt";
$encfile = $clearfile . ".enc";
$clearfile .= ".txt";

$fp = fopen($clearfile, "w");
fwrite($fp, $data);
fclose($fp);

// 对其进行加密
if (openssl_pkcs7_encrypt($clearfile,$encfile, $key,
array("To" => "[email protected]", // 带键的语法
"From" => "HQ <[email protected]>", // 带索引的语法
"Subject" => "Eyes only")))
{
// 消息已加密 - 发送它!
exec(ini_get("sendmail_path") . " < $encfile");

};
// 现在擦除临时文件
unlink($clearfile);
unlink($encfile);

?>
msisolak at yahoo dot com
22 年前
对于那些试图从 Windows 使用 Outlook 或 Outlook Express 中的密钥来使用此函数的人来说,弄清楚如何以 OpenSSL 所需的格式导出密钥可能很棘手。由于所有(至少所有 Microsoft)产品共享一个通用的密钥存储,因此从 IE 导出密钥比从 Outlook 导出密钥更容易。

在 IE 中,选择工具 -> 互联网选项,然后选择“内容”选项卡,然后单击“证书”按钮。从列表中选择您的证书,然后单击“导出”按钮。要加密电子邮件,您只需要以“Base-64 编码的 X.509 (.CER)”格式导出您的公钥。此过程创建的文件可以直接用作密钥文件,以使用 openssl-pkcs7-encrypt 进行 S/MIME 加密。
匿名
8 年前
示例代码是错误的(至少在 4.2.0 版本中是这样)。recipcerts 参数要么是 base64 编码密钥文件的实际文本,要么必须是“file://...”格式的文件名。普通路径将不起作用(OpenSSL 会尝试将路径用作实际证书)。上面的代码按以下方式工作

<?php
// 您要加密并发送给您的秘密特工的消息
// 在现场,称为 nighthawk。您有他的证书
// 在 nighthawk.pem 文件中
$data = <<<EOD
Nighthawk
,

Top secret, for your eyes only!

The enemy is closing in! Meet me at the cafe at 8.30am
to collect your forged passport
!

HQ
EOD
;

// 加载密钥
$key = implode("", file("my.pem"));

// 将消息保存到文件
$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);

// 对其进行加密
if (openssl_pkcs7_encrypt("msg.txt", "enc.txt", $key,
array(
"To" => "[email protected]", // 带键的语法
"From: HQ <[email protected]>", // 带索引的语法
"Subject" => "Eyes only")))
{
// 消息已加密 - 发送它!
exec(ini_get("sendmail_path") . " < enc.txt");
}
?>

[您也可以传递一个接收者证书数组,但我没有使用过,所以不知道它期望什么。]
msisolak at yahoo dot com
22 年前
示例代码是错误的(至少在 4.2.0 版本中是这样)。recipcerts 参数要么是 base64 编码密钥文件的实际文本,要么必须是“file://...”格式的文件名。普通路径将不起作用(OpenSSL 会尝试将路径用作实际证书)。上面的代码按以下方式工作

<?php
// 您要加密并发送给您的秘密特工的消息
// 在该领域,被称为 nighthawk。您有他的证书
// 在文件 nighthawk.pem 中
$data = <<<EOD
Nighthawk,

绝密,仅供您阅览!

敌人正在逼近!在上午 8:30 在咖啡馆与我会面
领取您的伪造护照!

总部
EOD;

// 加载密钥
$key = implode("", file("my.pem"));

// 将消息保存到文件
$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);

// 加密它
if (openssl_pkcs7_encrypt("msg.txt", "enc.txt", $key,
array(
"To" => "[email protected]", // 密钥语法
"From: HQ <[email protected]>", // 索引语法
"Subject" => "Eyes only")))
{
// 消息已加密 - 发送它!
exec(ini_get("sendmail_path") . " < enc.txt");
}
?>

[您也可以传递一个接收者证书数组,但我没有使用过,所以不知道它期望什么。]
To Top