ssl — TLS/SSL 套接字对象包装器

源代码: Lib/ssl.py


本模块提供了对传输层安全(通常称为“安全套接字层”)加密和对等身份验证功能的访问,用于客户端和服务器端的网络套接字。本模块使用 OpenSSL 库。只要在平台上安装了 OpenSSL,它就可在所有现代 Unix 系统、Windows、macOS 以及可能的其他平台上使用。

备注

一些行为可能取决于平台,因为会调用操作系统套接字 API。安装的 OpenSSL 版本也可能导致行为差异。例如,TLSv1.3 随 OpenSSL 版本 1.1.1 一起提供。

警告

请在阅读 安全注意事项 之前,请勿使用本模块。这样做可能会产生虚假的安全感,因为 ssl 模块的默认设置不一定适合您的应用程序。

可用性:非 WASI。

此模块在 WebAssembly 上不起作用或不可用。有关更多信息,请参阅 WebAssembly 平台

本节介绍 ssl 模块中的对象和函数;有关 TLS、SSL 和证书的更一般信息,请参阅底部的“另请参阅”部分中的文档。

本模块提供了一个类 ssl.SSLSocket,它继承自 socket.socket 类型,并提供了一个类似套接字的包装器,该包装器还可以对通过套接字传输的数据进行 SSL 加密和解密。它支持其他方法,例如 getpeercert()(用于检索连接另一方的证书)、cipher()(用于检索安全连接正在使用的密码套件)或 get_verified_chain()get_unverified_chain()(用于检索证书链)。

对于更复杂的应用程序,ssl.SSLContext 类有助于管理设置和证书,然后可以通过 SSLContext.wrap_socket() 方法创建的 SSL 套接字继承这些设置。

3.5.3 版本更改: 更新以支持与 OpenSSL 1.1.0 链接

3.6 版本更改: OpenSSL 0.9.8、1.0.0 和 1.0.1 已弃用,不再支持。将来,ssl 模块将至少需要 OpenSSL 1.0.2 或 1.1.0。

3.10 版本更改: 已实现 PEP 644。ssl 模块需要 OpenSSL 1.1.1 或更新版本。

使用已弃用的常量和函数会导致弃用警告。

函数、常量和异常

套接字创建

SSLSocket 的实例必须使用 SSLContext.wrap_socket() 方法创建。辅助函数 create_default_context() 返回一个新的上下文,并具有安全默认设置。

使用默认上下文和 IPv4/IPv6 双栈的客户端套接字示例

import socket
import ssl

hostname = 'www.python.org'
context = ssl.create_default_context()

with socket.create_connection((hostname, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

使用自定义上下文和 IPv4 的客户端套接字示例

hostname = 'www.python.org'
# PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('path/to/cabundle.pem')

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

在 localhost IPv4 上监听的服务器套接字示例

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('/path/to/certchain.pem', '/path/to/private.key')

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    sock.bind(('127.0.0.1', 8443))
    sock.listen(5)
    with context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()
        ...

上下文创建

一个方便的函数,用于为常见用途创建 SSLContext 对象。

ssl.create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, capath=None, cadata=None)

返回一个新的 SSLContext 对象,并具有给定 purpose 的默认设置。这些设置由 ssl 模块选择,通常代表比直接调用 SSLContext 构造函数更高的安全级别。

cafilecapathcadata 代表用于证书验证的可选 CA 证书,与 SSLContext.load_verify_locations() 中的 cafile 参数格式相同。如果所有三个参数都是 None,则此函数可以选择信任系统默认的 CA 证书。

设置包括:PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVEROP_NO_SSLv2,以及 OP_NO_SSLv3,并带有高加密强度的密码套件,不含 RC4 且不含未经身份验证的密码套件。将 SERVER_AUTH 作为 purpose 传递会设置 verify_modeCERT_REQUIRED,并加载 CA 证书(当 cafilecapathcadata 中至少有一个给出时)或使用 SSLContext.load_default_certs() 加载默认 CA 证书。

keylog_filename 受支持且环境变量 SSLKEYLOGFILE 已设置时,create_default_context() 会启用密钥记录。

此上下文的默认设置包括 VERIFY_X509_PARTIAL_CHAINVERIFY_X509_STRICT。这些设置使底层的 OpenSSL 实现更符合 RFC 5280 的要求,但可能会与旧的 X.509 证书存在一些不兼容性。

备注

协议、选项、密码套件和其他设置可能会随时更改为更严格的值,恕不另行通知。这些值在兼容性和安全性之间取得了合理的平衡。

如果您的应用程序需要特定的设置,您应该创建 SSLContext 并自行应用这些设置。

备注

如果您发现当某些旧客户端或服务器尝试使用此函数创建的 SSLContext 连接时出现“协议或密码套件不匹配”的错误,可能是因为它们仅支持 SSL3.0,而此函数通过 OP_NO_SSLv3 禁用了它。SSL3.0 被广泛认为已经 完全失效。如果您仍希望继续使用此函数但仍允许 SSL 3.0 连接,您可以使用以下方法重新启用它们:

ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
ctx.options &= ~ssl.OP_NO_SSLv3

备注

此上下文默认启用 VERIFY_X509_STRICT,这可能会拒绝底层 OpenSSL 实现原本会接受的 RFC 5280 之前的或格式错误的证书。虽然不建议禁用此设置,但您可以使用以下方法进行禁用:

ctx = ssl.create_default_context()
ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT

在 3.4 版本加入。

3.4.4 版本更改: RC4 已从默认密码字符串中移除。

3.6 版本更改: ChaCha20/Poly1305 已添加到默认密码字符串。

3DES 已从默认密码字符串中移除。

3.8 版本更改: 添加了对向 SSLKEYLOGFILE 记录密钥的支持。

3.10 版本更改: 上下文现在使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 协议,而不是通用的 PROTOCOL_TLS

3.13 版本更改: 上下文现在在其默认验证标志中使用 VERIFY_X509_PARTIAL_CHAINVERIFY_X509_STRICT

异常

exception ssl.SSLError

当底层 SSL 实现(当前由 OpenSSL 库提供)发生错误时引发。这表示叠加在底层网络连接之上的更高级别的加密和身份验证层存在某种问题。此错误是 OSError 的子类。SSLError 实例的错误代码和消息由 OpenSSL 库提供。

3.3 版本更改: SSLError 曾是 socket.error 的子类。

library

一个字符串助记符,指示发生错误的 OpenSSL 子模块,例如 SSLPEMX509。可能值的范围取决于 OpenSSL 版本。

在 3.3 版本加入。

reason

一个字符串助记符,指示发生此错误的原因,例如 CERTIFICATE_VERIFY_FAILED。可能值的范围取决于 OpenSSL 版本。

在 3.3 版本加入。

exception ssl.SSLZeroReturnError

当尝试读写时 SSL 连接已干净关闭,则引发 SSLError 的子类。请注意,这并不意味着底层传输(读取的 TCP)已关闭。

在 3.3 版本加入。

exception ssl.SSLWantReadError

非阻塞 SSL 套接字 在尝试读写数据时引发的 SSLError 的子类,但需要从底层 TCP 传输接收更多数据才能完成请求。

在 3.3 版本加入。

exception ssl.SSLWantWriteError

非阻塞 SSL 套接字 在尝试读写数据时引发的 SSLError 的子类,但需要向底层 TCP 传输发送更多数据才能完成请求。

在 3.3 版本加入。

exception ssl.SSLSyscallError

SSLError 的子类,在尝试在 SSL 套接字上执行操作时遇到系统错误时引发。不幸的是,没有简单的方法可以检查原始 errno 号码。

在 3.3 版本加入。

exception ssl.SSLEOFError

SSLError 的子类,当 SSL 连接被突然终止时引发。通常,遇到此错误时不应尝试重用底层传输。

在 3.3 版本加入。

exception ssl.SSLCertVerificationError

当证书验证失败时,引发 SSLError 的子类。

在 3.7 版本加入。

verify_code

表示验证错误的数字错误代码。

verify_message

表示验证错误的易读字符串。

exception ssl.CertificateError

SSLCertVerificationError 的别名。

3.7 版本更改: 此异常现在是 SSLCertVerificationError 的别名。

随机数生成

ssl.RAND_bytes(num, /)

返回 num 个加密安全的伪随机字节。如果 PRNG 未使用足够的数据进行播种,或者当前 RAND 方法不支持该操作,则引发 SSLErrorRAND_status() 可用于检查 PRNG 的状态,而 RAND_add() 可用于播种 PRNG。

对于几乎所有应用程序,os.urandom() 是更好的选择。

阅读维基百科文章 加密安全的伪随机数生成器 (CSPRNG),以了解加密强生成器的要求。

在 3.3 版本加入。

ssl.RAND_status()

如果 SSL 伪随机数生成器已使用“足够”的随机性进行了播种,则返回 True,否则返回 False。您可以使用 ssl.RAND_egd()ssl.RAND_add() 来增加伪随机数生成器的随机性。

ssl.RAND_add(bytes, entropy, /)

将给定的 bytes 混合到 SSL 伪随机数生成器中。参数 entropy(一个浮点数)是字符串中熵的下限(因此您始终可以使用 0.0)。有关熵源的更多信息,请参阅 RFC 1750

3.5 版本中修改: 现在接受可写 bytes-like object

证书处理

ssl.cert_time_to_seconds(cert_time)

给定证书中的“notBefore”或“notAfter”日期的 cert_time 字符串(采用 "%b %d %H:%M:%S %Y %Z" strptime 格式(C 语言环境)),返回自 Epoch 以来的秒数。

这是一个例子

>>> import ssl
>>> timestamp = ssl.cert_time_to_seconds("Jan  5 09:34:43 2018 GMT")
>>> timestamp
1515144883
>>> from datetime import datetime
>>> print(datetime.utcfromtimestamp(timestamp))
2018-01-05 09:34:43

“notBefore”或“notAfter”日期必须使用 GMT(RFC 5280)。

3.5 版本更改: 将输入时间解释为 UTC 时间,如输入字符串中的“GMT”时区所示。以前使用的是本地时区。返回一个整数(输入格式中没有秒的小数部分)

ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None[, timeout])

