详解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正则
Jul 07 PHP
基于PHP读取TXT文件向数据库导入海量数据的方法
Apr 23 PHP
深入Memcache的Session数据的多服务器共享详解
Jun 13 PHP
PHP 线程安全与非线程安全版本的区别深入解析
Aug 06 PHP
PHP制作图形验证码代码分享
Oct 23 PHP
PHP入门教程之正则表达式基本用法实例详解(正则匹配,搜索,分割等)
Sep 11 PHP
Yii2框架RESTful API 格式化响应,授权认证和速率限制三部分详解
Nov 10 PHP
php连接微软MSSQL(sql server)完全攻略
Nov 27 PHP
php实现微信公众号企业转账功能
Oct 01 PHP
php分享朋友圈的实现代码
Feb 18 PHP
php使用redis的有序集合zset实现延迟队列应用示例
Feb 20 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大小写问题:函数名和类名不区分,变量名区分
2013/06/17 PHP
修改destoon会员公司的伪静态中的com目录的方法
2014/08/21 PHP
WordPress中自定义后台管理界面配色方案的小技巧
2015/12/29 PHP
zend framework中使用memcache的方法
2016/03/04 PHP
详解yii2实现分库分表的方案与思路
2017/02/03 PHP
PHP实现Snowflake生成分布式唯一ID的方法示例
2020/08/30 PHP
jQuery JSON的解析方式分享
2011/04/05 Javascript
jquery中防刷IP流量软件影响统计的一点对策
2011/07/10 Javascript
js禁止document element对象选中文本实现代码
2013/03/21 Javascript
创建js对象和js类的方法汇总
2014/12/24 Javascript
jQuery实现ichat在线客服插件
2014/12/29 Javascript
jquery动态添加删除(tr/td)
2015/02/09 Javascript
jQuery实现按钮的点击 全选/反选 单选框/复选框 文本框 表单验证
2015/06/25 Javascript
JS实现文档加载完成后执行代码
2015/07/09 Javascript
JavaScript实现文字跟随鼠标特效
2015/08/06 Javascript
javascript的 {} 语句块详解
2016/02/27 Javascript
利用Query+bootstrap和js两种方式实现日期选择器
2017/01/10 Javascript
Bootstrap模态框插件使用详解
2017/05/11 Javascript
Vue-router 类似Vuex实现组件化开发的示例
2017/09/15 Javascript
小程序两种滚动公告栏的实现方法
2019/09/17 Javascript
js前端如何写一个精确的倒计时代码
2019/10/25 Javascript
[06:42]DOTA2每周TOP10 精彩击杀集锦vol.1
2014/06/25 DOTA
[48:32]VGJ.T vs Fnatic 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
[58:46]OG vs NAVI 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
Python 3.x 新特性及10大变化
2015/06/12 Python
python opencv 二值化 计算白色像素点的实例
2019/07/03 Python
Django-Model数据库操作(增删改查、连表结构)详解
2019/07/17 Python
Python Selenium安装及环境配置的实现
2020/03/17 Python
莫斯科购买书籍网站:Book24
2020/01/12 全球购物
退休感言
2014/01/28 职场文书
中青班党性分析材料
2014/02/16 职场文书
2014年生活老师工作总结
2014/12/23 职场文书
行政前台岗位职责
2015/04/16 职场文书
期中考试后的感想
2015/08/07 职场文书
工作简历的自我评价
2019/05/16 职场文书
各国货币符号大全
2022/02/17 杂记