PHP版本兼容之openssl調用參數

背景與問題解決方式

老項目重構支付寶部分代碼整合支付寶新的sdk時發現驗籤老是失敗,才發現是open_verify最後的參數傳輸問題。而open_sign一樣如此。本文主要說明open_verify的解決方式和代碼解析。而問題的解決方式也是修改最後的加密類型參數,解決方式代碼以下:php

// 將最後的常量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 ] )
複製代碼

參數註釋bash

  • data 之前用來生成簽名的數據字符串。php7

  • signature 原始二進制字符串,經過openssl_sign()或相似的函數生成。函數

  • pub_key_idui

    • 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方法打印參數以下,而這些字符串也是對應加密方式的摘要信息,後文源碼中可能會看的對函數調用稍微明白那麼一丟丟。spa

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擴展的問題上。那下面來看看對應的源碼去發現問題出如今哪吧。code

函數源碼

openssl_verify函數源碼

openssl_verify源碼中有這樣一段,若是參數method爲string類型的時候,調用openssl庫的EVP_get_digestbyname方法,在網上查看了下此方法的做用,主要是根據摘要信息返回 EVP_MD結構,而EVP_get_digestbyname方法因爲是openssl庫源代碼而且對C語言知之甚少,熊某就沒去查看, 只是瞭解php代碼調用背後的一些處理邏輯,有興趣的能夠看看openssl庫的代碼實現。ip

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少了幾個條件,在此也但願你們發現問題的時候,能夠先去解決問題,而後有興趣的話能夠去查看源代碼分析下問題所致使的緣由。

相關文章
相關標籤/搜索