给定一个 SSL 保护的服务器地址 addr,其格式为 (hostname, port-number) 对,则获取服务器的证书,并将其作为 PEM 编码的字符串返回。如果指定了 ssl_version,则使用该版本的 SSL 协议尝试连接到服务器。如果指定了 ca_certs,它应该是一个包含根证书列表的文件,格式与 SSLContext.load_verify_locations() 中的 cafile 参数相同。此调用将尝试根据该根证书集验证服务器证书,如果验证尝试失败,则会引发错误。可以使用 timeout 参数指定超时。

3.3 版本更改: 此函数现在支持 IPv6。

3.5 版本更改: 为了与现代服务器最大程度兼容,默认的 ssl_versionPROTOCOL_SSLv3 更改为 PROTOCOL_TLS

3.10 版本更改: 添加了 timeout 参数。

ssl.DER_cert_to_PEM_cert(der_cert_bytes)

给定一个 DER 编码的字节串格式的证书,返回该证书的 PEM 编码字符串版本。

ssl.PEM_cert_to_DER_cert(pem_cert_string)

给定一个 ASCII PEM 字符串格式的证书,返回该证书的 DER 编码字节序列。

ssl.get_default_verify_paths()

返回一个命名元组,其中包含 OpenSSL 的默认 cafile 和 capath 的路径。这些路径与 SSLContext.set_default_verify_paths() 使用的路径相同。返回值是一个名为 DefaultVerifyPaths命名元组

  • cafile - cafile 的解析路径,如果文件不存在则为 None

  • capath - capath 的解析路径,如果目录不存在则为 None

  • openssl_cafile_env - 指向 cafile 的 OpenSSL 环境变量键,

  • openssl_cafile - cafile 的硬编码路径,

  • openssl_capath_env - 指向 capath 的 OpenSSL 环境变量键,

  • openssl_capath - capath 目录的硬编码路径

在 3.4 版本加入。

ssl.enum_certificates(store_name)

从 Windows 的系统证书存储中检索证书。store_name 可以是 CAROOTMY。Windows 也可能提供其他证书存储。

该函数返回 (cert_bytes, encoding_type, trust) 元组列表。encoding_type 指定 cert_bytes 的编码。对于 X.509 ASN.1 数据,它是 x509_asn,对于 PKCS#7 ASN.1 数据,它是 pkcs_7_asn。Trust 指定证书的目的,作为 OID 集合或确切地 True(如果证书对所有目的都可信)。

示例

>>> ssl.enum_certificates("CA")
[(b'data...', 'x509_asn', {'1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2'}),
 (b'data...', 'x509_asn', True)]

可用性: Windows。

在 3.4 版本加入。

ssl.enum_crls(store_name)

从 Windows 的系统证书存储中检索 CRL。store_name 可以是 CAROOTMY。Windows 也可能提供其他证书存储。

该函数返回 (cert_bytes, encoding_type, trust) 元组列表。encoding_type 指定 cert_bytes 的编码。对于 X.509 ASN.1 数据,它是 x509_asn,对于 PKCS#7 ASN.1 数据,它是 pkcs_7_asn

可用性: Windows。

在 3.4 版本加入。

常量

所有常量现在都是 enum.IntEnumenum.IntFlag 集合。

在 3.6 版本加入。

ssl.CERT_NONE

SSLContext.verify_mode 的可能值。除了 PROTOCOL_TLS_CLIENT 之外,它是默认模式。对于客户端套接字,几乎所有证书都会被接受。验证错误,如不受信任或过期的证书,将被忽略,不会中止 TLS/SSL 握手。

在服务器模式下,不会向客户端请求证书,因此客户端不会发送任何证书来进行客户端证书身份验证。

请参阅下文 安全注意事项 的讨论。

ssl.CERT_OPTIONAL

SSLContext.verify_mode 的可能值。在客户端模式下,CERT_OPTIONAL 的含义与 CERT_REQUIRED 相同。建议在客户端套接字上改用 CERT_REQUIRED

在服务器模式下,会向客户端发送客户端证书请求。客户端可以选择忽略该请求或发送证书以执行 TLS 客户端证书身份验证。如果客户端选择发送证书,则会对其进行验证。任何验证错误将立即中止 TLS 握手。

使用此设置需要在 SSLContext.load_verify_locations() 中提供有效的 CA 证书集。

ssl.CERT_REQUIRED

SSLContext.verify_mode 的可能值。在此模式下,套接字连接的另一方必须提供证书;如果没有提供证书,或者其验证失败,则会引发 SSLError。此模式 **不** 足以在客户端模式下验证证书,因为它不匹配主机名。必须同时启用 check_hostname 才能验证证书的真实性。PROTOCOL_TLS_CLIENT 使用 CERT_REQUIRED 并默认启用 check_hostname

使用服务器套接字时,此模式提供强制性的 TLS 客户端证书身份验证。会向客户端发送客户端证书请求,客户端必须提供一个有效且受信任的证书。

使用此设置需要在 SSLContext.load_verify_locations() 中提供有效的 CA 证书集。

class ssl.VerifyMode

CERT_* 常量的 enum.IntEnum 集合。

在 3.6 版本加入。

ssl.VERIFY_DEFAULT

SSLContext.verify_flags 的可能值。在此模式下,不检查证书吊销列表 (CRL)。默认情况下,OpenSSL 既不要求也不验证 CRL。

在 3.4 版本加入。

ssl.VERIFY_CRL_CHECK_LEAF

SSLContext.verify_flags 的可能值。在此模式下,只检查对等证书,而不检查任何中间 CA 证书。该模式需要由对等证书的颁发者(其直接上级 CA)签名的有效 CRL。如果未使用 SSLContext.load_verify_locations 加载正确的 CRL,验证将失败。

在 3.4 版本加入。

ssl.VERIFY_CRL_CHECK_CHAIN

SSLContext.verify_flags 的可能值。在此模式下,将检查对等证书链中所有证书的 CRL。

在 3.4 版本加入。

ssl.VERIFY_X509_STRICT

SSLContext.verify_flags 的可能值,用于禁用对损坏的 X.509 证书的错误修复。

在 3.4 版本加入。

ssl.VERIFY_ALLOW_PROXY_CERTS

SSLContext.verify_flags 的可能值,用于启用代理证书验证。

在 3.10 版本加入。

ssl.VERIFY_X509_TRUSTED_FIRST

SSLContext.verify_flags 的可能值。它指示 OpenSSL 在构建信任链以验证证书时优先使用受信任的证书。此标志默认启用。

添加于 3.4.4 版本。

ssl.VERIFY_X509_PARTIAL_CHAIN

SSLContext.verify_flags 的可能值。它指示 OpenSSL 接受信任存储中的中间 CA,如同自签名根 CA 证书一样。这使得无需信任中间 CA 的上级根 CA 即可信任由中间 CA 颁发的证书。

在 3.10 版本加入。

class ssl.VerifyFlags

VERIFY_* 常量的 enum.IntFlag 集合。

在 3.6 版本加入。

ssl.PROTOCOL_TLS

选择客户端和服务器都支持的最高协议版本。尽管名称如此,此选项可以同时选择“SSL”和“TLS”协议。

在 3.6 版本加入。

3.10 版本已弃用: TLS 客户端和服务器需要不同的默认设置才能安全通信。通用的 TLS 协议常量已弃用,建议使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER

ssl.PROTOCOL_TLS_CLIENT

自动协商客户端和服务器都支持的最高协议版本,并为上下文配置客户端连接。该协议默认启用 CERT_REQUIREDcheck_hostname

在 3.6 版本加入。

ssl.PROTOCOL_TLS_SERVER

自动协商客户端和服务器都支持的最高协议版本,并为上下文配置服务器端连接。

在 3.6 版本加入。

ssl.PROTOCOL_SSLv23

PROTOCOL_TLS 的别名。

3.6 版本已弃用: 使用 PROTOCOL_TLS 代替。

ssl.PROTOCOL_SSLv3

选择 SSL 版本 3 作为通道加密协议。

如果 OpenSSL 编译时使用了 no-ssl3 选项,则此协议不可用。

警告

SSL 版本 3 不安全。强烈建议不要使用它。

3.6 版本已弃用: OpenSSL 已弃用所有版本特定的协议。请使用默认协议 PROTOCOL_TLS_SERVERPROTOCOL_TLS_CLIENT,并结合 SSLContext.minimum_versionSSLContext.maximum_version

ssl.PROTOCOL_TLSv1

选择 TLS 版本 1.0 作为通道加密协议。

3.6 版本已弃用: OpenSSL 已弃用所有版本特定的协议。

ssl.PROTOCOL_TLSv1_1

选择 TLS 版本 1.1 作为通道加密协议。仅在使用 openssl 版本 1.0.1+ 时可用。

在 3.4 版本加入。

3.6 版本已弃用: OpenSSL 已弃用所有版本特定的协议。

ssl.PROTOCOL_TLSv1_2

选择 TLS 版本 1.2 作为通道加密协议。仅在使用 openssl 版本 1.0.1+ 时可用。

在 3.4 版本加入。

3.6 版本已弃用: OpenSSL 已弃用所有版本特定的协议。

ssl.OP_ALL

启用对其他 SSL 实现中存在的各种错误的修复。此选项默认设置。它不一定设置与 OpenSSL 的 SSL_OP_ALL 常量相同的标志。

在 3.2 版本加入。

ssl.OP_NO_SSLv2

阻止 SSLv2 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 SSLv2 作为协议版本。

在 3.2 版本加入。

3.6 版本已弃用: SSLv2 已弃用

ssl.OP_NO_SSLv3

阻止 SSLv3 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 SSLv3 作为协议版本。

在 3.2 版本加入。

3.6 版本已弃用: SSLv3 已弃用

ssl.OP_NO_TLSv1

阻止 TLSv1 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 TLSv1 作为协议版本。

在 3.2 版本加入。

3.7 版本已弃用: 该选项自 OpenSSL 1.1.0 起已弃用,请使用新的 SSLContext.minimum_versionSSLContext.maximum_version 代替。

ssl.OP_NO_TLSv1_1

阻止 TLSv1.1 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 TLSv1.1 作为协议版本。仅在使用 openssl 版本 1.0.1+ 时可用。

在 3.4 版本加入。

3.7 版本已弃用: 该选项自 OpenSSL 1.1.0 起已弃用。

ssl.OP_NO_TLSv1_2

