PHP实现Volume支付API签名验证报错求助
Volume Webhook支付签名验证错误排查请求
问题描述
在实现Volume的Webhook支付功能时,生产与本地环境均触发签名验证错误:
- 生产环境报错:
error:0480006C:PEM routines::no start line error:02000068:rsa routines::bad signature error:1C880004:Provider routines::RSA lib - 本地环境报错:
error:0909006C:PEM routines:get_name:no start line error:02000068:rsa routines::bad signature
测试代码
<?php // 加载公钥 // 来源:https://api.sandbox.volumepay.io/.well-known/signature/pem $pemContent = file_get_contents("https://api.sandbox.volumepay.io/.well-known/signature/pem"); if ($pemContent === false) { die("读取公钥失败。\n"); } $publicKey = "-----BEGIN PUBLIC KEY-----\n" . trim($pemContent) . "\n-----END PUBLIC KEY-----"; // 加载payload $payload = '{"paymentId":"a08ff808-53bf-4716-8e0d-c1185c8b0b6b","merchantPaymentId":"payment_6705370a441ed9.34887321","paymentStatus":"FAILED","errorDescription":"Failed to create payment authorization - payment provider returned an error","paymentRequest":{"amount":9.99,"currency":"GBP","reference":"Payment Reference"},"paymentRefundData":null,"paymentMetadata":null,"applicationId":"96c1e0da-93ae-407e-aae5-aa02fa314ff9"}'; if ($payload === false) { die("读取payload失败。\n"); } // 加载并解码Base64签名 $signatureBase64 = "dllgtxuoO3SKehfxs02i9PF9i32m//xeEUp2CLFWs9RCjbKSTFCbIdFwjlQKDGUrQcZQVfFQ4XFb6/COMB9pUjWcXX874uWJVZvkzzGDaaqwxb9obkte49o73NlfCrfpk/kKE1MJ3rXxeHJTkh2A2AcE4tDBP8V9M+gWmytLbtpLG6MAF/lvze0wqgy8Kg5eQ3nwcAqqMiz1ruK6XFnzMzURHNPo6kyqaSH/3/dL+j89WiISPOYZ9uUuJmmQGHRbFW6Jor2BjDAav9I6fCIcOgsgwQkXYs+hA42JZUV5adrau4gtHEvxfmt8xualaB15+OenKcM+3CgXRkPSTKNmYQ=="; $signatureBase64 = str_replace('\/', '/', $signatureBase64); //$signatureBase64 = str_replace('//', '/', $signatureBase64); if ($signatureBase64 === false) { die("读取签名失败。\n"); } else { echo "签名Base64: " . $signatureBase64; } $signature = base64_decode($signatureBase64); // 检查签名解码是否成功 if ($signature === false) { die("解码签名失败。\n"); }else{ echo "\n签名解码后Base64: " . $signatureBase64; } // 计算payload的SHA-256哈希 $hashedPayload = hash('sha256', $payload, true); // 初始化公钥资源 $publicKeyResource = openssl_pkey_get_public($publicKey); if ($publicKeyResource === false) { die("加载公钥资源失败。\n"); } // 执行签名验证 $result = openssl_verify($hashedPayload, $signature, $publicKeyResource, OPENSSL_ALGO_SHA256); echo "\n验证结果: " . $result . "\n"; // 释放公钥资源 openssl_free_key($publicKeyResource); echo "Payload:\n{$payload}\n\n"; echo "Payload的SHA-256哈希: " . bin2hex($hashedPayload) . "\n\n"; echo "解码后的签名(Hex): " . bin2hex($signature) . "\n\n"; if ($result === 1) { echo "签名验证成功。\n"; } elseif ($result === 0) { echo "签名验证失败: 签名不匹配。\n"; while ($error = openssl_error_string()) { echo("OpenSSL验证错误: {$error} \n"); } return false; } else { while ($error = openssl_error_string()) { echo("OpenSSL验证过程错误: {$error} \n"); } return false; } ?>
运行输出
$ php index.php 签名Base64: dllgtxuoO3SKehfxs02i9PF9i32m//xeEUp2CLFWs9RCjbKSTFCbIdFwjlQKDGUrQcZQVfFQ4XFb6/COMB9pUjWcXX874uWJVZvkzzGDaaqwxb9obkte49o73NlfCrfpk/kKE1MJ3rXxeHJTkh2A2AcE4tDBP8V9M+gWmytLbtpLG6MAF/lvze0wqgy8Kg5eQ3nwcAqqMiz1ruK6XFnzMzURHNPo6kyqaSH/3/dL+j89WiISPOYZ9uUuJmmQGHRbFW6Jor2BjDAav9I6fCIcOgsgwQkXYs+hA42JZUV5adrau4gtHEvxfmt8xualaB15+OenKcM+3CgXRkPSTKNmYQ== 签名解码后Base64: dllgtxuoO3SKehfxs02i9PF9i32m//xeEUp2CLFWs9RCjbKSTFCbIdFwjlQKDGUrQcZQVfFQ4XFb6/COMB9pUjWcXX874uWJVZvkzzGDaaqwxb9obkte49o73NlfCrfpk/kKE1MJ3rXxeHJTkh2A2AcE4tDBP8V9M+gWmytLbtpLG6MAF/lvze0wqgy8Kg5eQ3nwcAqqMiz1ruK6XFnzMzURHNPo6kyqaSH/3/dL+j89WiISPOYZ9uUuJmmQGHRbFW6Jor2BjDAav9I6fCIcOgsgwQkXYs+hA42JZUV5adrau4gtHEvxfmt8xualaB15+OenKcM+3CgXRkPSTKNmYQ== 验证结果: 0 Payload: {"paymentId":"a08ff808-53bf-4716-8e0d-c1185c8b0b6b","merchantPaymentId":"payment_6705370a441ed9.34887321","paymentStatus":"FAILED","errorDescription":"Failed to create payment authorization - payment provider returned an error","paymentRequest":{"amount":9.99,"currency":"GBP","reference":"Payment Reference"},"paymentRefundData":null,"paymentMetadata":null,"applicationId":"96c1e0da-93ae-407e-aae5-aa02fa314ff9"} Payload的SHA-256哈希: 4e41719f186a3d94cc54a5e569107ea5901a516980527904affba2b34e1c69c8 解码后的签名(Hex): 765960b71ba83b748a7a17f1b34da2f4f17d8b7da6fffc5e114a7608b156b3d4428db2924c509b21d1708e540a0c652b41c65055f150e1715bebf08e301f6952359c5d7f3be2e589559be4cf318369aab0c5bf686e4b5ee3da3bdcd95f0ab7e993f90a135309deb5f1787253921d80d80704e2d0c13fc57d33e8169b2b4b6eda4b1ba30017f96fcded30aa0cbc2a0e5e4379f0700aaa322cf5aee2ba5c59f33335111cd3e8ea4caa6921ffdff74bfa3f3d5a22123ce619f6e52e26699018745b156e89a2bd818c301abfd23a7c221c3a0b20c1091762cfa1038d8965457969dadabb882d1c4bf17e6b7cc6e6a5681d79f8e7a729c33edc28174643d24ca36661 签名验证失败: 签名不匹配。 OpenSSL验证错误: error:0480006C:PEM routines::no start line OpenSSL验证错误: error:02000068:rsa routines::bad signature OpenSSL验证错误: error:1C880004:Provider routines::RSA lib
问题排查与解决方案
1. 修复PEM格式错误(no start line)
你拼接公钥的逻辑存在问题:
trim($pemContent)会移除所有换行符,导致公钥内容不符合PEM规范(要求每64字符分一行)。- 正确做法是保留原公钥的换行结构,仅在缺失头尾标识时补充。
修改公钥加载代码:
$pemContent = file_get_contents("https://api.sandbox.volumepay.io/.well-known/signature/pem"); if ($pemContent === false) { die("读取公钥失败。\n"); } // 检查是否已包含PEM头尾标识,缺失则补充 if (!str_contains($pemContent, "-----BEGIN PUBLIC KEY-----")) { $publicKey = "-----BEGIN PUBLIC KEY-----\n" . $pemContent . "\n-----END PUBLIC KEY-----"; } else { $publicKey = $pemContent; }
2. 修正签名验证逻辑
openssl_verify不需要手动计算payload的哈希,函数会自动按指定算法处理:
- 当前代码传入的是预哈希后的
$hashedPayload,但函数需要原始payload,它会重新执行哈希并验证签名。
修改验证逻辑:
// 移除手动计算哈希的代码 // $hashedPayload = hash('sha256', $payload, true); // 直接传入原始payload进行验证 $result = openssl_verify($payload, $signature, $publicKeyResource, OPENSSL_ALGO_SHA256);
3. 移除多余的Base64处理
str_replace('\/', '/', $signatureBase64)属于冗余操作,签名的Base64不会包含转义斜杠,只有从JSON字符串中读取时才需要处理,直接解码即可。
4. 额外检查项
- 生产环境需使用对应环境的公钥,不要混用沙箱环境的公钥。
- 接收Webhook时,必须保证原始payload的字符、格式、编码完全一致,不能做任何修改(包括空格、换行、引号)。
修改后的完整测试代码
<?php // 加载公钥 $pemContent = file_get_contents("https://api.sandbox.volumepay.io/.well-known/signature/pem"); if ($pemContent === false) { die("读取公钥失败。\n"); } // 确保公钥格式合规 if (!str_contains($pemContent, "-----BEGIN PUBLIC KEY-----")) { $publicKey = "-----BEGIN PUBLIC KEY-----\n" . $pemContent . "\n-----END PUBLIC KEY-----"; } else { $publicKey = $pemContent; } // 加载payload $payload = '{"paymentId":"a08ff808-53bf-4716-8e0d-c1185c8b0b6b","merchantPaymentId":"payment_6705370a441ed9.34887321","paymentStatus":"FAILED","errorDescription":"Failed to create payment authorization - payment provider returned an error","paymentRequest":{"amount":9.99,"currency":"GBP","reference":"Payment Reference"},"paymentRefundData":null,"paymentMetadata":null,"applicationId":"96c1e0da-93ae-407e-aae5-aa02fa314ff9"}'; if ($payload === false) { die("读取payload失败。\n"); } // 解码签名 $signatureBase64 = "dllgtxuoO3SKehfxs02i9PF9i32m//xeEUp2CLFWs9RCjbKSTFCbIdFwjlQKDGUrQcZQVfFQ4XFb6/COMB9pUjWcXX874uWJVZvkzzGDaaqwxb9obkte49o73NlfCrfpk/kKE1MJ3rXxeHJTkh2A2AcE4tDBP8V9M+gWmytLbtpLG6MAF/lvze0wqgy8Kg5eQ3nwcAqqMiz1ruK6XFnzMzURHNPo6kyqaSH/3/dL+j89WiISPOYZ9uUuJmmQGHRbFW6Jor2BjDAav9I6fCIcOgsgwQkXYs+hA42JZUV5adrau4gtHEvxfmt8xualaB15+OenKcM+3CgXRkPSTKNmYQ=="; $signature = base64_decode($signatureBase64); if ($signature === false) { die("解码签名失败。\n"); } // 初始化公钥资源 $publicKeyResource = openssl_pkey_get_public($publicKey); if ($publicKeyResource === false) { die("加载公钥资源失败。\n"); } // 执行签名验证 $result = openssl_verify($payload, $signature, $publicKeyResource, OPENSSL_ALGO_SHA256); echo "\n验证结果: " . $result . "\n"; // 释放资源 openssl_free_key($publicKeyResource); echo "Payload:\n{$payload}\n\n"; if ($result === 1) { echo "签名验证成功。\n"; } elseif ($result === 0) { echo "签名验证失败: 签名不匹配。\n"; while ($error = openssl_error_string()) { echo("OpenSSL验证错误: {$error} \n"); } exit(1); } else { while ($error = openssl_error_string()) { echo("OpenSSL验证过程错误: {$error} \n"); } exit(1); } ?>
内容的提问来源于stack exchange,提问作者Cleber Castiglioni
相关产品推荐
相关产品推荐

