如何使用 python 解码 SSL 证书?

Posted

技术标签:

【中文标题】如何使用 python 解码 SSL 证书?【英文标题】:How can I decode a SSL certificate using python? 【发布时间】:2013-05-29 18:02:48 【问题描述】:

如何使用 Python 解码 pem 编码 (base64) 证书?例如这里来自 github.com:

-----BEGIN CERTIFICATE-----
MIIHKjCCBhKgAwIBAgIQDnd2il0H8OV5WcoqnVCCtTANBgkqhkiG9w0BAQUFADBp
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBDQS0xMB4XDTExMDUyNzAwMDAwMFoXDTEzMDcyOTEyMDAwMFowgcoxHTAb
BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT
MRswGQYLKwYBBAGCNzwCAQITCkNhbGlmb3JuaWExETAPBgNVBAUTCEMzMjY4MTAy
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2Fu
IEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRo
dWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7dOJw11wcgnz
M08acnTZtlqVULtoYZ/3+x8Z4doEMa8VfBp/+XOvHeVDK1YJAEVpSujEW9/Cd1JR
GVvRK9k5ZTagMhkcQXP7MrI9n5jsglsLN2Q5LLcQg3LN8OokS/rZlC7DhRU5qTr2
iNr0J4mmlU+EojdOfCV4OsmDbQIXlXh9R6hVg+4TyBkaszzxX/47AuGF+xFmqwld
n0xD8MckXilyKM7UdWhPJHIprjko/N+NT02Dc3QMbxGbp91i3v/i6xfm/wy/wC0x
O9ZZovLdh0pIe20zERRNNJ8yOPbIGZ3xtj3FRu9RC4rGM+1IYcQdFxu9fLZn6TnP
pVKACvTqzQIDAQABo4IDajCCA2YwHwYDVR0jBBgwFoAUTFjLJfBBT1L0KMiBQ5um
qKDmkuUwHQYDVR0OBBYEFIfRjxlu5IdvU4x3kQdQ36O/VUcgMCUGA1UdEQQeMByC
CmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMIGBBggrBgEFBQcBAQR1MHMwJAYI
KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBLBggrBgEFBQcwAoY/
aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ0FDZXJ0cy9EaWdpQ2VydEhpZ2hBc3N1
cmFuY2VFVkNBLTEuY3J0MAwGA1UdEwEB/wQCMAAwYQYDVR0fBFowWDAqoCigJoYk
aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2V2MjAwOWEuY3JsMCqgKKAmhiRodHRw
Oi8vY3JsNC5kaWdpY2VydC5jb20vZXYyMDA5YS5jcmwwggHEBgNVHSAEggG7MIIB
tzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGln
aWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCC
AVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp
AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBw
AHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQ
AC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQBy
AHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0
ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy
AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBl
AG4AYwBlAC4wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGCWCGSAGG
+EIBAQQEAwIGwDAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBABRS
cR+GnW01Poa7ZhqLhZi5AEzLQrVG/AbnRDnI6FLYERQjs3KW6RSUni8AKPfVBEVA
AMb0V0JC3gmJlxENFFxrvQv3GKNfZwLzCThjv8ESnTC6jqVUdFlTZ6EbUFsm2v0T
flkXv0nvlH5FpP06STLwav+JjalhqaqblkbIHOAYHOb7gvQKq1KmyuhUItnbKj1a
InuA6gcF1PnH8FNZX7t3ft6TcEFOI8t4eXnELurXZioY99HFfOISeIKNHeyCngGi
5QK+eKG5WVjFTG9PpTG0SVtemB4uOPYZxDmiSvt5BbjyWeUmEnCtwOh1Ix8Y0Qvg
n2Xkw9dJh1tybLEvrG8=
-----END CERTIFICATE-----

根据ssl-shopper 应该是这样的:

Common Name: github.com
Subject Alternative Names: github.com, www.github.com
Organization: GitHub, Inc.
Locality: San Francisco
State: California
Country: US
Valid From: May 26, 2011
Valid To: July 29, 2013

如何使用 python 获取此明文?

【问题讨论】:

一定要用python吗? openssl cmdline 输出将为您执行此操作。 我想从 python 程序中的证书文件中即时获取一些信息。 您可以使用 asn1crypto、pyOpenSSL、M2Crypto、密码学、pyasn1 python 包。这是code example (in Russian) 【参考方案1】:

即使在最新版本中,Python 的标准库也不包含任何可以解码 X.509 证书的内容。但是,附加组件 cryptography 包确实支持这一点。引用example from the documentation:

>>> from cryptography import x509
>>> from cryptography.hazmat.backends import default_backend
>>> cert = x509.load_pem_x509_certificate(pem_data, default_backend())
>>> cert.serial_number
2

另一个可选的附加包是pyopenssl。这是围绕 OpenSSL C API 的一个薄包装,这意味着它可能做你想做的事,但预计要花几天时间在文档上撕下你的头发。

如果您无法安装 Python 附加包,但您有 openssl 命令行实用程序,

import subprocess
cert_txt = subprocess.check_output(["openssl", "x509", "-text", "-noout", 
                                    "-in", certificate])

应该产生与您在cert_txt 中从网络实用程序获得的内容大致相同的内容。

顺便说一句,直接进行 base64 解码的原因是二进制 gobbledygook 是因为这里有两层编码。 X.509 certificates 是 ASN.1 数据结构,序列化为 X.690 DER 格式,然后,由于 DER 是二进制格式,因此使用 base64 保护以方便文件传输。 (这方面的许多标准都是在 90 年代编写的,当时除了 7 位 ASCII 外,您无法可靠地发布任何东西。)

【讨论】:

上例中的 pem_data 需要是 bytes 对象,而不是 str 对象。 我的证书是一个字符串:pem_data = ssl.get_server_certificate((hostname, port)) 所以我必须将其转换为字节输入:cert = x509.load_pem_x509_certificate(str.encode(pem_data), default_backend())【参考方案2】:

您可以使用pyasn1pyasn1-modules 包来解析此类数据。例如:

from pyasn1_modules import pem, rfc2459
from pyasn1.codec.der import decoder

substrate = pem.readPemFromFile(open('cert.pem'))
cert = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())[0]
print(cert.prettyPrint())

其余部分请阅读 pyasn1 的文档。

【讨论】:

【参考方案3】:

注意事项