阻止 TLSv1.2 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 TLSv1.2 作为协议版本。仅在使用 openssl 版本 1.0.1+ 时可用。

在 3.4 版本加入。

3.7 版本已弃用: 该选项自 OpenSSL 1.1.0 起已弃用。

ssl.OP_NO_TLSv1_3

阻止 TLSv1.3 连接。此选项仅与 PROTOCOL_TLS 结合使用。它阻止对等方选择 TLSv1.3 作为协议版本。TLS 1.3 在 OpenSSL 1.1.1 或更高版本中可用。当 Python 编译到较旧版本的 OpenSSL 时,此标志默认为 0

添加于 3.6.3 版本。

3.7 版本已弃用: 该选项自 OpenSSL 1.1.0 起已弃用。它已添加到 2.7.15 和 3.6.3 以与 OpenSSL 1.0.2 向后兼容。

ssl.OP_NO_RENEGOTIATION

禁用 TLSv1.2 及更早版本中的所有重新协商。不发送 HelloRequest 消息,并忽略通过 ClientHello 进行的重新协商请求。

此选项仅在使用 OpenSSL 1.1.0h 及更高版本时可用。

在 3.7 版本加入。

ssl.OP_CIPHER_SERVER_PREFERENCE

使用服务器的密码套件排序偏好,而不是客户端的。此选项对客户端套接字和 SSLv2 服务器套接字无效。

在 3.3 版本加入。

ssl.OP_SINGLE_DH_USE

防止为不同的 SSL 会话重用相同的 DH 密钥。这可以提高前向保密性,但需要更多的计算资源。此选项仅适用于服务器套接字。

在 3.3 版本加入。

ssl.OP_SINGLE_ECDH_USE

防止为不同的 SSL 会话重用相同的 ECDH 密钥。这可以提高前向保密性,但需要更多的计算资源。此选项仅适用于服务器套接字。

在 3.3 版本加入。

ssl.OP_ENABLE_MIDDLEBOX_COMPAT

在 TLS 1.3 握手中发送虚拟的 Change Cipher Spec (CCS) 消息,使 TLS 1.3 连接看起来更像 TLS 1.2 连接。

此选项仅在使用 OpenSSL 1.1.1 及更高版本时可用。

在 3.8 版本加入。

ssl.OP_NO_COMPRESSION

禁用 SSL 通道上的压缩。如果应用程序协议支持自己的压缩方案,则此功能很有用。

在 3.3 版本加入。

class ssl.Options

OP_* 常量的 enum.IntFlag 集合。

ssl.OP_NO_TICKET

阻止客户端请求会话票证。

在 3.6 版本加入。

ssl.OP_IGNORE_UNEXPECTED_EOF

忽略 TLS 连接的意外关闭。

此选项仅在使用 OpenSSL 3.0.0 及更高版本时可用。

在 3.10 版本加入。

ssl.OP_ENABLE_KTLS

启用内核 TLS 的使用。为了受益于此功能,OpenSSL 必须已编译支持它,并且协商的密码套件和扩展必须得到它的支持(支持的列表可能因平台和内核版本而异)。

请注意,在使用内核 TLS 时,某些加密操作由内核直接执行,而不是通过任何可用的 OpenSSL Providers 执行。如果应用程序要求所有加密操作都由 FIPS provider 执行,这可能是不可取的。

此选项仅在使用 OpenSSL 3.0.0 及更高版本时可用。

3.12 新版功能.

ssl.OP_LEGACY_SERVER_CONNECT

仅允许 OpenSSL 和未修补的服务器之间的遗留不安全重新协商。

3.12 新版功能.

ssl.HAS_ALPN

OpenSSL 库是否内置支持 应用程序层协议协商 TLS 扩展,如 RFC 7301 所述。

在 3.5 版本加入。

ssl.HAS_NEVER_CHECK_COMMON_NAME

OpenSSL 库是否内置支持不检查主题通用名称,并且 SSLContext.hostname_checks_common_name 是可写的。

在 3.7 版本加入。

ssl.HAS_ECDH

OpenSSL 库是否内置支持基于椭圆曲线的 Diffie-Hellman 密钥交换。除非分发者明确禁用了该功能,否则这应为 true。

在 3.3 版本加入。

ssl.HAS_SNI

OpenSSL 库是否内置支持 服务器名称指示 扩展(如 RFC 6066 中所述)。

在 3.2 版本加入。

ssl.HAS_NPN

OpenSSL 库是否内置支持 下一协议协商,如 应用程序层协议协商 中所述。如果为 true,您可以使用 SSLContext.set_npn_protocols() 方法来声明您希望支持的协议。

在 3.3 版本加入。

ssl.HAS_SSLv2

OpenSSL 库是否内置支持 SSL 2.0 协议。

在 3.7 版本加入。

ssl.HAS_SSLv3

OpenSSL 库是否内置支持 SSL 3.0 协议。

在 3.7 版本加入。

ssl.HAS_TLSv1

OpenSSL 库是否内置支持 TLS 1.0 协议。

在 3.7 版本加入。

ssl.HAS_TLSv1_1

OpenSSL 库是否内置支持 TLS 1.1 协议。

在 3.7 版本加入。

ssl.HAS_TLSv1_2

OpenSSL 库是否内置支持 TLS 1.2 协议。

在 3.7 版本加入。

ssl.HAS_TLSv1_3

OpenSSL 库是否内置支持 TLS 1.3 协议。

在 3.7 版本加入。

ssl.HAS_PSK

OpenSSL 库是否内置支持 TLS-PSK。

在 3.13 版本加入。

ssl.HAS_PHA

OpenSSL 库是否内置支持 TLS-PHA。

在 3.14 版本加入。

ssl.CHANNEL_BINDING_TYPES

支持的 TLS 通道绑定类型列表。此列表中的字符串可用作 SSLSocket.get_channel_binding() 的参数。

在 3.3 版本加入。

ssl.OPENSSL_VERSION

解释器加载的 OpenSSL 库的版本字符串。

>>> ssl.OPENSSL_VERSION
'OpenSSL 1.0.2k  26 Jan 2017'

在 3.2 版本加入。

ssl.OPENSSL_VERSION_INFO

一个由五个整数组成的元组,表示 OpenSSL 库的版本信息。

>>> ssl.OPENSSL_VERSION_INFO
(1, 0, 2, 11, 15)

在 3.2 版本加入。

ssl.OPENSSL_VERSION_NUMBER

OpenSSL 库的原始版本号,表示为一个整数。

>>> ssl.OPENSSL_VERSION_NUMBER
268443839
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x100020bf'

在 3.2 版本加入。

ssl.ALERT_DESCRIPTION_HANDSHAKE_FAILURE
ssl.ALERT_DESCRIPTION_INTERNAL_ERROR
ALERT_DESCRIPTION_*

来自 RFC 5246 及其他标准的告警描述。 IANA TLS 告警注册表 包含此列表以及定义其含义的 RFC 的链接。

SSLContext.set_servername_callback() 中用作回调函数的返回值。

在 3.4 版本加入。

class ssl.AlertDescription

enum.IntEnum 集合,包含 ALERT_DESCRIPTION_* 常量。

在 3.6 版本加入。

Purpose.SERVER_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的选项。此值表示上下文可用于验证 Web 服务器(因此,它将用于创建客户端套接字)。

在 3.4 版本加入。

Purpose.CLIENT_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的选项。此值表示上下文可用于验证 Web 客户端(因此,它将用于创建服务器端套接字)。

在 3.4 版本加入。

class ssl.SSLErrorNumber

enum.IntEnum 集合,包含 SSL_ERROR_* 常量。

在 3.6 版本加入。

class ssl.TLSVersion

用于 SSLContext.maximum_versionSSLContext.minimum_version 的 SSL 和 TLS 版本 enum.IntEnum 集合。

在 3.7 版本加入。

TLSVersion.MINIMUM_SUPPORTED
TLSVersion.MAXIMUM_SUPPORTED

支持的最低或最高 SSL 或 TLS 版本。这些是魔法常量。它们的值不反映最低和最高的可用 TLS/SSL 版本。

TLSVersion.SSLv3
TLSVersion.TLSv1
TLSVersion.TLSv1_1
TLSVersion.TLSv1_2
TLSVersion.TLSv1_3

SSL 3.0 到 TLS 1.3。

版本 3.10 已弃用: 除了 TLSVersion.TLSv1_2TLSVersion.TLSv1_3 之外,所有 TLSVersion 成员均已弃用。

SSL 套接字

class ssl.SSLSocket(socket.socket)

SSL 套接字提供 套接字对象 的以下方法:

然而,由于 SSL(和 TLS)协议在 TCP 之上具有自己的帧协议,SSL 套接字抽象在某些方面可能与普通操作系统级套接字的规范有所不同。请特别参阅 关于非阻塞套接字的说明

SSLSocket 的实例必须使用 SSLContext.wrap_socket() 方法创建。

版本 3.5 已更改: 添加了 sendfile() 方法。

版本 3.5 已更改: shutdown() 不再每次接收或发送字节时重置套接字超时。套接字超时现在是关闭的总最长时间。

版本 3.6 已弃用: 直接创建 SSLSocket 实例是过时的,请使用 SSLContext.wrap_socket() 来包装套接字。

版本 3.7 已更改: SSLSocket 实例必须通过 wrap_socket() 创建。在早期版本中,可以直接创建实例。这从未被记录或官方支持。

版本 3.10 已更改: Python 现在内部使用 SSL_read_exSSL_write_ex。这些函数支持读写大于 2 GB 的数据。写入零长度数据不再因协议违规错误而失败。

SSL 套接字还具有以下附加方法和属性:

SSLSocket.read(len=1024, buffer=None)

从 SSL 套接字读取最多 len 字节的数据,并将结果作为 bytes 实例返回。如果指定了 buffer,则改用缓冲区读取,并返回读取的字节数。

如果套接字是 非阻塞 且读取会阻塞,则引发 SSLWantReadErrorSSLWantWriteError

由于随时可能进行重新协商,调用 read() 也可能导致写入操作。

版本 3.5 已更改: 套接字超时不再每次接收或发送字节时重置。套接字超时现在是读取最多 len 字节的总最长时间。

版本 3.6 已弃用: 使用 recv() 而不是 read()

SSLSocket.write(data)

