Python 解析支付寶公鑰證書

因爲工做須要,咱們開發的 App 須要接入支付寶的支付功能。因而咱們着手瞭解 Alipay 相關的 api 文檔。
通過斟酌後,咱們選擇使用了一個第三方 SDK (https://github.com/fzlee/alipay)。這裏不得不吐槽一下官方的python SDK, 從包路徑到使用,都帶有強烈的 java 風格,沒有了 python 的簡約氣息。
儘管使用了 SDK,可是咱們發現,最新的支付寶都使用了公鑰證書來簽名。而官方 SDK 也只有 java 才支持公鑰證書方式,咱們使用的第三方 SDK 也沒有。 因而乎咱們不得不本身來實現公鑰證書的解析,可是網絡上關於自行實現簽名的內容比較少,僅有提供一些說明。
官方提供了自行實現簽名的過程 https://docs.open.alipay.com/291/106118。
其中比較關鍵的是從證書提取app_cert_snalipay_root_cert_sn兩個關鍵參數,這裏給出 Java 中的實現:php

/**
 * 從公鑰證書中提取公鑰序列號
 *
 * @param certPath 公鑰證書存放路徑,例如:/home/admin/cert.crt
 * @return 公鑰證書序列號
 * @throws AlipayApiException
 */
public static String getCertSN(String certPath) throws AlipayApiException {
    InputStream inputStream = null;
    try {
        inputStream = new FileInputStream(certPath);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes());
        String certSN = new BigInteger(1, md.digest()).toString(16);
        //BigInteger會把0省略掉,需補全至32位
        certSN = fillMD5(certSN);
        return certSN;

    } catch (NoSuchAlgorithmException e) {
        throw new AlipayApiException(e);
    } catch (IOException e) {
        throw new AlipayApiException(e);
    } catch (CertificateException e) {
        throw new AlipayApiException(e);
    } finally {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            throw new AlipayApiException(e);
        }
    }
}

/**
 * 獲取根證書序列號
 *
 * @param rootCertContent
 * @return
 */
public static String getRootCertSN(String rootCertContent) {
    String rootCertSN = null;
    try {
        X509Certificate[] x509Certificates = readPemCertChain(rootCertContent);
        MessageDigest md = MessageDigest.getInstance("MD5");
        for (X509Certificate c : x509Certificates) {
            if (c.getSigAlgOID().startsWith("1.2.840.113549.1.1")) {
                md.update((c.getIssuerX500Principal().getName() + c.getSerialNumber()).getBytes());
                String certSN = new BigInteger(1, md.digest()).toString(16);
                //BigInteger會把0省略掉,需補全至32位
                certSN = fillMD5(certSN);
                if (StringUtils.isEmpty(rootCertSN)) {
                    rootCertSN = certSN;
                } else {
                    rootCertSN = rootCertSN + "_" + certSN;
                }
            }

        }
    } catch (Exception e) {
        AlipayLogger.logBizError(("提取根證書失敗"));
    }
    return rootCertSN;

}

private static String fillMD5(String md5) {
    return md5.length() == 32 ? md5 : fillMD5("0" + md5);
}

這裏和官網所說的流程大概相同:html

  • 解析X.509證書文件,獲取證書籤發機構名稱(name)以及證書內置序列號(serialNumber)。
  • 將name與serialNumber拼接成字符串,再對該字符串作MD5計算。

第一步中解析X.509證書比較容易,在 python 實現中咱們使用了 openssl 來解析證書:java

cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)

可是在獲取 name 和 serialNumber 碰到了障礙。 在 Java 中咱們看到 md.update((c.getIssuerX500Principal().getName() + c.getSerialNumber()).getBytes()); 這一行能夠輕鬆提取 name 和 serialName。 惋惜的是,openssl 裏只有get_serial_number這樣的 API 來提取序列號,沒有像 java 裏 getIssuerX500Principal 來獲取他想要的機構名稱。 通過長時間的資料查詢和研究,從 https://sbing.vip/archives/2019-new-alipay-php-docking.html 這裏找到了線索:python

須要拼接成:CN=Ant Financial Certification Authority Class 2 R1,OU=Certification Authority,O=Ant Financial,C=CN

因而找到了解決方法:name = 'CN={},OU={},O={},C={}'.format(certIssue.CN, certIssue.OU, certIssue.O, certIssue.C)
第二步中的拼接和 MD5 校驗就比較簡單,使用 python 自帶的 hashlib 就能夠完成,而且比 Java 更簡潔。
最後一個問題來自於根證書,源碼顯示根證書包含多個證書信息,讀取文件的時候須要使用 split('\n\n') 來獲取證書字符串列表,再遍歷獲取證書 SN 信息。 還有源碼裏作了篩選if (c.getSigAlgOID().startsWith("1.2.840.113549.1.1"))Openssl裏也沒有這樣的 API 能夠調度。 我沒有選擇像它那樣解析出算法的 OID。我猜測這個就是爲了找到指定算法類型,因而我使用了別的方法代替:git

try:
    sigAlg = cert.get_signature_algorithm()
except ValueError:
    continue
if b'rsaEncryption' in sigAlg or b'RSAEncryption' in sigAlg:

以上是我對支付寶公鑰證書驗證的大體理解,最後的算法類型也是個人猜想,有問題能夠告訴我哦。這麼看起來不是特別困難的問題,可是在解決問題的過程當中的確花了不少時間,網絡上能提供的資料也只有支付寶官網的文檔和上面 的一個php實現的博客。解決完讓我豁然開朗,也但願還在炮坑的同窗能從中受益。
目前我是在alipay ( 我以爲作的還能夠 )基礎上加入了證書籤名。須要的朋友能夠直接下載使用該SDK。
完整 Demo 的地址:https://github.com/00Kai0/pyAliPay/blob/master/demo.py
原文地址:https://00kai0.github.io/alipay-cert/github

相關文章
相關標籤/搜索