详解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 相关文章推荐
可以在线执行PHP代码包装修正版
Mar 15 PHP
php 进度条实现代码
Mar 10 PHP
php 求质素(素数) 的实现代码
Apr 12 PHP
php写的简易聊天室代码
Jun 04 PHP
PHP中根据IP地址判断城市实现城市切换或跳转代码
Sep 04 PHP
thinkphp中html:list标签传递多个参数实例
Oct 30 PHP
PHP使用strstr()函数获取指定字符串后所有字符的方法
Jan 07 PHP
php单链表实现代码分享
Jul 04 PHP
redis查看连接数及php模拟并发创建redis连接的方法
Dec 15 PHP
深入理解Yii2.0乐观锁与悲观锁的原理与使用
Jul 26 PHP
php获取微信共享收货地址的方法
Dec 21 PHP
Yii框架的redis命令使用方法简单示例
Oct 15 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 Global定义全局变量使用说明
2013/08/15 PHP
一个js的tab切换效果代码[代码分离]
2010/04/11 Javascript
asp.net+jquery滚动滚动条加载数据的下拉控件
2010/06/25 Javascript
javascript中检测变量的类型的代码
2010/12/28 Javascript
JavaScript高级程序设计(第3版)学习笔记12 js正则表达式
2012/10/11 Javascript
js控制CSS样式属性语法对照表
2012/12/11 Javascript
jquery showModelDialog的使用方法示例详解
2013/11/19 Javascript
jquery遍历筛选数组的几种方法和遍历解析json对象
2013/12/13 Javascript
js打开windows上的可执行文件示例
2014/05/27 Javascript
jQuery Ajax中的事件详细介绍
2015/04/16 Javascript
jquery点赞功能实现代码 点个赞吧!
2020/05/29 jQuery
jQuery实现切换隐藏与显示同时切换图标功能
2017/10/29 jQuery
vue使用axios时关于this的指向问题详解
2017/12/22 Javascript
详解VUE单页应用骨架屏方案
2019/01/17 Javascript
Javascript实现鼠标点击冒泡特效
2019/12/24 Javascript
python中使用序列的方法
2015/08/03 Python
Python第三方库xlrd/xlwt的安装与读写Excel表格
2017/01/21 Python
Django项目实战之用户头像上传与访问的示例
2018/04/21 Python
Python判断中文字符串是否相等的实例
2018/07/06 Python
Python面向对象基础入门之设置对象属性
2018/12/11 Python
pytorch:model.train和model.eval用法及区别详解
2020/02/20 Python
ubuntu 安装pyqt5和卸载pyQt5的方法
2020/03/24 Python
基于Python绘制个人足迹地图
2020/06/01 Python
html5使用html2canvas实现浏览器截图的示例
2017/08/31 HTML / CSS
学习党课思想汇报
2013/12/29 职场文书
年度考核自我评价
2014/01/25 职场文书
运动会跳远广播稿
2014/02/04 职场文书
土木工程专业推荐信
2014/02/19 职场文书
副总经理岗位职责
2014/03/16 职场文书
村居抓节水倡议书
2014/05/19 职场文书
咖啡店创业计划书范文
2014/09/15 职场文书
2015年个人思想总结
2015/03/09 职场文书
2015年宣传部个人工作总结
2015/05/14 职场文书
焦裕禄观后感
2015/06/03 职场文书
关于Oracle12C默认用户名system密码不正确的解决方案
2021/10/16 Oracle
MySQL对数据表已有表进行分区表的实现
2021/11/01 MySQL