data 写入 SSL 套接字,并返回写入的字节数。data 参数必须是支持缓冲区接口的对象。

如果套接字是 非阻塞 且写入会阻塞,则引发 SSLWantReadErrorSSLWantWriteError

由于随时可能进行重新协商,调用 write() 也可能导致读取操作。

版本 3.5 已更改: 套接字超时不再每次接收或发送字节时重置。套接字超时现在是写入 data 的总最长时间。

版本 3.6 已弃用: 使用 send() 而不是 write()

备注

read()write() 方法是低级方法,它们读取和写入未加密的应用层数据,并将其解密/加密为加密的线路上层数据。这些方法需要活动的 SSL 连接,即握手已完成且未调用 SSLSocket.unwrap()

通常,您应该使用 recv()send() 等套接字 API 方法,而不是这些方法。

SSLSocket.do_handshake(block=False)

执行 SSL 设置握手。

如果 block 为 true 且 gettimeout() 返回的超时为零,则套接字将被设置为阻塞模式,直到完成握手。

版本 3.4 已更改: 当套接字的 context 属性的 check_hostname 属性为 true 时,握手方法还会执行 match_hostname()

版本 3.5 已更改: 套接字超时不再每次接收或发送字节时重置。套接字超时现在是握手的总最长时间。

版本 3.7 已更改: 主机名或 IP 地址由 OpenSSL 在握手期间进行匹配。不再使用 match_hostname() 函数。如果 OpenSSL 拒绝主机名或 IP 地址,握手将提前中止,并向对端发送 TLS 告警消息。

SSLSocket.getpeercert(binary_form=False)

如果连接的另一端没有证书,则返回 None。如果 SSL 握手尚未完成,则引发 ValueError

如果 binary_form 参数为 False,并且从对端收到了证书,则此方法返回一个 dict 实例。如果证书未经验证,则 dict 为空。如果证书已验证,则返回一个具有多个键的 dict,其中包含 subject(证书颁发给的主体)和 issuer(颁发证书的主体)。如果证书包含“主体备用名称”扩展(参见 RFC 3280),则 dict 中还会有一个 subjectAltName 键。

subjectissuer 字段是元组,包含证书数据结构中相应字段的相对区分名称 (RDN) 序列,每个 RDN 是一个名称-值对序列。以下是一个真实世界的例子:

