如何通过PyOpenSSL获取证书中的SAN(Subject Alternative Names)列表
Hey there! I’ve dealt with this exact problem before when working with PyOpenSSL—its official docs can be a bit sparse on this specific detail. Let me walk you through two reliable ways to extract the Subject Alternative Names (SAN) from a certificate.
方法1:直接用PyOpenSSL解析X509扩展字段
SANs are stored in an X509v3 extension with the OID 2.5.29.17. You can load the certificate, iterate through all its extensions, find this specific one, and parse its content. Here’s a working example:
from OpenSSL import crypto def get_san_list(cert_path): # Load the certificate file (PEM format) with open(cert_path, 'rb') as cert_file: cert_data = cert_file.read() cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) san_entries = [] # Loop through all extensions to find the SAN one for ext in cert.get_extensions(): if ext.get_object().get_oid() == "2.5.29.17": # The extension's string output is formatted like "DNS:example.com, DNS:www.example.com" san_string = ext.__str__() # Split and clean up the entries for entry in san_string.split(', '): if entry.startswith('DNS:'): san_entries.append(entry[4:]) # Optional: Handle other SAN types like IP addresses elif entry.startswith('IP Address:'): san_entries.append(entry[11:]) return san_entries
A quick note: This method relies on parsing the string representation of the extension, which works for most standard certificates. If you’re dealing with non-standard or malformed extensions, you might want to use an ASN.1 parsing library like pyasn1 to decode the raw extension data instead of splitting strings.
方法2:结合cryptography库(更稳健)
PyOpenSSL plays nicely with the cryptography library, which has native, well-tested support for parsing SANs. This approach avoids string parsing entirely and is more reliable for edge cases. Here’s how to do it:
from OpenSSL import crypto from cryptography import x509 from cryptography.hazmat.backends import default_backend def get_san_list(cert_path): # Load the certificate with PyOpenSSL with open(cert_path, 'rb') as cert_file: cert_data = cert_file.read() openssl_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) # Convert the PyOpenSSL certificate to a cryptography-compatible format cert_der = crypto.dump_certificate(crypto.FILETYPE_ASN1, openssl_cert) cryptography_cert = x509.load_der_x509_certificate(cert_der, default_backend()) san_entries = [] try: # Fetch the SAN extension directly san_extension = cryptography_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) # Get all DNS names (add other types like IPAddress if needed) san_entries = san_extension.value.get_values_for_type(x509.DNSName) # Uncomment below to include IP addresses too # san_entries.extend(san_extension.value.get_values_for_type(x509.IPAddress)) except x509.ExtensionNotFound: # Handle cases where the certificate has no SAN extension pass return san_entries
Bonus Tips
- If your certificate is in DER format instead of PEM, replace
crypto.FILETYPE_PEMwithcrypto.FILETYPE_ASN1when loading. - Make sure you have the required libraries installed: run
pip install pyopenssl cryptographyif you haven’t already. - Always handle the
ExtensionNotFoundexception—some older certificates don’t include SAN extensions at all.
内容的提问来源于stack exchange,提问作者Elon Salfati

