详解PHP版本兼容之openssl调用参数
背景与问题解决方式
老项目重构支付宝部分代码整合支付宝新的sdk时发现验签总是失败,才发现是open_verify最后的参数传输问题。而open_sign同样如此。本文主要说明open_verify的解决方式和代码解析。而问题的解决方式也是修改最后的加密类型参数,解决方式代码如下:
// 将最后的常量openssl_algo_sha256修改成字符串 openssl_verify($data, base64_decode($sign), $res, "sha256withrsaencryption");
官方文档解释
上面只说了问题的出现与对应的解决方式,如果有兴趣继续了解该函数的,可以继续往下读,首先来看下官方文档对此函数的解释。
int openssl_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 格式的密钥, 比如, “—–begin public key—– miibcgk…”
signature_alg
int - 以下签名算法之一signature algorithms.
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] => gost 28147-89 mac
[6] => gost r 34.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); } else if (z_type_p(method) == is_string) { mdtype = evp_get_digestbyname(z_strval_p(method)); } else { php_error_docref(null, e_warning, "unknown signature algorithm."); return_false; }
原来是枚举值的问题?
一开始本人以为php5.3版本会是method参数类型的限制,一看源代码才发现,openssl_verify函数的实现逻辑是一致的,都是检测method参数类型,那么问题就不出现在参数类型上,然后我查看了参数为long类型是所调用的php_openssl_get_evp_md_from_algo函数,果然发现了问题所在。源码如下:
php5.3.27
static evp_md * php_openssl_get_evp_md_from_algo(long algo) { /* {{{ */ evp_md *mdtype; switch (algo) { case openssl_algo_sha1: mdtype = (evp_md *) evp_sha1(); break; case openssl_algo_md5: mdtype = (evp_md *) evp_md5(); break; case openssl_algo_md4: mdtype = (evp_md *) evp_md4(); break; #ifdef have_openssl_md2_h case openssl_algo_md2: mdtype = (evp_md *) evp_md2(); break; #endif case openssl_algo_dss1: mdtype = (evp_md *) evp_dss1(); break; default: return null; break; } return mdtype; }
php7.1.18
static evp_md * php_openssl_get_evp_md_from_algo(zend_long algo) { /* {{{ */ evp_md *mdtype; switch (algo) { case openssl_algo_sha1: mdtype = (evp_md *) evp_sha1(); break; case openssl_algo_md5: mdtype = (evp_md *) evp_md5(); break; case openssl_algo_md4: mdtype = (evp_md *) evp_md4(); break; #ifdef have_openssl_md2_h case openssl_algo_md2: mdtype = (evp_md *) evp_md2(); break; #endif #if openssl_version_number < 0x10100000l || defined (libressl_version_number) case openssl_algo_dss1: mdtype = (evp_md *) evp_dss1(); break; #endif case openssl_algo_sha224: mdtype = (evp_md *) evp_sha224(); break; case openssl_algo_sha256: mdtype = (evp_md *) evp_sha256(); break; case openssl_algo_sha384: mdtype = (evp_md *) evp_sha384(); break; case openssl_algo_sha512: mdtype = (evp_md *) evp_sha512(); break; case openssl_algo_rmd160: mdtype = (evp_md *) evp_ripemd160(); break; default: return null; break; } return mdtype; }
由上面源代码可以很清晰的发现问题所在,随着php版本的升级,其所在的openssl扩展对应的调用条件也增加了很多,最后导致上述问题的源码也只是switch…case少了几个条件,在此也希望大家发现问题的时候,可以先去解决问题,然后有兴趣的话可以去查看源代码分析下问题所导致的原因。
上一篇: php实现等比例压缩图片