{'issuer': ((('countryName', 'IL'),),
            (('organizationName', 'StartCom Ltd.'),),
            (('organizationalUnitName',
              'Secure Digital Certificate Signing'),),
            (('commonName',
              'StartCom Class 2 Primary Intermediate Server CA'),)),
 'notAfter': 'Nov 22 08:15:19 2013 GMT',
 'notBefore': 'Nov 21 03:09:52 2011 GMT',
 'serialNumber': '95F0',
 'subject': ((('description', '571208-SLe257oHY9fVQ07Z'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'Electronic Frontier Foundation, Inc.'),),
             (('commonName', '*.eff.org'),),
             (('emailAddress', 'hostmaster@eff.org'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

如果 binary_form 参数为 True,并且提供了证书,则此方法返回整个证书的 DER 编码形式作为字节序列,或者如果对端未提供证书,则返回 None。对端是否提供证书取决于 SSL 套接字的角色:

  • 对于客户端 SSL 套接字,服务器始终提供证书,无论是否需要验证;

  • 对于服务器 SSL 套接字,客户端仅在服务器请求时提供证书;因此,如果您使用了 CERT_NONE(而不是 CERT_OPTIONALCERT_REQUIRED),getpeercert() 将返回 None

另请参阅 SSLContext.check_hostname

版本 3.2 已更改: 返回的字典包含其他项,例如 issuernotBefore

版本 3.4 已更改: 当握手未完成时,会引发 ValueError。返回的字典包含其他 X509v3 扩展项,例如 crlDistributionPointscaIssuersOCSP URI。

版本 3.9 已更改: IPv6 地址字符串不再有尾随换行符。

SSLSocket.get_verified_chain()

以 DER 编码的字节列表的形式返回 SSL 通道另一端提供的已验证证书链。如果禁用了证书验证,则该方法与 get_unverified_chain() 的行为相同。

在 3.13 版本加入。

SSLSocket.get_unverified_chain()

以 DER 编码的字节列表的形式返回 SSL 通道另一端提供的原始证书链。

在 3.13 版本加入。

SSLSocket.cipher()

返回一个三元素元组,包含正在使用的密码套件的名称、定义其使用的 SSL 协议版本以及使用的密钥位数。如果尚未建立连接,则返回 None

SSLSocket.shared_ciphers()

返回客户端和服务器都可用的密码套件列表。返回列表的每个条目都是一个三元素元组,包含密码套件的名称、定义其使用的 SSL 协议版本以及密码套件使用的密钥位数。如果尚未建立连接或套接字是客户端套接字,则 shared_ciphers() 返回 None

在 3.5 版本加入。

SSLSocket.compression()

返回使用的压缩算法(作为字符串),或者如果连接未压缩,则返回 None

如果更高级的协议支持自己的压缩机制,您可以使用 OP_NO_COMPRESSION 来禁用 SSL 级别的压缩。

在 3.3 版本加入。

SSLSocket.get_channel_binding(cb_type='tls-unique')

获取当前连接的通道绑定数据,作为字节对象。如果未连接或握手尚未完成,则返回 None

cb_type 参数允许选择所需的通道绑定类型。有效的通道绑定类型列在 CHANNEL_BINDING_TYPES 列表中。目前仅支持 RFC 5929 中定义的“tls-unique”通道绑定。如果请求不支持的通道绑定类型,将引发 ValueError

在 3.3 版本加入。

SSLSocket.selected_alpn_protocol()

返回 TLS 握手期间选择的协议。如果未调用 SSLContext.set_alpn_protocols(),或者对端不支持 ALPN,或者此套接字不支持任何客户端提出的协议,或者尚未发生握手,则返回 None

在 3.5 版本加入。

SSLSocket.selected_npn_protocol()

返回 TLS/SSL 握手期间选择的高层协议。如果未调用 SSLContext.set_npn_protocols(),或者对端不支持 NPN,或者握手尚未发生,则返回 None

在 3.3 版本加入。

版本 3.10 已弃用: NPN 已被 ALPN 取代。

SSLSocket.unwrap()

执行 SSL 关闭握手,该握手会从底层套接字移除 TLS 层,并返回底层套接字对象。这可用于从加密操作转向未加密操作。应始终使用返回的套接字与连接的另一端进行进一步通信,而不是原始套接字。

SSLSocket.verify_client_post_handshake()

请求 TLS 1.3 客户端进行握手后身份验证 (PHA)。PHA 只能从服务器端套接字针对 TLS 1.3 连接发起,在初始 TLS 握手之后,并且双方都启用了 PHA,请参见 SSLContext.post_handshake_auth

该方法不会立即执行证书交换。服务器端将在下一个写入事件期间发送 CertificateRequest,并期望客户端在下一个读取事件中用证书进行响应。

如果任何先决条件不满足(例如,不是 TLS 1.3、未启用 PHA),则会引发 SSLError

备注

仅在使用 OpenSSL 1.1.1 和启用了 TLS 1.3 时可用。如果不支持 TLS 1.3,则该方法将引发 NotImplementedError

在 3.8 版本加入。

SSLSocket.version()

返回连接协商的实际 SSL 协议版本(作为字符串),或者在没有建立安全连接时返回 None。截至目前,可能返回值包括 "SSLv2""SSLv3""TLSv1""TLSv1.1""TLSv1.2"。较新的 OpenSSL 版本可能定义更多返回值。

在 3.5 版本加入。

SSLSocket.pending()

返回已解密并可供读取的字节数,这些字节在连接中等待。

SSLSocket.context

此 SSL 套接字关联的 SSLContext 对象。

在 3.2 版本加入。

SSLSocket.server_side

一个布尔值,对于服务器端套接字为 True,对于客户端套接字为 False

在 3.2 版本加入。

SSLSocket.server_hostname

服务器的主机名:str 类型,或者对于服务器端套接字为 None,或者如果在构造函数中未指定主机名。

在 3.2 版本加入。

版本 3.7 已更改: 该属性现在始终是 ASCII 文本。当 server_hostname 是国际化域名 (IDN) 时,此属性现在存储 A-label 形式("xn--pythn-mua.org"),而不是 U-label 形式("pythön.org")。

SSLSocket.session

此 SSL 连接的 SSLSession。在 TLS 握手完成后,会话对于客户端和服务器端套接字均可用。对于客户端套接字,可以在调用 do_handshake() 之前设置会话以重用会话。

在 3.6 版本加入。

SSLSocket.session_reused

在 3.6 版本加入。

SSL 上下文

在 3.2 版本加入。

SSL 上下文保存比单个 SSL 连接更长期的各种数据,例如 SSL 配置选项、证书和私钥。它还管理服务器端套接字的 SSL 会话缓存,以加快同一客户端的重复连接速度。

class ssl.SSLContext(protocol=None)

创建一个新的 SSL 上下文。您可以传递 protocol,它必须是此模块中定义的 PROTOCOL_* 常量之一。该参数指定要使用的 SSL 协议版本。通常,服务器选择特定的协议版本,客户端必须适应服务器的选择。大多数版本与其他版本不兼容。如果未指定,则默认为 PROTOCOL_TLS;它提供了与其他版本最大的兼容性。

下表显示了客户端(纵向)的哪些版本可以连接到服务器(横向)的哪些版本:

客户端 / **服务器**

SSLv2

SSLv3

**TLS** [3]

TLSv1

TLSv1.1

TLSv1.2

SSLv2

[1]

SSLv3

[2]

TLS (SSLv23) [3]

[1]

[2]

TLSv1

TLSv1.1

TLSv1.2

脚注

参见

create_default_context()ssl 模块为给定的目的选择安全设置。

版本 3.6 已更改: 上下文以安全默认值创建。默认设置了选项 OP_NO_COMPRESSIONOP_CIPHER_SERVER_PREFERENCEOP_SINGLE_DH_USEOP_SINGLE_ECDH_USEOP_NO_SSLv2OP_NO_SSLv3(除了 PROTOCOL_SSLv3)。初始密码套件列表仅包含 HIGH 密码,不包含 NULL 密码和 MD5 密码。

版本 3.10 已弃用: 不带协议参数的 SSLContext 已弃用。将来,上下文类将需要 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 协议。

版本 3.10 已更改: 默认密码套件现在仅包括具有前向保密和安全级别 2 的安全 AES 和 ChaCha20 密码。禁止使用少于 2048 位的 RSA 和 DH 密钥,以及少于 224 位的 ECC 密钥。PROTOCOL_TLSPROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 使用 TLS 1.2 作为最低 TLS 版本。

备注

SSLContext 在被连接使用后仅支持有限的变异。允许向内部信任存储添加新证书,但更改密码套件、验证设置或 mTLS 证书可能会导致意外行为。

备注

SSLContext 设计为可共享并供多个连接使用。因此,只要它在连接使用后不被重新配置,它就是线程安全的。

SSLContext 对象具有以下方法和属性:

SSLContext.cert_store_stats()

获取有关已加载的 X.509 证书数量、标记为 CA 证书的 X.509 证书数量以及证书撤销列表(CRL)数量的统计信息,以字典形式返回。

对于包含一个 CA 证书和另一个证书的上下文的示例:

>>> context.cert_store_stats()
{'crl': 0, 'x509_ca': 1, 'x509': 2}

在 3.4 版本加入。

SSLContext.load_cert_chain(certfile, keyfile=None, password=None)

加载私钥和相应的证书。certfile 字符串必须是 PEM 格式的单个文件的路径,其中包含证书以及建立证书真实性所需的任何数量的 CA 证书。keyfile 字符串(如果存在)必须指向包含私钥的文件。否则,私钥也将从 certfile 中获取。有关证书如何在 certfile 中存储的信息,请参阅关于 证书 的讨论。

password 参数可以是一个函数,在需要解密私钥时调用该函数来获取密码。仅当私钥已加密且需要密码时才会调用它。它将不带参数地调用,并且应返回一个字符串、字节或 bytearray。如果返回值是字符串,则在用于解密密钥之前将以 UTF-8 编码。或者,可以直接将字符串、字节或 bytearray 值作为 password 参数提供。如果私钥未加密且不需要密码,则将忽略该参数。

如果未指定 password 参数并且需要密码,则将使用 OpenSSL 的内置密码提示机制与用户交互以获取密码。

如果私钥与证书不匹配,则会引发 SSLError

版本 3.3 已更改: 新增可选参数 password

SSLContext.load_default_certs(purpose=Purpose.SERVER_AUTH)

从默认位置加载一组默认的“证书颁发机构”(CA)证书。在 Windows 上,它从 CAROOT 系统存储中加载 CA 证书。在所有系统上,它会调用 SSLContext.set_default_verify_paths()。将来,该方法可能还会从其他位置加载 CA 证书。

purpose 标志指定加载哪种类型的 CA 证书。默认设置 Purpose.SERVER_AUTH 加载已标记并受信任用于 TLS Web 服务器身份验证(客户端套接字)的证书。Purpose.CLIENT_AUTH 加载用于服务器端客户端证书验证的 CA 证书。

在 3.4 版本加入。

SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None)

加载一组“证书颁发机构”(CA)证书,用于在 verify_mode 不是 CERT_NONE 时验证其他对端证书。必须指定 cafilecapath 中的至少一个。

此方法还可以加载 PEM 或 DER 格式的证书撤销列表(CRL)。为了使用 CRL,必须正确配置 SSLContext.verify_flags

cafile 字符串(如果存在)是 PEM 格式的连接 CA 证书文件的路径。有关在此文件中排列证书的信息,请参阅关于 证书 的讨论。

capath 字符串(如果存在)是包含多个 PEM 格式 CA 证书的目录的路径,遵循 OpenSSL 特定布局

cadata 对象(如果存在)是一个 ASCII 字符串,包含一个或多个 PEM 编码的证书,或者是一个 类字节对象,包含 DER 编码的证书。与 capath 类似,PEM 编码证书周围的多余行将被忽略,但至少必须存在一个证书。

版本 3.4 已更改: 新增可选参数 cadata

SSLContext.get_ca_certs(binary_form=False)

获取已加载的“证书颁发机构”(CA)证书列表。如果 binary_form 参数为 False,则列表的每个条目都是一个字典,类似于 SSLSocket.getpeercert() 的输出。否则,该方法返回 DER 编码证书的列表。返回的列表不包含 capath 中的证书,除非在 SSL 连接中请求并加载了某个证书。

备注

capath 目录中的证书不会被加载,除非它们至少被使用过一次。

在 3.4 版本加入。

SSLContext.get_ciphers()

获取已启用密码套件的列表。列表按优先级排序。请参阅 SSLContext.set_ciphers()

示例

>>> ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
>>> ctx.set_ciphers('ECDHE+AESGCM:!ECDSA')
>>> ctx.get_ciphers()
[{'aead': True,
  'alg_bits': 256,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(256) Mac=AEAD',
  'digest': None,
  'id': 50380848,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES256-GCM-SHA384',
  'protocol': 'TLSv1.2',
  'strength_bits': 256,
  'symmetric': 'aes-256-gcm'},
 {'aead': True,
  'alg_bits': 128,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(128) Mac=AEAD',
  'digest': None,
  'id': 50380847,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES128-GCM-SHA256',
  'protocol': 'TLSv1.2',
  'strength_bits': 128,
  'symmetric': 'aes-128-gcm'}]

在 3.6 版本加入。

SSLContext.set_default_verify_paths()

从 OpenSSL 库构建时定义的本地文件系统路径加载一组默认的“证书颁发机构”(CA)证书。不幸的是,没有简单的方法可以知道此方法是否成功:如果没有找到证书,则不返回错误。当 OpenSSL 库作为操作系统的一部分提供时,它很可能已正确配置。

SSLContext.set_ciphers(ciphers, /)

设置此上下文创建的套接字可用的密码套件。它应该是一个遵循 OpenSSL 密码列表格式 的字符串。如果无法选择任何密码套件(因为编译时选项或其他配置禁止使用所有指定的密码套件),则会引发 SSLError

备注

连接后,SSLSocket.cipher() 方法将显示当前选择的密码套件。

TLS 1.3 密码套件不能用 set_ciphers() 禁用。

SSLContext.set_alpn_protocols(alpn_protocols)

指定套接字在 SSL/TLS 握手期间应通告哪些协议。它应该是一个 ASCII 字符串列表,例如 ['http/1.1', 'spdy/2'],按偏好排序。协议的选择将在握手过程中进行,并根据 RFC 7301 进行处理。在成功握手后,SSLSocket.selected_alpn_protocol() 方法将返回商定的协议。

如果 HAS_ALPNFalse,此方法将引发 NotImplementedError

在 3.5 版本加入。

SSLContext.set_npn_protocols(npn_protocols)

指定套接字在 SSL/TLS 握手期间应通告哪些协议。它应该是一个字符串列表,例如 ['http/1.1', 'spdy/2'],按偏好排序。协议的选择将在握手过程中进行,并根据 应用层协议协商 进行处理。在成功握手后,SSLSocket.selected_npn_protocol() 方法将返回商定的协议。

如果 HAS_NPNFalse,此方法将引发 NotImplementedError

在 3.3 版本加入。

版本 3.10 已弃用: NPN 已被 ALPN 取代。

SSLContext.sni_callback

注册一个回调函数,当 TLS 客户端指定服务器名称指示时,该函数将在 SSL/TLS 服务器接收到 TLS Client Hello 握手消息后被调用。服务器名称指示机制在 RFC 6066 第 3 节 - 服务器名称指示 中定义。

每个 SSLContext 只能设置一个回调。如果 sni_callback 设置为 None,则禁用回调。后续调用此函数将禁用之前注册的回调。

回调函数将使用三个参数调用;第一个是 ssl.SSLSocket,第二个是表示客户端意图通信的服务器名称的字符串(如果 TLS Client Hello 不包含服务器名称,则为 None),第三个参数是原始 SSLContext。服务器名称参数是文本。对于国际化域名,服务器名称是 IDN A-label("xn--pythn-mua.org")。

此回调的典型用法是将 ssl.SSLSocketSSLSocket.context 属性更改为代表与服务器名称匹配的证书链的新 SSLContext 对象。

由于 TLS 连接的早期协商阶段,只能使用有限的方法和属性,如 SSLSocket.selected_alpn_protocol()SSLSocket.contextSSLSocket.getpeercert()SSLSocket.get_verified_chain()SSLSocket.get_unverified_chain()SSLSocket.cipher()SSLSocket.compression() 方法要求 TLS 连接已超越 TLS Client Hello 阶段,因此不会返回有意义的值,也无法安全调用。

回调函数必须返回 None 以允许 TLS 协商继续。如果需要 TLS 失败,可以返回常量 ALERT_DESCRIPTION_*。其他返回值将导致以 ALERT_DESCRIPTION_INTERNAL_ERROR 告警的 TLS 致命错误。

如果从 sni_callback 函数引发异常,TLS 连接将以 fatal TLS 告警消息 ALERT_DESCRIPTION_HANDSHAKE_FAILURE 终止。

如果 OpenSSL 库在构建时定义了 OPENSSL_NO_TLSEXT,则此方法将引发 NotImplementedError

在 3.7 版本加入。

SSLContext.set_servername_callback(server_name_callback)

这是一个为向后兼容而保留的旧 API。尽可能时,您应该使用 sni_callback。给定的 server_name_callbacksni_callback 类似,只是当服务器主机名是 IDN 编码的国际化域名时,server_name_callback 接收的是解码后的 U-label("pythön.org")。

如果服务器名称出现解码错误,TLS 连接将以 ALERT_DESCRIPTION_INTERNAL_ERROR 致命 TLS 警报消息终止与客户端的连接。

在 3.4 版本加入。

SSLContext.load_dh_params(dhfile, /)

加载用于 Diffie-Hellman (DH) 密钥交换的密钥生成参数。使用 DH 密钥交换以计算资源(服务器和客户端)为代价来提高前向保密性。dhfile 参数应该是包含 PEM 格式 DH 参数的文件的路径。

此设置不适用于客户端套接字。您还可以使用 OP_SINGLE_DH_USE 选项来进一步提高安全性。

在 3.3 版本加入。

SSLContext.set_ecdh_curve(curve_name, /)

设置基于椭圆曲线的 Diffie-Hellman (ECDH) 密钥交换的曲线名称。ECDH 比普通 DH 快得多,同时安全性也同样高。curve_name 参数应该是描述一个知名椭圆曲线的字符串,例如 prime256v1,这是一个广泛支持的曲线。

此设置不适用于客户端套接字。您还可以使用 OP_SINGLE_ECDH_USE 选项来进一步提高安全性。

如果 HAS_ECDHFalse,则此方法不可用。

在 3.3 版本加入。

参见

SSL/TLS 与前向保密

Vincent Bernat。

SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, session=None)

包装现有的 Python 套接字 sock,并返回一个 SSLContext.sslsocket_class(默认为 SSLSocket)的实例。返回的 SSL 套接字与上下文、其设置和证书绑定。sock 必须是 SOCK_STREAM 套接字;不支持其他套接字类型。

参数 server_side 是一个布尔值,用于标识此套接字是需要服务器端行为还是客户端行为。

对于客户端套接字,上下文的构建是惰性的;如果底层套接字尚未连接,则上下文构建将在调用套接字的 connect() 之后执行。对于服务器端套接字,如果套接字没有远程对等方,则假定它是一个监听套接字,并且服务器端的 SSL 包装会在通过 accept() 方法接受的客户端连接上自动执行。该方法可能会引发 SSLError

在客户端连接上,可选参数 server_hostname 指定我们正在连接的服务的主机名。这允许单个服务器托管多个基于 SSL 的服务,并具有不同的证书,这与 HTTP 虚拟主机非常相似。指定 server_hostname 如果 server_side 为 true,则会引发 ValueError

参数 do_handshake_on_connect 指定是自动执行 SSL 握手(在执行 socket.connect() 之后),还是由应用程序显式调用,通过调用 SSLSocket.do_handshake() 方法。显式调用 SSLSocket.do_handshake() 使程序能够控制握手中涉及的套接字 I/O 的阻塞行为。

参数 suppress_ragged_eofs 指定 SSLSocket.recv() 方法应如何处理来自连接另一端的意外 EOF。如果指定为 True(默认值),则响应底层套接字引发的意外 EOF 错误,它将返回一个正常的 EOF(空字节对象);如果指定为 False,它将把异常抛回给调用者。

session,参见 session

要将 SSLSocket 包装在另一个 SSLSocket 中,请使用 SSLContext.wrap_bio()

3.5 版更改:始终允许传递 server_hostname,即使 OpenSSL 没有 SNI。

3.6 版更改:添加了 session 参数。

3.7 版更改:该方法返回 SSLContext.sslsocket_class 的实例,而不是硬编码的 SSLSocket

SSLContext.sslsocket_class

SSLContext.wrap_socket() 的返回类型,默认为 SSLSocket。该属性可以赋值给 SSLContext 的实例,以返回 SSLSocket 的自定义子类。

在 3.7 版本加入。

SSLContext.wrap_bio(incoming, outgoing, server_side=False, server_hostname=None, session=None)

包装 BIO 对象 incomingoutgoing,并返回一个 SSLContext.sslobject_class(默认为 SSLObject)的实例。SSL 例程将从 incoming BIO 读取输入数据,并向 outgoing BIO 写入数据。

server_sideserver_hostnamesession 参数的含义与 SSLContext.wrap_socket() 中的相同。

3.6 版更改:添加了 session 参数。

3.7 版更改:该方法返回 SSLContext.sslobject_class 的实例,而不是硬编码的 SSLObject

SSLContext.sslobject_class

SSLContext.wrap_bio() 的返回类型,默认为 SSLObject。可以覆盖该属性以返回 SSLObject 的自定义子类。

在 3.7 版本加入。

SSLContext.session_stats()

获取有关此上下文创建或管理的 SSL 会话的统计信息。返回一个字典,其中将每个信息片段的名称映射到其数值。例如,以下是自创建上下文以来会话缓存中的命中和未命中总数

>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)
SSLContext.check_hostname

是否在 SSLSocket.do_handshake() 中匹配对等证书的主机名。上下文的 verify_mode 必须设置为 CERT_OPTIONALCERT_REQUIRED,并且您必须将 server_hostname 传递给 wrap_socket() 以匹配主机名。启用主机名检查会自动将 verify_modeCERT_NONE 设置为 CERT_REQUIRED。只要启用了主机名检查,就不能将其设置回 CERT_NONE。协议 PROTOCOL_TLS_CLIENT 默认启用主机名检查。对于其他协议,必须显式启用主机名检查。

示例

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_default_certs()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(s, server_hostname='www.verisign.com')
ssl_sock.connect(('www.verisign.com', 443))

在 3.4 版本加入。

3.7 版更改:启用主机名检查时,如果 verify_modeCERT_NONE,则 verify_mode 现在会自动更改为 CERT_REQUIRED。以前,同样的操作会因 ValueError 而失败。

SSLContext.keylog_filename

每当生成或接收密钥材料时,将 TLS 密钥写入密钥日志文件。密钥日志文件仅用于调试目的。文件格式由 NSS 指定,并被 Wireshark 等许多流量分析器使用。日志文件以追加模式打开。写入在线程之间同步,但在进程之间不进行同步。

在 3.8 版本加入。

SSLContext.maximum_version

TLSVersion 枚举成员,表示支持的最高 TLS 版本。该值默认为 TLSVersion.MAXIMUM_SUPPORTED。对于 PROTOCOL_TLSPROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 以外的协议,此属性是只读的。

属性 maximum_versionminimum_versionSSLContext.options 都会影响上下文支持的 SSL 和 TLS 版本。该实现不会阻止无效的组合。例如,一个在 options 中包含 OP_NO_TLSv1_2maximum_version 设置为 TLSVersion.TLSv1_2 的上下文将无法建立 TLS 1.2 连接。

在 3.7 版本加入。

SSLContext.minimum_version

SSLContext.maximum_version 类似,但它是最低支持版本或 TLSVersion.MINIMUM_SUPPORTED

在 3.7 版本加入。

SSLContext.num_tickets

控制 PROTOCOL_TLS_SERVER 上下文的 TLS 1.3 会话票证的数量。此设置对 TLS 1.0 到 1.2 的连接没有影响。

在 3.8 版本加入。

SSLContext.options

一个整数,表示在此上下文中启用的 SSL 选项集。默认值为 OP_ALL,但您可以通过 OR 操作来指定其他选项,例如 OP_NO_SSLv2

3.6 版更改:SSLContext.options 返回 Options 标志

>>> ssl.create_default_context().options
<Options.OP_ALL|OP_NO_SSLv3|OP_NO_SSLv2|OP_NO_COMPRESSION: 2197947391>

3.7 版已弃用:所有 OP_NO_SSL*OP_NO_TLS* 选项自 Python 3.7 起已弃用。请改用 SSLContext.minimum_versionSSLContext.maximum_version

SSLContext.post_handshake_auth

启用 TLS 1.3 握手后客户端身份验证。握手后身份验证默认禁用,服务器只能在初始握手中请求 TLS 客户端证书。启用后,服务器可以在握手后的任何时间请求 TLS 客户端证书。

在客户端套接字上启用时,客户端向服务器发出信号,表明它支持握手后身份验证。

在服务器端套接字上启用时,SSLContext.verify_mode 也必须设置为 CERT_OPTIONALCERT_REQUIRED。实际的客户端证书交换将延迟到调用 SSLSocket.verify_client_post_handshake() 并执行一些 I/O 操作之后。

在 3.8 版本加入。

SSLContext.protocol

在构造上下文时选择的协议版本。此属性是只读的。

SSLContext.hostname_checks_common_name

在缺少主题备用名称扩展时,check_hostname 是否回退以验证证书的主题通用名称(默认值:true)。

在 3.7 版本加入。

3.10 版更改:在 OpenSSL 1.1.1l 之前的版本中,该标志无效。Python 3.8.9、3.9.3 和 3.10 包含了对先前版本的解决方法。

SSLContext.security_level

一个整数,表示上下文的安全级别。此属性是只读的。

在 3.10 版本加入。

SSLContext.verify_flags

证书验证操作的标志。您可以通过 OR 操作来设置标志,例如 VERIFY_CRL_CHECK_LEAF。默认情况下,OpenSSL 不要求也不验证证书撤销列表 (CRL)。

在 3.4 版本加入。

3.6 版更改:SSLContext.verify_flags 返回 VerifyFlags 标志

>>> ssl.create_default_context().verify_flags
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>
SSLContext.verify_mode

是否尝试验证其他对等方的证书以及在验证失败时如何处理。此属性必须是 CERT_NONECERT_OPTIONALCERT_REQUIRED 之一。

3.6 版更改:SSLContext.verify_mode 现在返回 VerifyMode 枚举

>>> ssl.create_default_context().verify_mode
<VerifyMode.CERT_REQUIRED: 2>
SSLContext.set_psk_client_callback(callback)

在客户端连接上启用 TLS-PSK(预共享密钥)身份验证。

总的来说,应优先使用基于证书的身份验证,而不是此方法。

参数 callback 是一个可调用对象,其签名如下:def callback(hint: str | None) -> tuple[str | None, bytes]hint 参数是服务器发送的可选身份提示。返回值是一个(client-identity, psk)形式的元组。Client-identity 是一个可选字符串,可用于服务器选择相应的 PSK。UTF-8 编码后,该字符串的最大长度为 256 个字节。PSK 是一个表示预共享密钥的类字节对象。返回一个零长度的 PSK 以拒绝连接。

callback 设置为 None 会移除任何现有的回调。

备注

使用 TLS 1.3 时

  • hint 参数始终为 None

  • client-identity 必须是非空字符串。

用法示例:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.maximum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('PSK')

# A simple lambda:
psk = bytes.fromhex('c0ffee')
context.set_psk_client_callback(lambda hint: (None, psk))

# A table using the hint from the server:
psk_table = { 'ServerId_1': bytes.fromhex('c0ffee'),
              'ServerId_2': bytes.fromhex('facade')
}
def callback(hint):
    return 'ClientId_1', psk_table.get(hint, b'')
context.set_psk_client_callback(callback)

如果 HAS_PSKFalse,此方法将引发 NotImplementedError

在 3.13 版本加入。

SSLContext.set_psk_server_callback(callback, identity_hint=None)

在服务器端连接上启用 TLS-PSK(预共享密钥)身份验证。

总的来说,应优先使用基于证书的身份验证,而不是此方法。

参数 callback 是一个可调用对象,其签名如下:def callback(identity: str | None) -> bytesidentity 参数是客户端发送的可选身份,可用于选择相应的 PSK。返回值是一个表示预共享密钥的类字节对象。返回一个零长度的 PSK 以拒绝连接。

callback 设置为 None 会移除任何现有的回调。

参数 identity_hint 是一个可选的身份提示字符串,发送给客户端。UTF-8 编码后,该字符串的最大长度为 256 个字节。

备注

使用 TLS 1.3 时,identity_hint 参数不会发送给客户端。

用法示例:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.maximum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('PSK')

# A simple lambda:
psk = bytes.fromhex('c0ffee')
context.set_psk_server_callback(lambda identity: psk)

# A table using the identity of the client:
psk_table = { 'ClientId_1': bytes.fromhex('c0ffee'),
              'ClientId_2': bytes.fromhex('facade')
}
def callback(identity):
    return psk_table.get(identity, b'')
context.set_psk_server_callback(callback, 'ServerId_1')

如果 HAS_PSKFalse,此方法将引发 NotImplementedError

在 3.13 版本加入。

证书

一般而言,证书是公钥/私钥系统的一部分。在此系统中,每个主体(可以是机器、个人或组织)都被分配一个唯一的两部分加密密钥。密钥的一部分是公开的,称为公钥;另一部分是保密的,称为私钥。这两部分是相关的,因为如果你用其中一部分加密消息,你可以用另一部分解密它,而且**只有**用另一部分才能解密。

证书包含关于两个主体的 {0}。它包含一个*主体*的名称及其公钥。它还包含第二个主体*颁发者*的声明,即主体就是其声称的样子,并且这是主体的公钥。颁发者的声明是用颁发者的私钥签名的,而这个私钥只有颁发者知道。然而,任何人都可以通过查找颁发者的公钥、用其解密声明并与证书中的其他信息进行比较来验证颁发者的声明。证书还包含关于其有效时间段的信息。这表示为两个字段,称为“notBefore”和“notAfter”。

在 Python 使用证书时,客户端或服务器可以使用证书来证明其身份。网络连接的另一方也可以被要求提供证书,并且该证书可以被验证以使要求此类验证的客户端或服务器满意。连接尝试可以设置为在验证失败时引发异常。验证由底层 OpenSSL 框架自动完成;应用程序不必关心其内部机制。但是,应用程序通常需要提供证书集以允许此过程进行。

Python 使用文件来包含证书。它们应格式化为“PEM”(参见 RFC 1422),这是一种 base-64 编码形式,用标题行和页脚行包装

-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

证书链

包含证书的 Python 文件可以包含一系列证书,有时称为*证书链*。此链应以特定证书开始,该证书是“是”客户端或服务器的主体,然后是该证书的颁发者的证书,然后是*该*证书的颁发者的证书,依此类推,直到获得一个*自签名*的证书,也就是说,一个主体和颁发者相同的证书,有时称为*根证书*。证书应简单地连接在一起。例如,假设我们有一个三证书链,从我们的服务器证书到签发我们服务器证书的认证机构的证书,再到签发认证机构证书的机构的根证书

-----BEGIN CERTIFICATE-----
... (certificate for your server)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the certificate for the CA)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the root certificate for the CA's issuer)...
-----END CERTIFICATE-----

