当前位置:首页 > PHP教程 > php高级应用 > 列表

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

发布:smiling 来源: PHP粉丝网  添加日期:2021-10-19 19:32:13 浏览: 评论:0 

背景与问题解决方式

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

  1. // 将最后的常量OPENSSL_ALGO_SHA256修改成字符串 
  2. openssl_verify($database64_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方法打印参数如下,而这些字符串也是对应加密方式的摘要信息,后文源码中可能会看的对函数调用稍微明白那么一丢丢。

  1. Array 
  2. [0] => DSA 
  3. [1] => DSA-SHA 
  4. [2] => DSA-SHA1 
  5. [3] => DSA-SHA1-old 
  6. [4] => DSS1 
  7. [5] => GOST 28147-89 MAC 
  8. [6] => GOST R 34.11-94 
  9. [7] => MD4 
  10. [8] => MD5 
  11. [9] => MDC2 
  12. [10] => RIPEMD160 
  13. [11] => RSA-MD4 
  14. [12] => RSA-MD5 
  15. [13] => RSA-MDC2 
  16. [14] => RSA-RIPEMD160 
  17. [15] => RSA-SHA 
  18. [16] => RSA-SHA1 
  19. [17] => RSA-SHA1-2 
  20. [18] => RSA-SHA224 
  21. [19] => RSA-SHA256 
  22. [20] => RSA-SHA384 
  23. [21] => RSA-SHA512 
  24. [22] => SHA 
  25. [23] => SHA1 
  26. [24] => SHA224 
  27. [25] => SHA256 
  28. [26] => SHA384 
  29. [27] => SHA512 
  30. [28] => dsaEncryption 
  31. [29] => dsaWithSHA 
  32. [30] => dsaWithSHA1 
  33. [31] => dss1 
  34. [32] => ecdsa-with-SHA1 
  35. [33] => gost-mac 
  36. [34] => md4 
  37. [35] => md4WithRSAEncryption 
  38. [36] => md5 
  39. [37] => md5WithRSAEncryption 
  40. [38] => md_gost94 
  41. [39] => mdc2 
  42. [40] => mdc2WithRSA 
  43. [41] => ripemd 
  44. [42] => ripemd160 
  45. [43] => ripemd160WithRSA 
  46. [44] => rmd160 
  47. [45] => sha 
  48. [46] => sha1 
  49. [47] => sha1WithRSAEncryption 
  50. [48] => sha224 
  51. [49] => sha224WithRSAEncryption 
  52. [50] => sha256 
  53. [51] => sha256WithRSAEncryption 
  54. [52] => sha384 
  55. [53] => sha384WithRSAEncryption 
  56. [54] => sha512 
  57. [55] => sha512WithRSAEncryption 
  58. [56] => shaWithRSAEncryption 
  59. [57] => ssl2-md5 
  60. [58] => ssl3-md5 
  61. [59] => ssl3-sha1 
  62. [60] => whirlpool 

由此也可看出函数是兼容两种模式的,但是为什么php版本会有兼容问题么?在openssl库版本是一致的情况下,接下来的原因应该只遗留在php扩展的问题上。那下面来看看对应的源码去发现问题出现在哪吧。

函数源码

openssl_verify函数源码

openssl_verify源码中有这样一段,如果参数method为string类型的时候,调用openssl库的EVP_get_digestbyname方法,在网上查看了下此方法的作用,主要是根据摘要信息返回

EVP_MD结构,而EVP_get_digestbyname方法由于是openssl库源代码并且对C语言知之甚少,熊某就没去查看,只是了解php代码调用背后的一些处理逻辑,有兴趣的可以看看openssl库的代码实现。

  1. if (method == NULL || Z_TYPE_P(method) == IS_LONG) { 
  2.     if (method != NULL) { 
  3.       signature_algo = Z_LVAL_P(method); 
  4.     } 
  5.     mdtype = php_openssl_get_evp_md_from_algo(signature_algo); 
  6.   } else if (Z_TYPE_P(method) == IS_STRING) { 
  7.     mdtype = EVP_get_digestbyname(Z_STRVAL_P(method)); 
  8.   } else { 
  9.     php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); 
  10.     RETURN_FALSE; 
  11.   } 

