openssl_pkcs7_sign

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

openssl_pkcs7_sign签署 S/MIME 消息

描述

openssl_pkcs7_sign(
    字符串 $input_filename,
    字符串 $output_filename,
    OpenSSLCertificate|字符串 $certificate,
    #[\SensitiveParameter] OpenSSLAsymmetricKey|OpenSSLCertificate|数组|字符串 $private_key,
    ?数组 $headers,
    整数 $flags = PKCS7_DETACHED,
    ?字符串 $untrusted_certificates_filename = null
): 布尔值

openssl_pkcs7_sign() 使用 certificateprivate_key 参数指定的证书及其匹配的私钥签署名为 input_filename 的文件的内容。

参数

input_filename

要进行数字签名的输入文件。

output_filename

将写入数字签名的文件。

certificate

用于对 input_filename 进行数字签名的 X.509 证书。有关有效值的列表,请参见 密钥/证书参数

private_key

private_key 是与 certificate 相对应的私钥。有关有效值的列表,请参见 公钥/私钥参数

headers

headers 是一个包含在数据签名后追加到数据之前的标头的数组(有关此参数格式的更多信息,请参见 openssl_pkcs7_encrypt())。

flags

flags 可用于更改输出 - 请参见 PKCS7 常量

untrusted_certificates_filename

untrusted_certificates_filename 指定包含一系列额外证书的文件的名称,这些证书可以包含在签名中,例如,可以帮助接收者验证您使用的证书。

返回值

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

变更日志

版本 描述
8.0.0 certificate 现在接受 OpenSSLCertificate 实例;以前,它接受类型为 OpenSSL X.509 CSR资源
8.0.0 private_key 现在接受 OpenSSLAsymmetricKeyOpenSSLCertificate 实例;以前,它接受类型为 OpenSSL keyOpenSSL X.509 CSR资源

范例

范例 #1 openssl_pkcs7_sign() 范例

<?php
// 您要签署的消息,以便接收者可以确定发送者是您
// 发送的
$data = <<<EOD

您已获得授权支出 $10,000 用于晚餐费用。

首席执行官
EOD;
// 将消息保存到文件
$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);
// 加密它
if (openssl_pkcs7_sign("msg.txt", "signed.txt", "file://mycert.pem",
array(
"file://mycert.pem", "mypassphrase"),
array(
"To" => "[email protected]", // 键值语法
"From: HQ <[email protected]>", // 索引语法
"Subject" => "Eyes only")
)) {
// 消息已签名 - 发送它!
exec(ini_get("sendmail_path") . " < signed.txt");
}
?>

添加备注

用户贡献的备注 12 则备注

匿名
11 年前
关于 $flags 参数的备注:PKCS7_BINARY 有 2 个影响
* 将 LF 转换为 CR+LF,如 https://php.net/manual/en/openssl.pkcs7.flags.php 中所述
* 它创建了一个不透明的 pkcs7 签名 (p7m)

如果您想防止 LF->CR+LF 转换 *并且* 仍然需要一个分离签名 (p7s),请使用 PKCS7_BINARY | PKCS7_DETACHED(两个标志都设置)。

如果已签署的消息已经是 MIME 多部分,使用上面描述的两个标志似乎是正确组装消息的解决方案。没有任何标志,显然只转换了部分 LF 字符。在特定情况下(本地 MTA 是 Postfix,然后消息通过另一台机器上的 sendmail 传输),MIME 边界在 sendmail 中会被混淆。但是,如果本地 MTA 是 sendmail,这种情况似乎不会发生。
jcmichot at usenet-fr dot net
6 年前
由于缺少示例,以下代码可能对某些人有用。

# openssl_pkcs7_sign() 和 openssl_pkcs7_encrypt() 的演示代码,用于对 Paypal EWP 进行签名和加密。
#
# 生成并自签名证书
# % openssl genrsa -out my-private-key.pem 2048
# % openssl req -new -key my-private-key.pem -x509 -days 3650 -out my-public-key.pem
#

