payment server 的 cUrl HTTP POST 为何需要 private key?
明确结论
CURLOPT_SSLCERT 用来配置客户端证书。如果支付服务器采用双向 TLS(mTLS)认证,客户端必须持有与该证书匹配的 private key。
这里需要的不是 payment server 的 private key,而是你方客户端证书对应的 private key。证书本身可以公开,但仅发送证书不能证明你是合法持有者。在 TLS 握手过程中,客户端需要使用自己的 private key 完成签名,服务器再用证书中的 public key 验证签名。
如果对方提供的只是服务器证书或 CA certificate,不应将其配置为 CURLOPT_SSLCERT,而应通过 CURLOPT_CAINFO 验证服务器身份。
先确认对方提供的文件是什么
常见情况有以下三种:
-
客户端证书和 private key
用于 mTLS。通常包括:
client-cert.pem:客户端证书client-key.pem:客户端 private key
-
PKCS#12 文件
扩展名通常是
.p12或.pfx,文件中可以同时包含客户端证书和 private key,一般还会设置导出密码。 -
服务器证书或 CA certificate
这类文件只用于验证支付服务器,不能代表客户端身份。此时应配置
CURLOPT_CAINFO,不需要客户端 private key。
可以使用 OpenSSL 检查文件内容:
openssl x509 -in certificate.pem -noout -subject -issuer
检查 PEM 文件中是否包含 private key:
grep "PRIVATE KEY" certificate.pem
如果文件是 .p12 或 .pfx:
openssl pkcs12 -info -in client.p12 -noout
这些命令只能检查文件结构。证书用于服务器验证还是客户端认证,仍要以支付服务商的接入文档为准。
使用独立的客户端证书和 private key
假设支付服务商要求使用 mTLS,并且你已有以下文件:
client-cert.pemclient-key.pem
PHP cURL 可以这样配置:
<?php
$x = curl_init();
$options = [
CURLOPT_URL => $serverURL,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $string,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
// 客户端身份认证
CURLOPT_SSLCERT => __DIR__ . '/client-cert.pem',
CURLOPT_SSLCERTTYPE => 'PEM',
CURLOPT_SSLKEY => __DIR__ . '/client-key.pem',
CURLOPT_SSLKEYTYPE => 'PEM',
// 验证支付服务器
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FAILONERROR => true,
];
curl_setopt_array($x, $options);
$data = curl_exec($x);
if ($data === false) {
throw new RuntimeException(
curl_error($x) . ' (' . curl_errno($x) . ')'
);
}
$statusCode = curl_getinfo($x, CURLINFO_RESPONSE_CODE);
curl_close($x);
echo "HTTP status: {$statusCode}\n";
echo $data;
如果 private key 已加密,还要配置密码:
CURLOPT_KEYPASSWD => $privateKeyPassword,
部分 PHP/cURL 环境也支持名称相近的 CURLOPT_SSLKEYPASSWD。具体使用哪个常量,取决于当前 PHP 构建提供的支持。
使用包含证书和 private key 的 PKCS#12 文件
如果收到的是 client.p12 或 client.pfx,可以直接使用:
<?php
$x = curl_init();
curl_setopt_array($x, [
CURLOPT_URL => $serverURL,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $string,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSLCERT => __DIR__ . '/client.p12',
CURLOPT_SSLCERTTYPE => 'P12',
CURLOPT_KEYPASSWD => $certificatePassword,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$data = curl_exec($x);
if ($data === false) {
throw new RuntimeException(
curl_error($x) . ' (' . curl_errno($x) . ')'
);
}
curl_close($x);
echo $data;
PHP 能否直接读取 PKCS#12 文件,取决于它使用的 libcurl 和 TLS 后端。如果当前环境不支持,可以先将文件转换为 PEM,再通过 CURLOPT_SSLCERT 和 CURLOPT_SSLKEY 分别配置证书与 private key。
如果提供的是 CA certificate
如果支付服务商提供的文件用于验证其服务器证书,应改为:
CURLOPT_CAINFO => __DIR__ . '/payment-ca.pem',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
完整配置中的关键部分如下:
curl_setopt_array($x, [
CURLOPT_URL => $serverURL,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $string,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CAINFO => __DIR__ . '/payment-ca.pem',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
这种情况下不要设置 CURLOPT_SSLCERT,除非支付服务器明确表示还需要客户端证书认证。
为什么浏览器也需要 private key
访问普通 HTTPS 网站时,通常只有服务器发送证书,浏览器不会发送客户端证书。
只有网站要求使用 mTLS 时,浏览器才会选择并发送客户端证书。对应的 private key 也保存在浏览器可访问的证书存储区、操作系统或硬件安全设备中。这个过程通常自动完成,所以用户看不到显式配置 private key 的步骤。
TLS 不会将 private key 发送给服务器。客户端只用它完成密码学签名,证明自己持有该密钥。服务器保存的是用于验证签名的 public key 或受信任的 CA 信息,不应持有客户端的 private key。
服务器知道客户端 IP,也不能代替这种身份凭证。IP 白名单和客户端证书属于两套不同的安全控制。
当前代码中的主要问题
当前代码将:
CURLOPT_SSLCERT => getcwd() . "/certificate.pem",
配置为客户端证书。如果该 PEM 文件不包含 private key,同时也没有通过 CURLOPT_SSLKEY 指定 private key,cURL 就可能报告无法设置或加载 private key。
代码中还有一项配置:
CURLOPT_SSL_VERIFYPEER => false,
它会关闭服务器证书链验证,连接可能因此遭受中间人攻击。支付接口的生产环境应保持以下配置:
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
如果系统不信任支付服务商的证书链,应正确安装 CA,或者通过 CURLOPT_CAINFO 指定 CA certificate,不能直接关闭验证。
还需要向支付服务商确认的内容
继续修改代码前,需要确认:
- 对方提供的是客户端证书、服务器证书还是 CA certificate;
- 接口是否要求使用 mTLS;
- 客户端 private key 由谁生成和保管;
- 对方是否提供
.p12/.pfx文件及其密码; - 是否需要发送中间证书链;
- HTTP 请求体的格式和
Content-Type; - 是否还要对业务报文单独签名。
mTLS 中的 private key 用于 TLS 身份认证,支付报文签名属于应用层安全机制。两者可能同时使用,不能互相替代。
如果支付服务商只提供了一个不含 private key 的客户端证书,却要求使用该证书进行 mTLS,那么接入材料并不完整。另一种可能是,应先由你方生成 key pair 和 CSR,再交给对方签发客户端证书。无论属于哪种情况,都不应索取或使用 payment server 自己的 private key。
备注:内容仅供参考。
