详解PHP版本兼容之openssl调用参数

背景与问题解决方式

老项目重构支付宝部分代码整合支付宝新的sdk时发现验签总是失败,才发现是open_verify最后的参数传输问题。而open_sign同样如此。本文主要说明open_verify的解决方式和代码解析。而问题的解决方式也是修改最后的加密类型参数,解决方代码如下:

rush:PHP;"> // 将最后的常量OPENSSL_ALGO_SHA256修改成字符串 openssl_verify($data,base64_decode($sign),$res,"sha256WithRSAEncryption");

官方文档解释

上面只说了问题的出现与对应的解决方式,如果有兴趣继续了解该函数的,可以继续往下读,首先来看下官方文档对此函数的解释。

rush:PHP;"> 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库的代码实现。

rush:PHP;"> 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

rush:PHP;"> 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

rush:PHP;"> 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少了几个条件,在此也希望大家发现问题的时候,可以先去解决问题,然后有兴趣的话可以去查看源代码分析下问题所导致的原因。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


服务器优化必备:深入了解PHP8底层开发原理
Golang的网络编程:如何快速构建高性能的网络应用?
Golang和其他编程语言的对比:为什么它的开发效率更高?
PHP8底层开发原理揭秘:如何利用新特性创建出色的Web应用
将字符重新排列以形成回文(如果可能)在C++中
掌握PHP8底层开发原理和新特性:创建高效可扩展的应用程序
服务器性能优化必学:掌握PHP8底层开发原理
PHP8新特性和底层开发原理详解:优化应用性能的终极指南
将 C/C++ 代码转换为汇编语言
深入研究PHP8底层开发原理:创建高效可扩展的应用程序
C++程序查找法向量和迹
PHP8底层开发原理实战指南:提升服务器效能
重排数组,使得当 i 为偶数时,arr[i] >= arr[j],当 i 为奇数时,arr[i] <= arr[j],其中 j < i,使用 C++ 语言实现
Golang的垃圾回收:为什么它可以减少开发人员的负担?
C++程序:将一个数组的所有元素复制到另一个数组中
Golang:构建智能系统的基石
为什么AI开发者应该关注Golang?
在C和C++中,逗号(comma)的用法是用来分隔表达式或语句
PHP8底层开发原理解析及新特性应用实例
利用PHP8底层开发原理解析新特性:如何构建出色的Web应用