各位大佬,貌似官方的php sdk里面没有公钥证书的签名接口,是不是公钥证书签名目前不支持php
=== 2018/8/30 16:00 终于搞定php对公钥证书签名 根据java SDK源码,用php取公钥证书SN如下:
$p = [];
$str = file_get_contents('./alipayRootCert.crt');
$p["alipay_root_cert_sn"] = getRootCertSN($str);
$str = file_get_contents('./alipayCertPublicKey_RSA2.crt');
$p["alipay_cert_sn"] = getCertSN($str);
$str = file_get_contents('./appCertPublicKey.crt');
$p["app_cert_sn"] = getCertSN($str);
var_export($p);
function getRootCertSN($str)
{
$arr = preg_split('/(?=-----BEGIN)/', $str, -1, PREG_SPLIT_NO_EMPTY);
$str = null;
foreach ($arr as $e) {
$sn = getCertSN($e, true);
if (!$sn)
continue;
if ($str === null)
$str = $sn;
else
$str .= "_" . $sn;
}
return $str;
}
function getCertSN($str, $matchAlgo=false)
{
/*
根据java SDK源码:AntCertificationUtil::getRootCertSN
对证书链中RSA的项目进行过滤(猜测是gm国密算法java抛错搞不定,故意略去)
java源码为:
if(c.getSigAlgOID().startsWith("1.2.840.113549.1.1"))
根据 https://www.alvestrand.no/objectid/1.2.840.113549.1.1.html
该OID为RSA算法系。
*/
if ($matchAlgo) {
openssl_x509_export($str, $out, false);
if (!preg_match('/Signature Algorithm:.*?RSA/im', $out, $m))
return;
}
$a = openssl_x509_parse($str);
$issuer = null;
// 注意:根据java代码输出,需要倒着排列 CN,OU,O
foreach ($a["issuer"] as $k=>$v) {
if ($issuer === null) {
$issuer = "$k=$v";
}
else {
$issuer = "$k=$v," . $issuer;
}
}
# echo($issuer . $a["serialNumber"] . "\n");
$sn = md5($issuer . $a["serialNumber"]);
return $sn;
}
做为首批踩坑者,只能长叹一声
=== 2018/8/30 12:15 更新:
名字和序列化取出来像这样:
CN=Ant Financial Certification Authority Class 2 R1,OU=Certification Authority,O=Ant Financial,C=CN
42665268812499181166312682537244063920
看了半天java源码,终于运行起来,把sn值打出来了,拷贝到php中,终于看到手机上支付页面出来了。结果大致像这样:
"app_cert_sn" => "06de145e15a73a8ce87f3eefeddce8b2",
//"alipay_cert_sn" => "b1a855128d973b1ff1b1609bbce77fe0",
"alipay_root_cert_sn" => "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6"
支付宝根证书要用特殊方法来取(源码里用AntCertificationUtil.getRootCertSN),怪不得之前写的不行。
定此接口的人需要深刻检讨,竟然直接以java语言的某个输出来定接口规范。难怪php或其它语言的相关sdk没有出来,我感觉也不好写。
============ 旧消息
新申请的开放平台帐号,被强制使用了公钥证书签名。 !!!下面开始吐槽:
首先是接口文档比如 https://docs.open.alipay.com/api_1/alipay.trade.app.pay 中完全没提到有公钥证书签名这回事。
调用支付出问题后(报错总是报“支付取消”??),四处查文档,才找到php SDK没有提供此功能,于是决定按接口文档来写。
签名算法的文档中介绍是这样介绍算法的: “SN值是通过解析X.509证书文件中签发机构名称(name)以及内置序列号(serialNumber),将二者拼接后的字符串计算MD5值获取”
尼码,文档完全没有介绍name和serialNumber分别是怎样的文本格式?也没有例子。 我试了name是 “ROOTCA”,“/C=CN/O=NRCAC/CN=ROOTCA”,“C=CN, O=NRCAC, CN=ROOTCA”各种格式,而serialNumber格式我试了 “69e2fec0170ac67b”以及加"69 e2..."、加“69:e2..."等,以及各种组合,都没有验签成功。
于是走第二条路,找它说的java SDK源码来参考。打开java sdk链接,是在maven上的,找了好久才搜到了新版本java源码。
终于找到了介绍的getCertSN函数,其核心是这样实现的:
public static String getCertSN(String certPath)throws AlipayApiException{
InputStream inputStream = null;
inputStream = new FileInputStream(certPath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inputStream);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((cert.getIssuerX500Principal().getName()+cert.getSerialNumber()).getBytes());
String certSN = new BigInteger(1,md.digest()).toString(16);
//BigInteger会把0省略掉,需补全至32位
certSN = fillMD5(certSN);
return certSN;
于是找这cert.getIssuerX500Principal().getName()+cert.getSerialNumber()文档 实在看不懂,算了,还是直接运行这段java代码吧,把结果复制到php中填参数也可以,眼看就要成功了,结果运行java是这样的:
java.security.cert.CertificateParsingException: java.io.IOException: Unknown named curve: 1.2.156.10197.1.301
at sun.security.x509.X509CertInfo.<init>(Unknown Source)
at sun.security.x509.X509CertImpl.parse(Unknown Source)
at sun.security.x509.X509CertImpl.<init>(Unknown Source)
at sun.security.provider.X509Factory.engineGenerateCertificate(Unknown Source)
at java.security.cert.CertificateFactory.generateCertificate(Unknown Source)
at j1.getCertSN(j1.java:33)
at j1.main(j1.java:24)
搜索发现,是java不支持国密算法。。。(https://cloud.tencent.com/info/fcfae2f8bd49fcce8737c7485afcdf29.html)
https://www.freesion.com/article/9400246878/ 这篇文章亲测有效
资金类接口全强制要求公钥证书,结果下了官方的PHP的SDK和demo后,在demo里找不到公钥证书的验签方式。无奈只能自己重写了个demo,调试的时候也踩了不少坑才配置成功。
以前支付宝PHP的SDK就是一坨屎,那个什么AopSDK给你加了个什么鸟lotusphp_runtime框架。现在好了,连屎都吃不上了
用了你的方法生成的app_cert_sn,提交上去提示
错误代码 app-cert-not-exist 错误原因: 应用公钥证书不存在
function getSn($filepath, $isroot = false)
{
$fileData = file_get_contents($filepath);
if ($isroot) {
return getRootCertSN($fileData);
} else {
return getCertSN($fileData);
}
return $md5_str;
}
function getRootCertSN($str)
{
// return '687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6';
$arr = preg_split('/(?=-----BEGIN)/', $str, -1, PREG_SPLIT_NO_EMPTY);
$str = null;
foreach ($arr as $e) {
$sn = getCertSN($e, true);
if (!$sn) {
continue;
}
if ($str === null) {
$str = $sn;
} else {
$str .= "_" . $sn;
}
}
return $str;
}
function getCertSN($str, $matchAlgo = false)
{
/*
根据java SDK源码:AntCertificationUtil::getRootCertSN
对证书链中RSA的项目进行过滤(猜测是gm国密算法java抛错搞不定,故意略去)
java源码为:
if(c.getSigAlgOID().startsWith("1.2.840.113549.1.1"))
根据 https://www.alvestrand.no/objectid/1.2.840.113549.1.1.html
该OID为RSA算法系。
*/
if ($matchAlgo) {
openssl_x509_export($str, $out, false);
if (!preg_match('/Signature Algorithm:.*?RSA/im', $out, $m)) {
return;
}
}
$a = openssl_x509_parse($str);
$issuer = null;
// 注意:根据java代码输出,需要倒着排列 CN,OU,O
foreach ($a["issuer"] as $k => $v) {
if ($issuer === null) {
$issuer = "$k=$v";
} else {
$issuer = "$k=$v," . $issuer;
}
}
# echo($issuer . $a["serialNumber"] . "\n");
$serialNumberHex = decimalNotation($a['serialNumberHex']);
$sn = md5($issuer . $serialNumberHex);
return $sn;
}
function decimalNotation($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++) {
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
修复16进制转10进制问题
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。