原来是枚举值的问题?

一开始本人以为php5.3版本会是method参数类型的限制,一看源代码才发现,openssl_verify函数的实现逻辑是一致的,都是检测method参数类型,那么问题就不出现在参数类型上,然后我查看了参数为long类型是所调用的php_openssl_get_evp_md_from_algo函数,果然发现了问题所在。源码如下:

php5.3.27

  1. static EVP_MD * php_openssl_get_evp_md_from_algo(long algo) { /* {{{ */ 
  2.   EVP_MD *mdtype; 
  3.  
  4.   switch (algo) { 
  5.     case OPENSSL_ALGO_SHA1: 
  6.       mdtype = (EVP_MD *) EVP_sha1(); 
  7.       break
  8.     case OPENSSL_ALGO_MD5: 
  9.       mdtype = (EVP_MD *) EVP_md5(); 
  10.       break
  11.     case OPENSSL_ALGO_MD4: 
  12.       mdtype = (EVP_MD *) EVP_md4(); 
  13.       break
  14. #ifdef HAVE_OPENSSL_MD2_H 
  15.     case OPENSSL_ALGO_MD2: 
  16.       mdtype = (EVP_MD *) EVP_md2(); 
  17.       break
  18. #endif 
  19.     case OPENSSL_ALGO_DSS1: 
  20.       mdtype = (EVP_MD *) EVP_dss1(); 
  21.       break
  22.     default
  23.       return NULL; 
  24.       break
  25.   } 
  26.   return mdtype; 

php7.1.18

  1. static EVP_MD * php_openssl_get_evp_md_from_algo(zend_long algo) { /* {{{ */ 
  2.   EVP_MD *mdtype; 
  3.  
  4.   switch (algo) { 
  5.     case OPENSSL_ALGO_SHA1: 
  6.       mdtype = (EVP_MD *) EVP_sha1(); 
  7.       break
  8.     case OPENSSL_ALGO_MD5: 
  9.       mdtype = (EVP_MD *) EVP_md5(); 
  10.       break
  11.     case OPENSSL_ALGO_MD4: 
  12.       mdtype = (EVP_MD *) EVP_md4(); 
  13.       break
  14. #ifdef HAVE_OPENSSL_MD2_H 
  15.     case OPENSSL_ALGO_MD2: 
  16.       mdtype = (EVP_MD *) EVP_md2(); 
  17.       break
  18. #endif 
  19. #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER) 
  20.     case OPENSSL_ALGO_DSS1: 
  21.       mdtype = (EVP_MD *) EVP_dss1(); 
  22.       break
  23. #endif 
  24.     case OPENSSL_ALGO_SHA224: 
  25.       mdtype = (EVP_MD *) EVP_sha224(); 
  26.       break
  27.     case OPENSSL_ALGO_SHA256: 
  28.       mdtype = (EVP_MD *) EVP_sha256(); 
  29.       break
  30.     case OPENSSL_ALGO_SHA384: 
  31.       mdtype = (EVP_MD *) EVP_sha384(); 
  32.       break
  33.     case OPENSSL_ALGO_SHA512: 
  34.       mdtype = (EVP_MD *) EVP_sha512(); 
  35.       break
  36.     case OPENSSL_ALGO_RMD160: 
  37.       mdtype = (EVP_MD *) EVP_ripemd160(); 
  38.       break
  39.     default
  40.       return NULL; 
  41.       break
  42.   } 
  43.   return mdtype; 

由上面源代码可以很清晰的发现问题所在,随着php版本的升级,其所在的openssl扩展对应的调用条件也增加了很多,最后导致上述问题的源码也只是switch…case少了几个条件,在此也希望大家发现问题的时候,可以先去解决问题,然后有兴趣的话可以去查看源代码分析下问题所导致的原因。

Tags: PHP版本兼容 openssl

分享到: