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


Posted in PHP onJuly 25, 2018

背景与问题解决方式

老项目重构支付宝部分代码整合支付宝新的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 相关文章推荐
E路文章系统PHP
Dec 11 PHP
PHP+MYSQL 出现乱码的解决方法
Aug 08 PHP
php数组函数序列之in_array() 查找数组值是否存在
Oct 29 PHP
PHP随机生成随机个数的字母组合示例
Jan 14 PHP
php中error与exception的区别及应用
Jul 28 PHP
PHP过滤黑名单关键字的方法
Dec 01 PHP
PHP实现的构造sql语句类实例
Feb 03 PHP
PHP实现带重试功能的curl连接示例
Jul 28 PHP
PHP接收App端发送文件流的方法
Sep 23 PHP
form表单传递数组数据、php脚本接收的实例
Feb 09 PHP
PHP文件系统管理(实例讲解)
Sep 19 PHP
PHP程序员简单的开展服务治理架构操作详解(三)
May 14 PHP
PHP实现的多维数组去重操作示例
Jul 21 #PHP
php实现生成PDF文件的方法示例【基于FPDF类库】
Jul 21 #PHP
记录Yii2框架开发微信公众号遇到的问题及解决方法
Jul 20 #PHP
ThinkPHP 3使用OSS的方法
Jul 19 #PHP
php命令行写shell实例详解
Jul 19 #PHP
php工具型代码之印章抠图
Jul 18 #PHP
php压缩文件夹最新版
Jul 18 #PHP
You might like
PHP中显示格式化的用户输入
2006/10/09 PHP
swfupload 多文件上传实现代码
2008/08/27 PHP
PHP创建/删除/复制文件夹、文件
2016/05/03 PHP
php使用Jpgraph创建折线图效果示例
2017/02/15 PHP
PHP中register_shutdown_function函数的基础介绍与用法详解
2017/11/28 PHP
Alliance vs AM BO3 第二场2.13
2021/03/10 DOTA
[转]JS宝典学习笔记
2007/02/07 Javascript
javascript的trim,ltrim,rtrim自定义函数
2008/09/21 Javascript
javascript 对表格的行和列都能加亮显示
2008/12/26 Javascript
javascript 处理事件绑定的一些兼容写法
2009/12/24 Javascript
nodejs npm package.json中文文档
2014/09/04 NodeJs
JQuery拖动表头边框线调整表格列宽效果代码
2014/09/10 Javascript
图解JavaScript中的this关键字
2020/05/28 Javascript
Bootstrap按钮组件详解
2016/04/26 Javascript
详解Vue.js——60分钟组件快速入门(上篇)
2016/12/05 Javascript
基于Vue.js实现tab滑块效果
2017/07/23 Javascript
vue.js整合vux中的上拉加载下拉刷新实例教程
2018/01/09 Javascript
vue使用v-for实现hover点击效果
2018/09/29 Javascript
SSM+layUI 根据登录信息显示不同的页面方法
2019/09/20 Javascript
Layui带搜索的下拉框的使用以及动态数据绑定方法
2019/09/28 Javascript
微信小程序手动添加收货地址省市区联动
2020/05/18 Javascript
微信小程序 button样式设置为图片的方法
2020/06/19 Javascript
Element Alert警告的具体使用方法
2020/07/27 Javascript
js实现贪吃蛇小游戏(加墙)
2020/07/31 Javascript
vant 时间选择器--开始时间和结束时间实例
2020/11/04 Javascript
让Vue响应Map或Set的变化操作
2020/11/11 Javascript
Python中的ceil()方法使用教程
2015/05/14 Python
tensorflow实现逻辑回归模型
2018/09/08 Python
详解Matplotlib绘图之属性设置
2019/08/23 Python
TensorFlow获取加载模型中的全部张量名称代码
2020/02/11 Python
python matplotlib:plt.scatter() 大小和颜色参数详解
2020/04/14 Python
机械设计职业生涯规划书
2013/12/27 职场文书
小学优秀班集体申报材料
2014/05/25 职场文书
民事二审代理词
2015/05/25 职场文书
再读《皇帝的新衣》的读后感悟!
2019/08/07 职场文书
Linux磁盘管理方法介绍
2022/06/01 Servers