详解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 02 PHP
解析linux下安装memcacheq(mcq)全过程笔记
Jun 27 PHP
PHP修改session_id示例代码
Jan 08 PHP
新浪SAE云平台下使用codeigniter的数据库配置
Jun 12 PHP
PHP中提问频率最高的11个面试题和答案
Sep 02 PHP
Laravel框架路由配置总结、设置技巧大全
Sep 03 PHP
PHP动态页生成静态页的3种常用方法
Nov 13 PHP
两种php去除二维数组的重复项方法
Nov 04 PHP
PHP构造函数与析构函数用法示例
Sep 28 PHP
ThinkPHP实现生成和校验验证码功能
Apr 28 PHP
ThinkPHP3.2.3框架实现执行原生SQL语句的方法示例
Apr 03 PHP
php7连接MySQL实现简易查询程序的方法
Oct 13 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 PDO函数库详解
2010/04/27 PHP
php使用date和strtotime函数输出指定日期的方法
2014/11/14 PHP
分享php分页的功能模块
2015/06/16 PHP
JS 面向对象的5钟写法
2009/07/31 Javascript
jQuery入门知识简介
2010/03/04 Javascript
DWZ table的原生分页浅谈
2013/03/01 Javascript
使用JQuery 加载页面时调用JS的实现方法
2016/05/30 Javascript
Bootstrap table两种分页示例
2016/12/23 Javascript
关于在vue-cli中使用微信自动登录和分享的实例
2017/06/22 Javascript
node.js 核心http模块,起一个服务器,返回一个页面的实例
2017/09/11 Javascript
vue-cli axios请求方式及跨域处理问题
2018/03/28 Javascript
Vue实现导出excel表格功能
2018/03/30 Javascript
MVVM框架下实现分页功能示例
2018/06/14 Javascript
vue+iview 兼容IE11浏览器的实现方法
2019/01/07 Javascript
浅谈Python 对象内存占用
2016/07/15 Python
Python中str.format()详解
2017/03/12 Python
Python使用getpass库读取密码的示例
2017/10/10 Python
Python实现简单的语音识别系统
2017/12/13 Python
python 输出上个月的月末日期实例
2018/04/11 Python
Python实现堡垒机模式下远程命令执行操作示例
2019/05/09 Python
python 接口实现 供第三方调用的例子
2019/08/13 Python
opencv实现简单人脸识别
2021/02/19 Python
python flask搭建web应用教程
2019/11/19 Python
利用Python将多张图片合成视频的实现
2020/11/23 Python
Python+unittest+requests+excel实现接口自动化测试框架
2020/12/23 Python
python实现无边框进度条的实例代码
2020/12/30 Python
建筑工程自我鉴定
2013/10/18 职场文书
行政经理岗位职责
2013/11/09 职场文书
办护照工作证明范本
2014/01/14 职场文书
不服从公司安排检讨书
2014/09/24 职场文书
大学生旷课检讨书1000字
2015/02/19 职场文书
使用canvas仿Echarts实现金字塔图的实例代码
2021/11/11 HTML / CSS
Spring Boot 实现 WebSocket
2022/04/30 Java/Android
使用 Koa + TS + ESLlint 搭建node服务器的过程详解
2022/05/30 NodeJs
Pandas实现批量拆分与合并Excel的示例代码
2022/05/30 Python
华为HarmonyOS3.0强在哪? 看看鸿蒙3.0这7个小功能
2023/01/09 数码科技