function demo_paypal_encrypt( $webform_hash )
{
$MY_PUBLIC_KEY = "file:///usr/local/etc/paypal/my-public-key.pem";
$MY_PRIVATE_KEY = "file:///usr/local/etc/paypal/my-private-key.pem";
$PAYPAL_PUBLIC_KEY = "file:///usr/local/etc/paypal/paypal_cert_pem.txt";

// 为 PayPal 支持分配构建符号
$webform_hash['bn']= 'MyWebRef.PHP_EWP2';

$data = "";
foreach ($webform_hash as $key => $value)
if ($value != "")
$data .= "$key=$value\n";

$file_msg = sprintf( "/tmp/pp-msg-%d.txt", getmypid() );
$file_sign = sprintf( "/tmp/pp-sign-%d.mpem", getmypid() );
$file_bsign = sprintf( "/tmp/pp-sign-%d.der", getmypid() );
$file_enc = sprintf( "/tmp/pp-enc-%d.txt", getmypid() );

if ( file_exists( $file_msg ) ) unlink( $file_msg );
if ( file_exists( $file_sign ) ) unlink( $file_sign );
如果 ( 文件存在 ( $file_bsign ) ) 则删除 ( $file_bsign );
如果 ( 文件存在 ( $file_enc ) ) 则删除 ( $file_enc );

$fp = fopen( $file_msg, "w" );
如果 ( $fp ) {
fwrite($fp, $data );
fclose($fp);
}

// 签署 HTML 表单消息的一部分
openssl_pkcs7_sign(
$file_msg,
$file_sign,
$MY_PUBLIC_KEY,
数组 ( $MY_PRIVATE_KEY, "" ), /// 私钥,密码
数组 (),
PKCS7_BINARY
);

// 将 PEM 转换为 DER
$pem_data = file_get_contents( $file_sign );
$begin = "Content-Transfer-Encoding: base64";
$pem_data = trim( substr($pem_data, strpos($pem_data, $begin)+strlen($begin)) );
$der = base64_decode( $pem_data );

$fp = fopen( $file_bsign, "w" );
如果 ( $fp ) {
fwrite($fp, $der );
fclose($fp);
}

// 你可以通过以下方式验证 DER 签名是否正确
// % openssl smime -verify -CAfile $MY_PUBLIC_KEY -inform DER -in $file_bsign

// 使用 PayPal 公钥加密消息
openssl_pkcs7_encrypt(
$file_bsign,
$file_enc,
$PAYPAL_PUBLIC_KEY,
数组 (),
PKCS7_BINARY,
OPENSSL_CIPHER_3DES );

$data = file_get_contents( $file_enc );
$data = substr($data, strpos($data, $begin)+strlen($begin));
$data = "-----BEGIN PKCS7-----\n". trim( $data ) . "\n-----END PKCS7-----";

// 清理
if ( file_exists( $file_msg ) ) unlink( $file_msg );
if ( file_exists( $file_sign ) ) unlink( $file_sign );
如果 ( 文件存在 ( $file_bsign ) ) 则删除 ( $file_bsign );
如果 ( 文件存在 ( $file_enc ) ) 则删除 ( $file_enc );

返回 ( $data );
}
Maciej_Niemir at ilim dot poznan dot pl
20 年前
此命令在 WIN32 上与 IIS 一起使用时无法正常工作。邮件无法被 IIS SMTP 服务器(以及 Outlook)正确解释。原因是 UNIX 和 WINDOWS 以不同的方式解释“回车到下一行”的 ASCII 码。

下面我提供了一个改进的代码

<?php

$data
= <<<EOD

测试 123

这是一个测试

测试

EOD;

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

// 使用发送者的密钥签署消息
openssl_pkcs7_sign("msg.txt", "signed.eml", "file://c:/max/cert.pem",
array(
"file://c:/max/priv.pem","your_password"),
array(
"To" => "recipient <[email protected]>",
"From" => "sender <[email protected]>",
"Subject" => "Order Notification - Test"),PKCS7_DETACHED,"c:\max\extra_cert.pem");

$file_arry = file("signed.eml");
$file = join ("", $file_arry);
$message = preg_replace("/\r\n|\r|\n/", "\r\n", $file);

$fp = fopen("c:\Inetpub\mailroot\Pickup\signed.eml", "wb");
flock($fp, 2);
fputs($fp, $message);
flock($fp, 3);
fclose($fp);

?>

此外,如果你想使用 Windows 创建的密钥,你应该将它们(从 IE 中)导出为 PKCS#12 文件(*.pfx)。

从以下地址安装 OpenSSLWin32:
http://www.shininglightpro.com/search.php?searchname=Win32+OpenSSL

执行:openssl.exe

输入以下命令:

pkcs12 -in <pfx-file> -nokeys -out <pem-certs-file>

pkcs12 -in <pfx-file> -nocerts -nodes -out <pem-key-file>

接下来,从 IE 根 CA 证书中导出为 Base-64 *.cer,并将文件重命名为 *.pem

就是这样!
ungdi at hotmail dot com
14 年前
在许多关于签名或加密电子邮件本身的讨论中,没有真正讨论过将电子邮件同时签名和加密的痛苦。