CA 证书

如果您需要验证连接另一方的证书,您需要提供一个“CA 证书”文件,其中包含您愿意信任的每个颁发者的证书链。同样,此文件只是将这些链串联在一起。对于验证,Python 将使用它在文件中找到的第一个匹配的链。可以通过调用 SSLContext.load_default_certs() 来使用平台的证书文件,这通过 create_default_context() 自动完成。

组合密钥和证书

通常,私钥与证书存储在同一文件中;在这种情况下,只需要传递 SSLContext.load_cert_chain() 的 `certfile` 参数。如果私钥与证书一起存储,它应位于证书链中的第一个证书之前

-----BEGIN RSA PRIVATE KEY-----
... (private key in base64 encoding) ...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

自签名证书

如果您要创建一个提供 SSL 加密连接服务的服务器,您将需要为该服务获取一个证书。获取合适的证书有许多方法,例如从证书颁发机构购买。另一种常见的做法是生成一个自签名证书。最简单的方法是使用 OpenSSL 包,使用类似以下内容的方法

% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
.......++++++
.............................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:MyState
Locality Name (eg, city) []:Some City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc.
Organizational Unit Name (eg, section) []:My Group
Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com
Email Address []:ops@myserver.mygroup.myorganization.com
%

自签名证书的缺点是它是自己的根证书,而且其他任何人都不会在他们已知的(和受信任的)根证书缓存中找到它。

