如何使用 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】:
您可以使用pyasn1
和pyasn1-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 证书?的主要内容,如果未能解决你的问题,请参考以下文章