根据 RFC 2311,你可以先加密再签名,或者先签名再加密。但是,这取决于你为其编程的客户端。在我的经验中,在 Outlook 2000 中,它更喜欢先加密再签名。而在 Outlook 2003 中,它是先签名再加密。一般来说,你应该先签名再加密,因为从普通信件的角度来看,这似乎更合乎逻辑。你首先签署一封信,然后把它放进信封里。某些客户端会抱怨你以它不喜欢的方式进行操作,所以你可能需要尝试一下。

当你执行第一个功能时,不要在 headers 数组参数中添加任何标头,你应该把它放在要执行的第二个功能中。如果你在第一个功能中添加了标头,那么第二个功能会把它隐藏起来,邮件服务器无法看到它。你不希望这样。这里我将先签名再加密。

<?php
// 设置邮件标头。
$headers = array("To" => "[email protected]",
"From" => "[email protected]",
"Subject" => "A signed and encrypted message.");

// 首先签署消息
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");

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

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

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

当你获取已签名的消息并将其输入到加密部分时,你必须记住,行间距也必须作为消息正文的一部分输入!如果你计划先签名再加密,不要将来自签名的标头输出作为 headers 数组参数输入到加密函数中!签名的输出应该保留作为正在加密的消息正文的一部分。(如果你是先加密再签名,也是一样。)下面是一个用于签署和加密消息的可重用例程,以及调用它以签署和加密消息的示例。

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

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

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

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

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

mail($emailAddr,$subject,$signedAndEncryptedArray[1],
$signedAndEncryptedArray[0]);
?>
yurchenko dot anton at gmail dot com
15 年前
我花费了几个小时试图找到错误的原因
"获取私钥时出错".

有时会出现此错误,有时不会。

我的解决方案是为 openssl_pkcs7_sign 的每个参数使用 realpath()。在我的情况下,代码看起来像

<?php
$Certif_path
= 'certificate/mycertificate.pem';

$clearfile = "certificate/random_name";
$encfile = $clearfile . ".enc";
$clearfile = $clearfile . ".txt";

// ----
// -- 填充 $clearfile 为要签名的邮件 ...
// ----

openssl_pkcs7_sign(realpath($clearfile),
realpath('.').'/'.$encfile, // 因为 $encfile 还不存在,所以不能使用 realpath($encfile);
'file://'.realpath($Certif_path),

array(
'file://'.realpath($Certif_path), PUBLIC_KEY),

array(
"To" => TO_EMAIL,
"From" => FROM_EMAIL,
"Subject" => ""),

PKCS7_DETACHED));

?>
spam at isag dot melbourne
4 年前
如果要使用 mail() 与标题,则需要在将签名版本嵌入正文或标题和边界之前修改它,否则标题和边界将最终出现在消息中。

<?php
openssl_pkcs7_sign
($basedir . 'email.txt', $basedir . 'signed.txt', 'file://' . $basedir . 'cert.pem', array('file://' . $basedir . 'key.pem', $keypass), array('To' => $smime_to, 'From' => $smime_from, 'Subject' => $smime_subject));
if (
preg_match('/To: [^\r\n]+(\r|\n)+(From: [^\r\n]+(\r|\n)+)Subject: [^\r\n]+(\r|\n)+MIME-Version: [^\r\n]+(\r|\n)+(Content-Type: [^\r\n]+)(\r|\n)+/', file_get_contents($basedir . 'signed.txt'), $matches))
{
$result = mail($smime_to, $smime_subject, str_replace($matches[0], '', file_get_contents($basedir . 'signed.txt')), $mailheaders . $matches[2] . $matches[6]);
}
?>

删除了签名标题($matches[0])并使用 From(不在 mail() 中)和 Content-Type(分别为 $matches[2] / $matches[6])更新了标题。
ungdi at hotmail dot com
17 年前
我想对之前笔记进行修改。一些客户希望以特定顺序对消息进行签名和加密(如果两种操作都想要)。新的电子邮件客户端,例如 Thunderbird 和 Outlook 2003,将接受最安全的方法“签名 -> 加密 -> 再次签名”。

为什么?

第一次签名对消息进行身份验证,表明确实是您写的。然后,电子邮件被加密,以便只有收件人才能打开和阅读它。然后,第二次签名通过识别加密的人是加密的人来确保机密性,这是一条意图供解密人解密的消息。这是最安全的方法。这确保了:消息不可抵赖(第一次签名)、机密性(加密)和上下文完整性[您是意图的收件人](第二次签名)。

如果您只签名然后加密,您无法保证(除了信的内容,标题以纯文本形式放在消息之外)该消息是原始发件人发给您的。例如