示例

测试 SSL 支持

要测试 Python 安装中是否存在 SSL 支持,用户代码应使用以下习惯用法

try:
    import ssl
except ImportError:
    pass
else:
    ...  # do something that requires SSL support

客户端操作

此示例创建一个 SSL 上下文,其中包含客户端套接字推荐的安全设置,包括自动证书验证

>>> context = ssl.create_default_context()

如果您更喜欢自己调整安全设置,您可以从头开始创建一个上下文(但请注意,您可能无法获得正确的设置)

>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")

(此代码段假定您的操作系统将所有 CA 证书的包放在 /etc/ssl/certs/ca-bundle.crt 中;如果不是,您将收到错误并且需要调整位置)

协议 PROTOCOL_TLS_CLIENT 配置上下文以进行证书验证和主机名验证。verify_mode 设置为 CERT_REQUIREDcheck_hostname 设置为 True。所有其他协议创建的 SSL 上下文均具有不安全的默认值。

当您使用上下文连接到服务器时,CERT_REQUIREDcheck_hostname 会验证服务器证书:它确保服务器证书是用 CA 证书之一签名的,检查签名是否正确,并验证诸如有效性和主机名标识等其他属性

>>> conn = context.wrap_socket(socket.socket(socket.AF_INET),
...                            server_hostname="www.python.org")
>>> conn.connect(("www.python.org", 443))

然后您可以获取证书

>>> cert = conn.getpeercert()

目视检查表明证书确实标识了所需的服务(即,HTTPS 主机 www.python.org

>>> pprint.pprint(cert)
{'OCSP': ('http://ocsp.digicert.com',),
 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt',),
 'crlDistributionPoints': ('http://crl3.digicert.com/sha2-ev-server-g1.crl',
                           'http://crl4.digicert.com/sha2-ev-server-g1.crl'),
 'issuer': ((('countryName', 'US'),),
            (('organizationName', 'DigiCert Inc'),),
            (('organizationalUnitName', 'www.digicert.com'),),
            (('commonName', 'DigiCert SHA2 Extended Validation Server CA'),)),
 'notAfter': 'Sep  9 12:00:00 2016 GMT',
 'notBefore': 'Sep  5 00:00:00 2014 GMT',
 'serialNumber': '01BB6F00122B177F36CAB49CEA8B6B26',
 'subject': ((('businessCategory', 'Private Organization'),),
             (('1.3.6.1.4.1.311.60.2.1.3', 'US'),),
             (('1.3.6.1.4.1.311.60.2.1.2', 'Delaware'),),
             (('serialNumber', '3359300'),),
             (('streetAddress', '16 Allen Rd'),),
             (('postalCode', '03894-4801'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'NH'),),
             (('localityName', 'Wolfeboro'),),
             (('organizationName', 'Python Software Foundation'),),
             (('commonName', 'www.python.org'),)),
 'subjectAltName': (('DNS', 'www.python.org'),
                    ('DNS', 'python.org'),
                    ('DNS', 'pypi.org'),
                    ('DNS', 'docs.python.org'),
                    ('DNS', 'testpypi.org'),
                    ('DNS', 'bugs.python.org'),
                    ('DNS', 'wiki.python.org'),
                    ('DNS', 'hg.python.org'),
                    ('DNS', 'mail.python.org'),
                    ('DNS', 'packaging.python.org'),
                    ('DNS', 'pythonhosted.org'),
                    ('DNS', 'www.pythonhosted.org'),
                    ('DNS', 'test.pythonhosted.org'),
                    ('DNS', 'us.pycon.org'),
                    ('DNS', 'id.python.org')),
 'version': 3}

现在 SSL 通道已建立并证书已验证,您可以继续与服务器通信

>>> conn.sendall(b"HEAD / HTTP/1.0\r\nHost: linuxfr.org\r\n\r\n")
>>> pprint.pprint(conn.recv(1024).split(b"\r\n"))
[b'HTTP/1.1 200 OK',
 b'Date: Sat, 18 Oct 2014 18:27:20 GMT',
 b'Server: nginx',
 b'Content-Type: text/html; charset=utf-8',
 b'X-Frame-Options: SAMEORIGIN',
 b'Content-Length: 45679',
 b'Accept-Ranges: bytes',
 b'Via: 1.1 varnish',
 b'Age: 2188',
 b'X-Served-By: cache-lcy1134-LCY',
 b'X-Cache: HIT',
 b'X-Cache-Hits: 11',
 b'Vary: Cookie',
 b'Strict-Transport-Security: max-age=63072000; includeSubDomains',
 b'Connection: close',
 b'',
 b'']

请参阅下文 安全注意事项 的讨论。

服务器端操作

对于服务器操作,通常您需要一个服务器证书和一个私钥,每个都存储在一个文件中。您将首先创建一个包含密钥和证书的上下文,以便客户端可以检查您的身份。然后您将打开一个套接字,将其绑定到一个端口,在其上调用 listen(),并开始等待客户端连接

import socket, ssl

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="mycertfile", keyfile="mykeyfile")

bindsocket = socket.socket()
bindsocket.bind(('myaddr.example.com', 10023))
bindsocket.listen(5)

当客户端连接时,您将调用套接字上的 accept() 来获取来自另一端的套接字,并使用上下文的 SSLContext.wrap_socket() 方法为连接创建一个服务器端 SSL 套接字

while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()

然后您将从 connstream 读取数据并对其进行处理,直到您完成与客户端的通信(或客户端完成与您的通信)

def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        if not do_something(connstream, data):
            # we'll assume do_something returns False
            # when we're finished with client
            break
        data = connstream.recv(1024)
    # finished with client