一切都依赖于 (!!!undocumented!!!) ssl._ssl._test_decode_cert (存在于 Python (3 / 2),不需要额外的模块 请查看[SO]: Can't receive peer certificate in Python client using OpenSSL's ssl.SSLContext() (@CristiFati's answer),它解决了更广泛的问题

关于问题中的证书(PEM):

将其保存在一个名为 q016899247.crt 的文件中(在脚本中 (code00.py)dir) 结束标记:("-----END CERTIFICATE----") 缺少连字符 (-强>) 结尾;在问题@VERSION #4 中更正。

code00.py

#!/usr/bin/env python3

import sys
import os
import ssl
import pprint


def main(*argv):
    cert_file_base_name = "q016899247.crt"
    cert_file_name = os.path.join(os.path.dirname(__file__), cert_file_base_name)
    try:
        cert_dict = ssl._ssl._test_decode_cert(cert_file_name)
    except Exception as e:
        print("Error decoding certificate: 0:".format(e))
    else:
        print("Certificate (0:s) data:\n".format(cert_file_base_name))
        pprint.pprint(cert_dict)


if __name__ == "__main__":
    print("Python 0:s 1:dbit on 2:s\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出

[cfati@CFATI-5510-0:e:\Work\Dev\***\q016899247]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32

Certificate (q016899247.crt) data:

'OCSP': ('http://ocsp.digicert.com',),
 'caIssuers': ('http://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt',),
 'crlDistributionPoints': ('http://crl3.digicert.com/ev2009a.crl',
                           'http://crl4.digicert.com/ev2009a.crl'),
 'issuer': ((('countryName', 'US'),),
            (('organizationName', 'DigiCert Inc'),),
            (('organizationalUnitName', 'www.digicert.com'),),
            (('commonName', 'DigiCert High Assurance EV CA-1'),)),
 'notAfter': 'Jul 29 12:00:00 2013 GMT',
 'notBefore': 'May 27 00:00:00 2011 GMT',
 'serialNumber': '0E77768A5D07F0E57959CA2A9D5082B5',
 'subject': ((('businessCategory', 'Private Organization'),),
             (('jurisdictionCountryName', 'US'),),
             (('jurisdictionStateOrProvinceName', 'California'),),
             (('serialNumber', 'C3268102'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'GitHub, Inc.'),),
             (('commonName', 'github.com'),)),
 'subjectAltName': (('DNS', 'github.com'), ('DNS', 'www.github.com')),
 'version': 3

Done.

【讨论】:

调用带下划线前缀的函数通常是一个禁忌,它们仅供内部使用,并且不能保证函数签名在不同版本之间保持相同。也就是说,干得好! @Twirrim:谢谢,我知道我上次编辑帖子时忘记了。我改写了它,以便更好地反映现实。 在不允许加密模块的环境中将 SSL 证书解码为文本的好方法。虽然不确定调用内部 SSL 函数。 @AbySamRoss:ssl 是一个密码学模块(它只是 Python 标准库的一部分)。并且它将实际工作委托给 OpenSSL(或其他此类加密提供者)。所有其他模块都这样做。【参考方案4】:

此代码转储证书文件内容:

import OpenSSL.crypto

cert = OpenSSL.crypto.load_certificate(
      OpenSSL.crypto.FILETYPE_PEM,
      open('/path/to/cert/file.crt').read()
)

print OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, cert)

试一试。

【讨论】:

【参考方案5】:

这允许从 SSL 证书中提取某些值:

from cryptography import x509
from cryptography.hazmat.backends import default_backend

hostname = 'google.com.com'
port = 443
cert = ssl.get_server_certificate((hostname, port))
certDecoded = x509.load_pem_x509_certificate(str.encode(cert), 
default_backend())
print(certDecoded.issuer)
print(certDecoded.subject)
print(certDecoded.not_valid_after)
print(certDecoded.not_valid_before)

【讨论】:

【参考方案6】:

You can download the code from here。它纯粹是从 .pem 和 .cer 类型的证书中提取数据。

否则,可以使用以下sn-p解码pem证书:

    #import pem & pyOpenSSL module

    certs = pem.parse_file(file_path)  # using pem module
    for pem_certificates in certs:
        strcert = str(pem_certificates)
        loadCert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,strcert)
        print(loadCert.get_issuer())```

 

【讨论】:

【参考方案7】:

还有另一种使用_test_decode_certificate 而不使用内部实现的方法。不过,它以不同的方式有点 hacky

import ssl

ctx = ssl.SSLContext()

# The filepath to your PEM-encoded x509 cert
ctx.load_verify_locations("369fa1ef21f5476c02814c637d83f71d851f867348eef21d1eb0058671d0e5a6.crt")

certificate_details = ctx.get_ca_certs()

在底层,这是_test_decode_certificate 使用的_decode_certificate 函数的另一个入口点。

您可以在 CPython 源代码 https://github.com/python/cpython/blob/main/Modules/_ssl.c#L4578 中看到它是如何工作的

【讨论】:

【参考方案8】:

我不确定你是如何收到它的,但另一种安装它的简单方法是将其编写为二进制文件,然后使用 os 运行它

import os

cert= function_gives_binary_cert()
with open('RecvdCert.der','wb') as file:
     file.write(cert)

os.startfile('RecvdCert.der')

小心运行从未知来源接收到的二进制文件。只想解码然后使用其他答案中提到的 OpenSSL。

【讨论】:

以上是关于如何使用 python 解码 SSL 证书?的主要内容,如果未能解决你的问题,请参考以下文章

禁用SSL证书检查双绞线代理

如何让 Python 请求信任自签名 SSL 证书?

如何连接 Python IMAP4_SSL 和自签名服务器 SSL 证书?

如何把一个web集群由HTTP转换为HTTPS

如何把一个web集群由HTTP转换为HTTPS

如何验证在Python SSL证书