Bob 签署了一封情书并将其加密给 Amy,只写着“我爱你。-- Bob”。Amy 解密了它,看到了消息(并开了一个玩笑),并使用 John 的公钥将消息转发给 John,重新加密,但没有篡改消息内容,使签名保持有效。这使得 Amy 可以让人看起来像 Bob 给 John 发了一封情书,并且 Bob 爱着 John,因为您无法在加密过程中验证是谁发送了它。这不是您想要的!

这就像有人拿着一份政府文件,自己把它放进一个信封里,在回邮地址上写上政府地址,然后把它寄给你。你知道这封信是政府写的,但你不能确定这封信是政府直接寄给你的,还是被打开并转发的。

虽然加密后签名的做法存在问题,但实际上这就像是在普通邮件的信封上签名。我知道是你寄的,但消息真的是你发来的吗?还是你转发的?

签名 - 加密 - 再次签名方法将使第一次签名显示你知道消息的作者是谁,加密它以防止其他人阅读它,再次签名以表明消息未被转发,并且发件人有意将邮件发送给您。

只需确保邮件的标题在最后一步应用,而不是第二步或第三步。

有关此情况的安全性和完整性风险的更多信息,请阅读此网页:http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html
maarten at xolphin dot nl
19 年前
也可以对包含附件的消息进行签名。一种简单的方法是

<?php
$boundary
= md5(uniqid(time()));
$boddy = "MIME-Version: 1.0\n";
$boddy .= "Content-Type: multipart/mixed; boundary=\"" . $boundary. "\"\n";
$boddy .= "Content-Transfer-Encoding: quoted-printable\n\n";
$boddy .= "This is a multi-part message in MIME format.\n\n";
$boddy .= "--$boundary\n";
$boddy .= "Content-Type: text/plain; charset=\"iso-8859-1\"\n";
$boddy .= "Content-Transfer-Encoding: quoted-printable\n\n";
$boddy .= $EmailText . "\n\n";
// 将附件添加到消息
do {
$boddy .= "--$boundary\n";
$boddy .= "Content-Type: application/pdf; name=\"FileName\"\n";
$boddy .= "Content-Transfer-Encoding: base64\n";
$boddy .= "Content-Disposition: attachment;\n\n";
$boddy .= chunk_split(base64_encode($file)) . "\n\n";
} while ( {
要附加的文件} );
$boddy .= "--$boundary--\n";

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

// 对它进行签名
if (openssl_pkcs7_sign($msg, $signed, 'file://cert.pem',
array(
'file://key.pem', 'test'),
array(
"To" => "[email protected]", // 键控语法
"From: HQ <[email protected]>", // 索引语法
"Subject" => "Eyes only"), PKCS7_DETACHED, 'intermediate_cert.pem' )) {
exec(ini_get('sendmail_path') . ' < ' . $signed);
}
?>

也可以通过使用 PEAR 包 Mail_Mime 与 openssl_pkcs7_sign 相结合来实现。
del at babel dot com dot au
22 年前
上面示例中显示的“mycert.pem”参数不正确。您必须传递包含 PEM 编码证书或密钥的字符串,或者传递文件的位置,格式为 file://path/to/file.pem。请参阅 OpenSSL 函数页面的评论(此页面上方)。
meint dot post at bigfoot dot com
22 年前
如果您想要将 PKCS7 签名/验证与浏览器集成,并且它只支持 Internet Explorer(或 Netscape + ActiveX 插件),那么您可以考虑使用 Capicom。它是一个免费组件,可以在 MSDN 网站上获得。
php at toyingwithfate dot com
20 年前
值得注意的是,在将消息开头添加一个换行符(\n)之前,我很难让 Mozilla 1.4 或 Outlook Express 6 验证 openssl_pkcs7_sign() 生成的签名。我不确定原因,但一旦我做了这个改变,所有问题都消失了。
dmitri at gmx dot net
18 年前
工作示例

<?php

$data
= <<< EOF
Content-Type: text/plain;
charset="us-ascii"
Content-Transfer-Encoding: 7bit

You have my authorization to spend 10,000 on dinner expenses.
The CEO
EOF;

$fp = fopen("msg.txt", "w");
fwrite($fp, $data);
fclose($fp);

$headers = array("From" => "[email protected]");

openssl_pkcs7_sign("msg.txt", "signed.txt", "file://email.pem", array("file://email.pem", "123456"), $headers);

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

$parts = explode("\n\n", $data, 2);

mail("[email protected]", "Signed message.", $parts[1], $parts[0]);

echo
"Email sent";

?>
To Top