如何在 PHP 中验证 Paypal webhook 签名?

Posted

技术标签:

【中文标题】如何在 PHP 中验证 Paypal webhook 签名?【英文标题】:How to verify Paypal webhook signature in PHP? 【发布时间】:2016-05-17 09:28:51 【问题描述】:

我对 SSL 和证书不是很了解。我用了帖子 "How to use hash_hmac() with "SHA256withRSA" on php?" 看看我是否可以使用 PayPal 获取 webhook。

我遇到的问题是在调用openssl_verify() 并返回结果为 (0) 后出现以下错误:

OpenSSL 错误 openssl_verify 错误:04091068:rsa 例程:INT_RSA_VERIFY:bad signature

我已尝试解决此问题,但有关错误和网络功能的文档很少。

我当前的代码如下所示:

 // get the header post to my php file by PayPal
 $headers = apache_request_headers();
 // get the body post to me php file by PayPal
 $body = @file_get_contents('php://input');
 $json = json_decode($body);

 // TransmissionId|TransmissionTimeStamp|WebhookId|CRC32 as per PayPal documentation
 $sigString = $headers['Paypal-Transmission-Id'].'|'.$headers['Paypal-Transmission-Time'].'|'.$json->id.'|'.crc32($body);

 // $headers['Paypal-Cert-Url'] contains the "-----BEGIN CERTIFICATE---MIIHmjCCBoKgAwIBAgIQDB8 ... -----END CERTIFICATE-----"
 $pubKey = openssl_pkey_get_public(file_get_contents($headers['Paypal-Cert-Url']));

 // and this is the call to verify that returns result (0)
 $verifyResult = openssl_verify($sigString, base64_decode($headers['Paypal-Transmission-Sig']), $pubKey, 'sha256WithRSAEncryption');

与我使用的参考代码唯一不同的是,我不使用openssl_pkey_get_details($pubKey),因为除了现有的签名错误之外,我还会得到以下错误:

OpenSSL 错误 openssl_verify 错误:0906D06C:PEM 例程:PEM_read_bio:no start line OpenSSL 错误 openssl_verify 错误:04091068:rsa 例程:INT_RSA_VERIFY:bad signature

我还尝试了一种变体,即不在标头上使用base64_decode(),但会得到相同的返回结果 (0) 并显示错误:

OpenSSL 错误 openssl_verify 错误:04091077:rsa routines:INT_RSA_VERIFY:wrong signature length

签名有什么问题?

【问题讨论】:

我相信你不应该使用$json->id,因为这是事件的ID,而不是网络钩子的ID。网络挂钩 ID 不会随事件一起发送 - 您必须在此处使用常量。 【参考方案1】:

公式是<transmissionId>|<timeStamp>|<webhookId>|<crc32> 而不是<transmissionId>|<timeStamp>|<eventId>|<crc32>。另请注意,无法验证 Webhook 模拟器事件。

【讨论】:

【参考方案2】:

这可能不是您正在寻找的,但是使用 Open SSL 手动验证签名的替代方法可能是使用 PayPal PHP Restful API。

PayPal Restful API 公开了一个端点,允许您验证 webhook:/v1/notifications/verify-webhook-signature

PayPal-PHP-SDK 提供了一个 VerifyWebhookSignature 类,可以轻松调用该端点。

他们还有一个Sample Script 说明如何使用VerifyWebhookSignature 类。

【讨论】:

【参考方案3】:

你可能想要使用这段代码:

$pubKey = openssl_pkey_get_public(file_get_contents($headers['PAYPAL-CERT-URL']));
$details = openssl_pkey_get_details($pubKey);

$verifyResult = openssl_verify($sigString, base64_decode($headers['PAYPAL-TRANSMISSION-SIG']), $details['key'], 'sha256WithRSAEncryption');

if ($verifyResult === 0) 
    throw new Exception('signature incorrect');
 elseif ($verifyResult === -1) 
    throw new Exception('error checking signature');

【讨论】:

我猜@Maxime Rainville 建议的带有官方 SDK 的解决方案是一个更好的主意。即使您需要为此执行另一个 HTTP 调用。

以上是关于如何在 PHP 中验证 Paypal webhook 签名?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PHP Omnipay 和 Paypal Express Checkout 验证付款?

Paypal 经常性问题 ruby​​ on rails

如何验证 PayPal 订阅

使用非托管 PayPal 按钮时如何使用 IPN 验证 PayPal 数据

PHP 验证 Paypal webhook 签名

Paypal 智能按钮元数据 - Webhook