然后返回监听新的客户端连接(当然,一个真正的服务器可能会在单独的线程中处理每个客户端连接,或者将套接字置于非阻塞模式并使用事件循环)。

关于非阻塞套接字

非阻塞模式下的 SSL 套接字行为与常规套接字略有不同。因此,在使用非阻塞套接字时,有几件事需要注意

  • 大多数 SSLSocket 方法将引发 SSLWantWriteErrorSSLWantReadError,而不是 BlockingIOError,如果 I/O 操作会阻塞。如果底层套接字上的读取操作是必需的,则会引发 SSLWantReadError,对于底层套接字上的写入操作,则会引发 SSLWantWriteError。请注意,尝试*写入* SSL 套接字可能需要先*从*底层套接字读取,而尝试*从* SSL 套接字读取可能需要先*写入*底层套接字。

    3.5 版更改:在早期 Python 版本中,SSLSocket.send() 方法返回零,而不是引发 SSLWantWriteErrorSSLWantReadError

  • 调用 select() 告诉您操作系统级别的套接字可以读取(或写入),但这并不意味着上层 SSL 层有足够的数据。例如,SSL 帧可能只收到一部分。因此,您必须准备好处理 SSLSocket.recv()SSLSocket.send() 失败,并在再次调用 select() 后重试。

  • 反之,由于 SSL 层有自己的帧处理,SSL 套接字可能仍有可供读取的数据,而 select() 并不知道。因此,您应该先调用 SSLSocket.recv() 来清空任何可能存在的数据,然后如果仍然需要,再阻塞于 select() 调用。

    (当然,当使用 poll()selectors 模块中的原语时,也适用类似的规定)

  • SSL 握手本身将是非阻塞的:必须重试 SSLSocket.do_handshake() 方法,直到它成功返回。以下是使用 select() 等待套接字就绪的概要

    while True:
        try:
            sock.do_handshake()
            break
        except ssl.SSLWantReadError:
            select.select([sock], [], [])
        except ssl.SSLWantWriteError:
            select.select([], [sock], [])
    

参见

asyncio 模块支持非阻塞 SSL 套接字,并提供更高级别的流 API。它使用 selectors 模块轮询事件,并处理 SSLWantWriteErrorSSLWantReadErrorBlockingIOError 异常。它还会异步运行 SSL 握手。

内存 BIO 支持

在 3.5 版本加入。

自从 Python 2.6 引入 SSL 模块以来,SSLSocket 类提供了两个相关但不同的功能区域

  • SSL 协议处理

  • 网络 IO

网络 IO API 与 socket.socket 提供的 API 相同,SSLSocket 也继承自它。这允许 SSL 套接字作为常规套接字的即插即用替换,从而非常容易地将 SSL 支持添加到现有应用程序。

将 SSL 协议处理和网络 IO 结合通常效果很好,但在某些情况下不适合。例如,异步 IO 框架希望使用不同于 socket.socket 和内部 OpenSSL 套接字 IO 例程所假设的“基于文件描述符的轮询/选择”(就绪性基础)模型。这主要与 Windows 等平台相关,因为该模型效率不高。为此,提供了一个 SSLSocket 的简化范围变体,称为 SSLObject

class ssl.SSLObject

SSLSocket 的简化范围变体,表示一个不包含任何网络 IO 方法的 SSL 协议实例。该类通常由框架作者使用,他们希望通过内存缓冲区实现 SSL 的异步 IO。

此类实现了 OpenSSL 实现的低级 SSL 对象之上的接口。此对象捕获 SSL 连接的状态,但本身不提供任何网络 IO。IO 需要通过单独的“BIO”对象进行,而 BIO 是 OpenSSL 的 IO 抽象层。

此类没有公共构造函数。必须使用 wrap_bio() 方法创建 SSLObject 实例。此方法将创建 SSLObject 实例并将其绑定到一对 BIO。*incoming* BIO 用于将数据从 Python 传递给 SSL 协议实例,而 *outgoing* BIO 用于将数据反向传递。

以下方法可用

SSLSocket 相比,此对象缺少以下功能

  • 任何形式的网络 IO;recv()send() 只读写底层 MemoryBIO 缓冲区。

  • 没有 do_handshake_on_connect 机制。您必须始终手动调用 do_handshake() 来启动握手。

  • 没有处理 suppress_ragged_eofs。所有违反协议的 EOF 条件都通过 SSLEOFError 异常报告。

  • 方法 unwrap() 的调用不返回任何内容,而对于 SSL 套接字,它会返回底层套接字。

  • SSLContext.set_servername_callback() 传递给 server_name_callback 回调将获得一个 SSLObject 实例,而不是作为其第一个参数的 SSLSocket 实例。

一些与使用 SSLObject 相关的注释

3.7 版更改:SSLObject 实例必须使用 wrap_bio() 创建。在早期版本中,可以直接创建实例。这从未被记录或正式支持。

SSLObject 通过内存缓冲区与外部世界进行通信。类 MemoryBIO 提供了一个可用于此目的的内存缓冲区。它包装了一个 OpenSSL 内存 BIO(基本 IO)对象

class ssl.MemoryBIO

一个内存缓冲区,可用于在 Python 和 SSL 协议实例之间传递数据。

pending

返回当前在内存缓冲区中的字节数。

eof

一个布尔值,指示内存 BIO 当前是否处于文件末尾位置。

read(n=-1, /)

从内存缓冲区读取最多 n 个字节。如果未指定 nn 为负数,则返回所有字节。

write(buf, /)

buf 中的字节写入内存 BIO。buf 参数必须是支持缓冲区协议的对象。

返回值是写入的字节数,该字节数始终等于 buf 的长度。

write_eof()

向内存 BIO 写入 EOF 标记。调用此方法后,调用 write() 是非法的。在读取完缓冲区中当前所有数据后,属性 eof 将变为 true。

SSL 会话

在 3.6 版本加入。

class ssl.SSLSession

session 使用的会话对象。

id
time
timeout
ticket_lifetime_hint
has_ticket

安全考虑

最佳默认值

对于**客户端使用**,如果您对安全策略没有特殊要求,强烈建议您使用 create_default_context() 函数来创建您的 SSL 上下文。它将加载系统的受信任 CA 证书,启用证书验证和主机名检查,并尝试选择合理安全协议和密码套件设置。

例如,您可以使用 smtplib.SMTP 类创建到 SMTP 服务器的可信、安全连接

>>> import ssl, smtplib
>>> smtp = smtplib.SMTP("mail.python.org", port=587)
>>> context = ssl.create_default_context()
>>> smtp.starttls(context=context)
(220, b'2.0.0 Ready to start TLS')

如果连接需要客户端证书,可以使用 SSLContext.load_cert_chain() 添加。

相比之下,如果您直接调用 SSLContext 构造函数来创建 SSL 上下文,默认情况下它不会启用证书验证或主机名检查。如果您这样做,请阅读以下段落以实现良好的安全级别。

手动设置

验证证书

直接调用 SSLContext 构造函数时,默认值为 CERT_NONE。由于它不验证对等方身份,因此可能不安全,尤其是在客户端模式下,大多数情况下您会希望确保您正在通信的服务器的真实性。因此,在客户端模式下,强烈建议使用 CERT_REQUIRED。但是,这本身并不足够;您还必须检查服务器证书(可以通过调用 SSLSocket.getpeercert() 获取)是否与所需服务匹配。对于许多协议和应用程序,服务可以通过主机名标识。当启用 SSLContext.check_hostname 时,此常见检查会自动执行。

3.7 版更改:主机名匹配现在由 OpenSSL 执行。Python 不再使用 match_hostname()

在服务器模式下,如果您想通过 SSL 层验证客户端(而不是使用更高级别的身份验证机制),您还必须指定 CERT_REQUIRED 并类似地检查客户端证书。

协议版本

SSL 版本 2 和 3 被认为是不安全的,因此使用它们很危险。如果您想在客户端和服务器之间实现最大兼容性,建议使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 作为协议版本。SSLv2 和 SSLv3 默认禁用。

>>> client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> client_context.minimum_version = ssl.TLSVersion.TLSv1_3
>>> client_context.maximum_version = ssl.TLSVersion.TLSv1_3

上面创建的 SSL 上下文将只允许与服务器建立 TLSv1.3 及更高版本(如果您的系统支持)的连接。PROTOCOL_TLS_CLIENT 默认启用证书验证和主机名检查。您必须将证书加载到上下文中。

密码套件选择

如果您有高级安全需求,可以通过 SSLContext.set_ciphers() 方法对协商 SSL 会话时启用的密码套件进行精细调整。从 Python 3.2.3 开始,ssl 模块默认禁用某些弱密码套件,但您可能需要进一步限制密码套件的选择。请务必阅读 OpenSSL 关于密码列表格式的文档。如果您想检查给定密码列表启用了哪些密码套件,请使用 SSLContext.get_ciphers() 或系统上的 openssl ciphers 命令。

多进程

如果将此模块用作多进程应用程序的一部分(例如,使用 multiprocessingconcurrent.futures 模块),请注意,OpenSSL 的内部随机数生成器无法正确处理分叉的进程。如果应用程序使用 os.fork() 使用任何 SSL 功能,则必须更改父进程的 PRNG 状态。任何成功的 RAND_add()RAND_bytes() 调用都已足够。

TLS 1.3

在 3.7 版本加入。

TLS 1.3 协议的行为与之前版本的 TLS/SSL 略有不同。一些新的 TLS 1.3 功能尚不可用。

  • TLS 1.3 使用一套独立的密码套件。默认启用所有 AES-GCM 和 ChaCha20 密码套件。方法 SSLContext.set_ciphers() 尚不能启用或禁用任何 TLS 1.3 密码,但 SSLContext.get_ciphers() 会返回它们。

  • 会话票证不再作为初始握手的一部分发送,并且处理方式也不同。 SSLSocket.sessionSSLSession 与 TLS 1.3 不兼容。

  • 客户端证书也不再在初始握手中进行验证。服务器可以随时请求证书。客户端在向服务器发送或接收应用程序数据时会处理证书请求。

  • TLS 1.3 的某些功能,如早期数据、延迟的 TLS 客户端证书请求、签名算法配置和重新密钥,尚不支持。