详解PHP版本兼容之openssl调用参数
背景与问题解决方式
老项目重构支付宝部分代码整合支付宝新的sdk时发现验签总是失败,才发现是open_verify最后的参数传输问题。而open_sign同样如此。本文主要说明open_verify的解决方式和代码解析。而问题的解决方式也是修改最后的加密类型参数,解决方式代码如下:
//将最后的常量OPENSSL_ALGO_SHA256修改成字符串 openssl_verify($data,base64_decode($sign),$res,"sha256WithRSAEncryption");
官方文档解释
上面只说了问题的出现与对应的解决方式,如果有兴趣继续了解该函数的,可以继续往下读,首先来看下官方文档对此函数的解释。
intopenssl_verify(string$data,string$signature,mixed$pub_key_id[,mixed$signature_alg=OPENSSL_ALGO_SHA1])
参数注释
data
以前用来生成签名的数据字符串。
signature
原始二进制字符串,通过openssl_sign()或类似的函数生成。
pub_key_id
resource-一个密钥,通过openssl_get_publickey()函数返回。
string-一个PEM格式的密钥,比如,“—–BEGINPUBLICKEY—–MIIBCgK…”
signature_alg
int-以下签名算法之一SignatureAlgorithms.
string-由openssl_get_md_methods()函数返回的可用字符串,比如,“sha1WithRSAEncryption”或者“sha512”.
官方文档给出的signature_alg参数可以为int或者string类型,int类型直接调用对应的枚举值,string则是openssl_get_md_methods函数返回的可用字符串,调用openssl_get_md_methods方法打印参数如下,而这些字符串也是对应加密方式的摘要信息,后文源码中可能会看的对函数调用稍微明白那么一丢丢。
Array
(
[0]=>DSA
[1]=>DSA-SHA
[2]=>DSA-SHA1
[3]=>DSA-SHA1-old
[4]=>DSS1
[5]=>GOST28147-89MAC
[6]=>GOSTR34.11-94
[7]=>MD4
[8]=>MD5
[9]=>MDC2
[10]=>RIPEMD160
[11]=>RSA-MD4
[12]=>RSA-MD5
[13]=>RSA-MDC2
[14]=>RSA-RIPEMD160
[15]=>RSA-SHA
[16]=>RSA-SHA1
[17]=>RSA-SHA1-2
[18]=>RSA-SHA224
[19]=>RSA-SHA256
[20]=>RSA-SHA384
[21]=>RSA-SHA512
[22]=>SHA
[23]=>SHA1
[24]=>SHA224
[25]=>SHA256
[26]=>SHA384
[27]=>SHA512
[28]=>dsaEncryption
[29]=>dsaWithSHA
[30]=>dsaWithSHA1
[31]=>dss1
[32]=>ecdsa-with-SHA1
[33]=>gost-mac
[34]=>md4
[35]=>md4WithRSAEncryption
[36]=>md5
[37]=>md5WithRSAEncryption
[38]=>md_gost94
[39]=>mdc2
[40]=>mdc2WithRSA
[41]=>ripemd
[42]=>ripemd160
[43]=>ripemd160WithRSA
[44]=>rmd160
[45]=>sha
[46]=>sha1
[47]=>sha1WithRSAEncryption
[48]=>sha224
[49]=>sha224WithRSAEncryption
[50]=>sha256
[51]=>sha256WithRSAEncryption
[52]=>sha384
[53]=>sha384WithRSAEncryption
[54]=>sha512
[55]=>sha512WithRSAEncryption
[56]=>shaWithRSAEncryption
[57]=>ssl2-md5
[58]=>ssl3-md5
[59]=>ssl3-sha1
[60]=>whirlpool
)
由此也可看出函数是兼容两种模式的,但是为什么php版本会有兼容问题么?在openssl库版本是一致的情况下,接下来的原因应该只遗留在php扩展的问题上。那下面来看看对应的源码去发现问题出现在哪吧。
函数源码
openssl_verify函数源码
openssl_verify源码中有这样一段,如果参数method为string类型的时候,调用openssl库的EVP_get_digestbyname方法,在网上查看了下此方法的作用,主要是根据摘要信息返回
EVP_MD结构,而EVP_get_digestbyname方法由于是openssl库源代码并且对C语言知之甚少,熊某就没去查看,
只是了解php代码调用背后的一些处理逻辑,有兴趣的可以看看openssl库的代码实现。
if(method==NULL||Z_TYPE_P(method)==IS_LONG){ if(method!=NULL){ signature_algo=Z_LVAL_P(method); } mdtype=php_openssl_get_evp_md_from_algo(signature_algo); }elseif(Z_TYPE_P(method)==IS_STRING){ mdtype=EVP_get_digestbyname(Z_STRVAL_P(method)); }else{ php_error_docref(NULL,E_WARNING,"Unknownsignaturealgorithm."); RETURN_FALSE; }
原来是枚举值的问题?
一开始本人以为php5.3版本会是method参数类型的限制,一看源代码才发现,openssl_verify函数的实现逻辑是一致的,都是检测method参数类型,那么问题就不出现在参数类型上,然后我查看了参数为long类型是所调用的php_openssl_get_evp_md_from_algo函数,果然发现了问题所在。源码如下:
php5.3.27
staticEVP_MD*php_openssl_get_evp_md_from_algo(longalgo){/*{{{*/ EVP_MD*mdtype; switch(algo){ caseOPENSSL_ALGO_SHA1: mdtype=(EVP_MD*)EVP_sha1(); break; caseOPENSSL_ALGO_MD5: mdtype=(EVP_MD*)EVP_md5(); break; caseOPENSSL_ALGO_MD4: mdtype=(EVP_MD*)EVP_md4(); break; #ifdefHAVE_OPENSSL_MD2_H caseOPENSSL_ALGO_MD2: mdtype=(EVP_MD*)EVP_md2(); break; #endif caseOPENSSL_ALGO_DSS1: mdtype=(EVP_MD*)EVP_dss1(); break; default: returnNULL; break; } returnmdtype; }
php7.1.18
staticEVP_MD*php_openssl_get_evp_md_from_algo(zend_longalgo){/*{{{*/ EVP_MD*mdtype; switch(algo){ caseOPENSSL_ALGO_SHA1: mdtype=(EVP_MD*)EVP_sha1(); break; caseOPENSSL_ALGO_MD5: mdtype=(EVP_MD*)EVP_md5(); break; caseOPENSSL_ALGO_MD4: mdtype=(EVP_MD*)EVP_md4(); break; #ifdefHAVE_OPENSSL_MD2_H caseOPENSSL_ALGO_MD2: mdtype=(EVP_MD*)EVP_md2(); break; #endif #ifOPENSSL_VERSION_NUMBER<0x10100000L||defined(LIBRESSL_VERSION_NUMBER) caseOPENSSL_ALGO_DSS1: mdtype=(EVP_MD*)EVP_dss1(); break; #endif caseOPENSSL_ALGO_SHA224: mdtype=(EVP_MD*)EVP_sha224(); break; caseOPENSSL_ALGO_SHA256: mdtype=(EVP_MD*)EVP_sha256(); break; caseOPENSSL_ALGO_SHA384: mdtype=(EVP_MD*)EVP_sha384(); break; caseOPENSSL_ALGO_SHA512: mdtype=(EVP_MD*)EVP_sha512(); break; caseOPENSSL_ALGO_RMD160: mdtype=(EVP_MD*)EVP_ripemd160(); break; default: returnNULL; break; } returnmdtype; }
由上面源代码可以很清晰的发现问题所在,随着php版本的升级,其所在的openssl扩展对应的调用条件也增加了很多,最后导致上述问题的源码也只是switch…case少了几个条件,在此也希望大家发现问题的时候,可以先去解决问题,然后有兴趣的话可以去查看源代码分析下问题所导致的原因。