18.2. ssl — 套接字对象的 TLS/SSL 包装器 — Python 文档

来自菜鸟教程
Python/docs/3.6/library/ssl
跳转至:导航、​搜索

18.2. ssl — 用于套接字对象的 TLS/SSL 包装器

源代码: :source:`Lib/ssl.py`


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

笔记

某些行为可能取决于平台,因为调用的是操作系统套接字 API。 已安装的 OpenSSL 版本也可能导致行为发生变化。 例如,TLSv1.1 和 TLSv1.2 带有 openssl 版本 1.0.1。


警告

不要在未阅读 安全注意事项 的情况下使用此模块。 这样做可能会导致错误的安全感,因为 ssl 模块的默认设置不一定适合您的应用程序。


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

该模块提供了一个类 ssl.SSLSocket,它派生自 socket.socket 类型,并提供了一个类似套接字的包装器,它也对通过套接字的数据进行加密和解密使用 SSL。 它支持其他方法,例如 getpeercert(),它检索连接另一端的证书,以及 cipher(),它检索用于安全连接的密码。

对于更复杂的应用程序,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。


18.2.1. 函数、常量和异常

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

尝试读取或写入时引发 SSLError 的子类并且 SSL 连接已完全关闭。 请注意,这并不意味着底层传输(读取 TCP)已关闭。

3.3 版中的新功能。

exception ssl.SSLWantReadError

SSLError 的子类在尝试读取或写入数据时由 非阻塞 SSL 套接字 引发,但需要在底层 TCP 传输上接收更多数据才能满足请求.

3.3 版中的新功能。

exception ssl.SSLWantWriteError

SSLError 的子类在尝试读取或写入数据时由 非阻塞 SSL 套接字 引发,但需要在底层 TCP 传输上发送更多数据才能满足请求.

3.3 版中的新功能。

exception ssl.SSLSyscallError

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

3.3 版中的新功能。

exception ssl.SSLEOFError

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

3.3 版中的新功能。

exception ssl.CertificateError
引发证书错误(例如主机名不匹配)。 但是,OpenSSL 检测到的证书错误会引发 SSLError

18.2.1.1. 套接字创建

以下函数允许创建独立的套接字。 从 Python 3.2 开始,使用 SSLContext.wrap_socket() 可以更灵活。

ssl.wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)

获取 socket.socket 的实例 sock,并返回 ssl.SSLSocket 的实例,socket.socket 的子类型,它包装SSL 上下文中的底层套接字。 sock 必须是 SOCK_STREAM 套接字; 不支持其他套接字类型。

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

keyfilecertfile 参数指定可选文件,其中包含用于标识连接本地端的证书。 有关证书如何存储在 certfile 中的更多信息,请参阅 Certificates 的讨论。

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

参数 cert_reqs 指定连接的另一端是否需要证书,如果提供,是否将对其进行验证。 它必须是三个值之一 CERT_NONE(忽略证书)、CERT_OPTIONAL(不是必需的,但如果提供则验证)或 CERT_REQUIRED(必需并验证) . 如果此参数的值不是 CERT_NONE,则 ca_certs 参数必须指向 CA 证书文件。

ca_certs 文件包含一组串联的“证书颁发机构”证书,用于验证从连接另一端传递的证书。 有关如何在此文件中排列证书的更多信息,请参阅 Certificates 的讨论。

参数 ssl_version 指定要使用的 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

是的

是的


脚注

1(1,2)

SSLContext 默认使用 OP_NO_SSLv2 禁用 SSLv2。

2(1,2)

SSLContext 默认使用 OP_NO_SSLv3 禁用 SSLv3。

3(1,2)

TLS 1.3 协议将在 OpenSSL >= 1.1.1 中与 PROTOCOL_TLS 一起使用。 仅 TLS 1.3 没有专用的 PROTOCOL 常量。

笔记

哪些连接成功取决于 OpenSSL 的版本。 例如,在 OpenSSL 1.0.0 之前,SSLv23 客户端将始终尝试 SSLv2 连接。

ciphers 参数设置此 SSL 对象的可用密码。 它应该是 OpenSSL 密码列表格式 中的字符串。

参数do_handshake_on_connect指定在做了socket.connect()后是否自动进行SSL握手,或者应用程序是否会通过调用SSLSocket.do_handshake()显式调用它方法。 显式调用 SSLSocket.do_handshake() 使程序可以控制握手中涉及的套接字 I/O 的阻塞行为。

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

3.2 版更改: 新的可选参数 ciphers


18.2.1.2. 上下文创建

一个方便的函数有助于创建 SSLContext 对象用于通用目的。

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

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

cafilecapathcadata 代表可选的 CA 证书以信任证书验证,如 SSLContext.load_verify_locations()。 如果三个都是None,这个函数可以选择信任系统默认的CA证书。

设置为:PROTOCOL_TLSOP_NO_SSLv2OP_NO_SSLv3 具有不带 RC4 和未经身份验证的密码套件的高加密密码套件。 将 SERVER_AUTH 作为 purposeverify_mode 设置为 CERT_REQUIRED 并加载 CA 证书(当至少有 cafile 之一时) , capathcadata 给出)或使用 SSLContext.load_default_certs() 加载默认 CA 证书。

笔记

协议、选项、密码和其他设置可能随时更改为更具限制性的值,而无需事先弃用。 这些值代表了兼容性和安全性之间的公平平衡。

如果您的应用程序需要特定设置,您应该创建一个 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

3.4 版中的新功能。

在 3.4.4 版更改:RC4 从默认密码字符串中删除。

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

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


18.2.1.3. 随机生成

ssl.RAND_bytes(num)

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

对于几乎所有应用程序 os.urandom() 是首选。

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

3.3 版中的新功能。

ssl.RAND_pseudo_bytes(num)

返回(字节,is_cryptographic):字节是num个伪随机字节,如果生成的字节是加密强的,is_cryptographic是True。 如果当前 RAND 方法不支持该操作,则引发 SSLError

如果生成的伪随机字节序列足够长,则它们将是唯一的,但不一定是不可预测的。 它们可用于非加密目的和加密协议中的某些目的,但通常不用于密钥生成等。

对于几乎所有应用程序 os.urandom() 是首选。

3.3 版中的新功能。

自 3.6 版起已弃用:OpenSSL 已弃用 ssl.RAND_pseudo_bytes(),请改用 ssl.RAND_bytes()

ssl.RAND_status()
如果 SSL 伪随机数生成器具有“足够”的随机性,则返回 True,否则返回 False。 您可以使用 ssl.RAND_egd()ssl.RAND_add() 来增加伪随机数生成器的随机性。
ssl.RAND_egd(path)

如果您在某处运行熵收集守护进程 (EGD),并且 path 是对其打开的套接字连接的路径名,则这将从套接字读取 256 字节的随机性,并将其添加到 SSL伪随机数生成器,以增加生成的密钥的安全性。 这通常只在没有更好随机源的系统上才有必要。

有关熵收集守护进程的来源,请参阅 http://egd.sourceforge.net/http://prngd.sourceforge.net/。

可用性:不适用于 LibreSSL 和 OpenSSL > 1.1.0

ssl.RAND_add(bytes, entropy)

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

3.5 版更改:现在接受 可写 字节类对象


18.2.1.4. 证书办理

ssl.match_hostname(cert, hostname)

验证 cert(以 SSLSocket.getpeercert() 返回的解码格式)与给定的 主机名 匹配。 应用的规则是用于检查 HTTPS 服务器身份的规则,如 RFC 2818RFC 5280 ]RFC 6125。 除HTTPS外,此功能应适用于在各种基于SSL的协议(如FTPS、IMAPS、POPS等)中检查服务器的身份。

CertificateError 在失败时引发。 成功时,该函数不返回任何内容:

>>> cert = {'subject': ((('commonName', 'example.com'),),)}
>>> ssl.match_hostname(cert, "example.com")
>>> ssl.match_hostname(cert, "example.org")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/py3k/Lib/ssl.py", line 130, in match_hostname
ssl.CertificateError: hostname 'example.org' doesn't match 'example.com'

3.2 版中的新功能。

在 3.3.3 版中更改:该函数现在遵循 RFC 6125,第 6.4.3 节并且不匹配多个通配符(例如 *.*.com*a*.example.org) 或国际化域名 (IDN) 片段中的通配符。 www*.xn--pthon-kva.org 等 IDN A 标签仍受支持,但 x*.python.org 不再匹配 xn--tda.python.org

在 3.5 版中更改:现在支持 IP 地址匹配(如果出现在证书的 subjectAltName 字段中)。

ssl.cert_time_to_seconds(cert_time)

返回自纪元以来的时间(以秒为单位),给定 cert_time 字符串,表示 "%b %d %H:%M:%S %Y %Z" strptime 格式(C 语言环境)中证书的“notBefore”或“notAfter”日期。

下面是一个例子:

>>> 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 版中更改: 将输入时间解释为输入字符串中“GMT”时区指定的 UTC 时间。 以前使用本地时区。 返回一个整数(输入格式中没有秒的小数部分)

ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None)

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

3.3 版变更:此功能现已兼容 IPv6。

3.5 版更改: 默认 ssl_versionPROTOCOL_SSLv3 更改为 PROTOCOL_TLS,以最大程度地与现代服务器兼容。

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 - OpenSSL 的环境密钥,指向一个 capath,

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

可用性:LibreSSL 忽略环境变量 openssl_cafile_envopenssl_capath_env

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 将证书的用途指定为一组 OIDS 或完全 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 版中的新功能。


18.2.1.5. 常数

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

3.6 版中的新功能。


ssl.CERT_NONE

SSLContext.verify_mode 的可能值,或 wrap_socket()cert_reqs 参数。 除 PROTOCOL_TLS_CLIENT 外,均为默认模式。 使用客户端套接字,几乎可以接受任何证书。 验证错误,例如不受信任或过期的证书,将被忽略并且不会中止 TLS/SSL 握手。

在服务器模式下,客户端不会请求任何证书,因此客户端不会发送任何用于客户端证书身份验证的证书。

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

ssl.CERT_OPTIONAL

SSLContext.verify_mode 的可能值,或 wrap_socket()cert_reqs 参数。 在客户端模式下,CERT_OPTIONALCERT_REQUIRED 的含义相同。 建议将 CERT_REQUIRED 用于客户端套接字。

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

使用此设置需要将一组有效的 CA 证书传递给 SSLContext.load_verify_locations() 或作为 wrap_socket()ca_certs 参数的值]。

ssl.CERT_REQUIRED

SSLContext.verify_mode 的可能值,或 wrap_socket()cert_reqs 参数。 在这种模式下,需要套接字连接另一端的证书; 如果未提供证书或验证失败,则会引发 SSLError。 此模式 足以验证客户端模式下的证书,因为它与主机名不匹配。 check_hostname 也必须启用以验证证书的真实性。 PROTOCOL_TLS_CLIENT 使用 CERT_REQUIRED 并默认启用 check_hostname

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

使用此设置需要将一组有效的 CA 证书传递给 SSLContext.load_verify_locations() 或作为 wrap_socket()ca_certs 参数的值]。

class ssl.VerifyMode

enum.IntEnum CERT_* 常量集合。

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,验证将失败。

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_X509_TRUSTED_FIRST

SSLContext.verify_flags 的可能值。 它指示 OpenSSL 在构建信任链以验证证书时更喜欢受信任的证书。 默认情况下启用此标志。

版本 3.4.4 中的新功能。

class ssl.VerifyFlags

enum.IntFlag VERIFY_* 常量的集合。

3.6 版中的新功能。

ssl.PROTOCOL_TLS

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

3.6 版中的新功能。

ssl.PROTOCOL_TLS_CLIENT

自动协商最高协议版本,如 PROTOCOL_TLS,但仅支持客户端 SSLSocket 连接。 该协议默认启用 CERT_REQUIREDcheck_hostname

3.6 版中的新功能。

ssl.PROTOCOL_TLS_SERVER

自动协商最高协议版本,如 PROTOCOL_TLS,但仅支持服务器端 SSLSocket 连接。

3.6 版中的新功能。

ssl.PROTOCOL_SSLv23

数据别名:PROTOCOL_TLS。

自 3.6 版起已弃用: 改用 PROTOCOL_TLS

ssl.PROTOCOL_SSLv2

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

如果使用 OPENSSL_NO_SSL2 标志编译 OpenSSL,则此协议不可用。

警告

SSL 版本 2 不安全。 非常不鼓励使用它。

自 3.6 版起已弃用:OpenSSL 已删除对 SSLv2 的支持。

ssl.PROTOCOL_SSLv3

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

如果使用 OPENSSL_NO_SSLv3 标志编译 OpenSSL,则此协议不可用。

警告

SSL 版本 3 不安全。 非常不鼓励使用它。

自 3.6 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议 PROTOCOL_TLS

ssl.PROTOCOL_TLSv1

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

自 3.6 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议 PROTOCOL_TLS

ssl.PROTOCOL_TLSv1_1

选择 TLS 1.1 版作为通道加密协议。 仅适用于 openssl 版本 1.0.1+。

3.4 版中的新功能。

自 3.6 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议 PROTOCOL_TLS

ssl.PROTOCOL_TLSv1_2

选择 TLS 1.2 版作为通道加密协议。 这是最现代的版本,如果双方都可以说的话,这可能是最大程度保护的最佳选择。 仅适用于 openssl 版本 1.0.1+。

3.4 版中的新功能。

自 3.6 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议 PROTOCOL_TLS

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 版中的新功能。

ssl.OP_NO_TLSv1_1

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

3.4 版中的新功能。

ssl.OP_NO_TLSv1_2

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

3.4 版中的新功能。

ssl.OP_NO_TLSv1_3

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

版本 3.6.3 中的新功能。

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 握手中发送虚拟更改密码规范 (CCS) 消息,使 TLS 1.3 连接看起来更像 TLS 1.2 连接。

此选项仅适用于 OpenSSL 1.1.1 及更高版本。

3.6.7 版中的新功能。

ssl.OP_NO_COMPRESSION

禁用 SSL 通道上的压缩。 如果应用程序协议支持其自己的压缩方案,这将非常有用。

此选项仅适用于 OpenSSL 1.0.0 及更高版本。

3.3 版中的新功能。

class ssl.Options
enum.IntFlag OP_* 常量的集合。
ssl.OP_NO_TICKET

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

3.6 版中的新功能。

ssl.HAS_ALPN

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

3.5 版中的新功能。

ssl.HAS_ECDH

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

3.3 版中的新功能。

ssl.HAS_SNI

OpenSSL 库是否具有对 服务器名称指示 扩展的内置支持(如 RFC 6066 中所定义)。

3.2 版中的新功能。

ssl.HAS_NPN

OpenSSL 库是否内置了对 Next Protocol Negotiation 的支持,如 NPN 草案规范 中所述。 当为真时,您可以使用 SSLContext.set_npn_protocols() 方法来通告您想要支持的协议。

3.3 版中的新功能。

ssl.HAS_TLSv1_3

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

版本 3.6.3 中的新功能。

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 Alert Registry 包含此列表和对定义其含义的 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 版中的新功能。


18.2.2. 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() 包装套接字。

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

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

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

如果套接字是 非阻塞 并且读取将阻塞,则提高 SSLWantReadErrorSSLWantWriteError

由于任何时候都可能进行重新协商,因此对 read() 的调用也可能导致写入操作。

3.5 版更改: 每次接收或发送字节时,套接字超时不再重置。 套接字超时现在是最大总持续时间,最多可读取 len 个字节。

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

SSLSocket.write(buf)

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

如果套接字是 非阻塞 并且写入会阻塞,则提高 SSLWantReadErrorSSLWantWriteError

由于任何时候都可能进行重新协商,因此对 write() 的调用也可能导致读取操作。

3.5 版更改: 每次接收或发送字节时,套接字超时不再重置。 套接字超时现在是写入 buf 的最大总持续时间。

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

笔记

read()write() 方法是读取和写入未加密的应用程序级数据并将其解密/加密为加密的有线级数据的低级方法。 这些方法需要一个有效的 SSL 连接,即 握手已完成且未调用 SSLSocket.unwrap()

通常你应该使用像 recv()send() 这样的套接字 API 方法而不是这些方法。


SSLSocket.do_handshake()

执行 SSL 设置握手。

3.4 版本更改: 当socket 的contextcheck_hostname 属性为真时,握手方法也会执行match_hostname()

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

SSLSocket.getpeercert(binary_form=False)

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

如果 binary_form 参数为 False,并且从对等方收到证书,则此方法返回 dict 实例。 如果证书未经过验证,则 dict 为空。 如果证书被验证,它会返回一个带有几个键的字典,其中包括 subject(颁发证书的主体)和 issuer(颁发证书的主体)。 如果证书包含 主题备用名称 扩展的实例(请参阅 RFC 3280),则在字典。

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}

笔记

要验证特定服务的证书,您可以使用 match_hostname() 函数。

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

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

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

3.2 版本更改: 返回的字典包括额外的项目,例如 issuernotBefore

在 3.4 版更改:ValueError 在未完成握手时引发。 返回的字典包括额外的 X509v3 扩展项,例如 crlDistributionPointscaIssuersOCSP URI。

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,如果这个 socket 不支持任何客户端提出的协议,或者如果握手还没有发生,None 返回。

3.5 版中的新功能。

SSLSocket.selected_npn_protocol()

返回在 TLS/SSL 握手期间选择的更高级别的协议。 如果 SSLContext.set_npn_protocols() 没有被调用,或者对方不支持 NPN,或者握手还没有发生,这将返回 None

3.3 版中的新功能。

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

3.6.7 版中的新功能。

笔记

仅在启用 OpenSSL 1.1.1 和 TLS 1.3 时可用。 如果没有 TLS 1.3 支持,该方法会引发 NotImplementedError

SSLSocket.version()

以字符串形式返回连接协商的实际 SSL 协议版本,否则 None 未建立安全连接。 在撰写本文时,可能的返回值包括 "SSLv2""SSLv3""TLSv1""TLSv1.1""TLSv1.2"。 最近的 OpenSSL 版本可能会定义更多的返回值。

3.5 版中的新功能。

SSLSocket.pending()
返回可用于读取的已解密字节数,等待连接。
SSLSocket.context

此 SSL 套接字绑定到的 SSLContext 对象。 如果 SSL 套接字是使用顶级 wrap_socket() 函数(而不是 SSLContext.wrap_socket())创建的,则这是为此 SSL 套接字创建的自定义上下文对象。

3.2 版中的新功能。

SSLSocket.server_side

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

3.2 版中的新功能。

SSLSocket.server_hostname

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

3.2 版中的新功能。

SSLSocket.session

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

3.6 版中的新功能。

SSLSocket.session_reused

3.6 版中的新功能。


18.2.3. SSL 上下文

3.2 版中的新功能。


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

class ssl.SSLContext(protocol=PROTOCOL_TLS)

创建新的 SSL 上下文。 您可以传递 protocol,它必须是此模块中定义的 PROTOCOL_* 常量之一。 PROTOCOL_TLS 目前推荐用于最大互操作性和默认值。

也可以看看

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

在 3.6 版更改:使用安全默认值创建上下文。 选项 OP_NO_COMPRESSIONOP_CIPHER_SERVER_PREFERENCEOP_SINGLE_DH_USE、OP_SINGLE_ECDH_USE[X127X1X18X1200X100X100X100X100X100X100X100X1000X1000X100X1000X1000X1000X1000X8 ]) 和 OP_NO_SSLv3PROTOCOL_SSLv3 除外)是默认设置的。 初始密码套件列表仅包含 HIGH 密码,没有 NULL 密码和 MD5 密码(PROTOCOL_SSLv2 除外)。

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

SSLContext.cert_store_stats()

获取有关加载的 X.509 证书数量、标记为 CA 证书的 X.509 证书计数以及作为字典的证书吊销列表的统计信息。

具有一个 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 中的更多信息,请参阅 Certificates 的讨论。

password 参数可以是一个函数,用于获取解密私钥的密码。 只有在私钥被加密并且需要密码时才会调用它。 它将在不带参数的情况下被调用,它应该返回一个字符串、字节或字节数组。 如果返回值是一个字符串,它将在使用它解密密钥之前被编码为 UTF-8。 或者,字符串、字节或字节数组值可以直接作为 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)

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

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

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

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

cadata 对象(如果存在)是一个或多个 PEM 编码证书的 ASCII 字符串或 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()  # OpenSSL 1.0.x
[{'alg_bits': 256,
  'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(256) Mac=AEAD',
  'id': 50380848,
  'name': 'ECDHE-RSA-AES256-GCM-SHA384',
  'protocol': 'TLSv1/SSLv3',
  'strength_bits': 256},
 {'alg_bits': 128,
  'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(128) Mac=AEAD',
  'id': 50380847,
  'name': 'ECDHE-RSA-AES128-GCM-SHA256',
  'protocol': 'TLSv1/SSLv3',
  'strength_bits': 128}]
在 OpenSSL 1.1 和更高版本上,密码字典包含其他字段:
>>> ctx.get_ciphers()  # OpenSSL 1.1+
[{'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'}]

可用性:OpenSSL 1.0.2+

3.6 版中的新功能。

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

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

笔记

连接后,SSL 套接字的 SSLSocket.cipher() 方法将给出当前选择的密码。

OpenSSL 1.1.1 默认启用了 TLS 1.3 密码套件。 无法使用 set_ciphers() 禁用套件。

SSLContext.set_alpn_protocols(protocols)

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

如果 HAS_ALPN 为 False,此方法将引发 NotImplementedError

OpenSSL 1.1.0 到 1.1.0e 将中止握手并在双方都支持 ALPN 但无法就协议达成一致时引发 SSLError。 1.1.0f+ 的行为类似于 1.0.2,SSLSocket.selected_alpn_protocol() 返回 None。

3.5 版中的新功能。

SSLContext.set_npn_protocols(protocols)

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

如果 HAS_NPN 为 False,此方法将引发 NotImplementedError

3.3 版中的新功能。

SSLContext.set_servername_callback(server_name_callback)

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

每个SSLContext只能设置一个回调。 如果 server_name_callbackNone,则回调被禁用。 后续调用此函数将禁用先前注册的回调。

回调函数 server_name_callback 将使用三个参数调用; 第一个是 ssl.SSLSocket,第二个是表示客户端打算通信的服务器名称的字符串(如果 TLS Client Hello 不包含服务器,则为 None名称),第三个参数是原始的 SSLContext。 服务器名称参数是 IDNA 解码的服务器名称。

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

由于 TLS 连接的早期协商阶段,只有有限的方法和属性可用,如 SSLSocket.selected_alpn_protocol()SSLSocket.contextSSLSocket.getpeercert(), SSLSocket.getpeercert(), SSLSocket.cipher()SSLSocket.compress() 方法要求 TLS 连接已取得进展超出 TLS 客户端 Hello,因此不会包含返回有意义的值,也不能安全地调用它们。

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

如果服务器名称存在 IDNA 解码错误,TLS 连接将终止,并向客户端发送 ALERT_DESCRIPTION_INTERNAL_ERROR 致命 TLS 警报消息。

如果从 server_name_callback 函数引发异常,TLS 连接将终止并显示致命的 TLS 警报消息 ALERT_DESCRIPTION_HANDSHAKE_FAILURE

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

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 和完美的前向保密

文森特·伯纳特。


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

包装一个现有的 Python 套接字 sock 并返回一个 SSLSocket 对象。 sock 必须是 SOCK_STREAM 套接字; 不支持其他套接字类型。

返回的 SSL 套接字与上下文、其设置和证书相关联。 参数 server_sidedo_handshake_on_connectsuppress_ragged_eofs 与顶层 wrap_socket() 函数的含义相同。

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

会话,见会话

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

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

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

通过包装 BIO 对象 incomingoutgoing,创建一个新的 SSLObject 实例。 SSL 例程将从传入 BIO 读取输入数据并将数据写入传出 BIO。

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

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

SSLContext.session_stats()

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

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

是否将对等证书的主机名与 SSLSocket.do_handshake() 中的 match_hostname() 匹配。 上下文的 verify_mode 必须设置为 CERT_OPTIONALCERT_REQUIRED,并且必须将 server_hostname 传递给 Xwrap_socket3(X146X]Xwrap_socket3(X76X)为了匹配主机名。

例子:

import socket, ssl

context = ssl.SSLContext()
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 版中的新功能。

笔记

此功能需要 OpenSSL 0.9.8f 或更新版本。

SSLContext.options

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

笔记

对于低于 0.9.8m 的 OpenSSL 版本,只能设置选项,不能清除它们。 尝试清除选项(通过重置相应位)将引发 ValueError

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

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

启用 TLS 1.3 握手后客户端身份验证。 默认情况下禁用握手后身份验证,服务器只能在初始握手期间请求 TLS 客户端证书。 启用后,服务器可以在握手后随时请求 TLS 客户端证书。

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

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

3.6.7 版中的新功能。

笔记

仅在启用 OpenSSL 1.1.1 和 TLS 1.3 时可用。 没有TLS 1.3支持,属性值为None,不可修改

SSLContext.protocol
构建上下文时选择的协议版本。 该属性是只读的。
SSLContext.verify_flags

证书验证操作的标志。 您可以通过将它们组合在一起来设置像 VERIFY_CRL_CHECK_LEAF 这样的标志。 默认情况下,OpenSSL 既不需要也不需要验证证书吊销列表 (CRL)。 仅适用于 openssl 版本 0.9.8+。

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>

18.2.4. 证书

证书通常是公钥/私钥系统的一部分。 在这个系统中,每个principal(可能是一台机器、一个人或一个组织)都被分配了一个唯一的两部分加密密钥。 密钥的一部分是公开的,称为公钥; 另一部分保密,称为私钥。 这两个部分是相关的,因为如果您用其中一个部分加密消息,您可以用另一部分解密,而 与另一部分。

一个证书包含有关两个主体的信息。 它包含 主题 的名称和主题的公钥。 它还包含第二个主体 发行者 的声明,该主体是他们声称的身份,并且这确实是该主体的公钥。 发行人的声明是用发行人的私钥签名的,只有发行人知道。 但是,任何人都可以通过找到发行者的公钥、用它解密声明并将其与证书中的其他信息进行比较来验证发行者的声明。 该证书还包含有关其有效时间段的信息。 这表示为两个字段,称为“notBefore”和“notAfter”。

在 Python 中使用证书时,客户端或服务器可以使用证书来证明他们是谁。 网络连接的另一端也可能需要生成证书,并且可以验证该证书以使需要此类验证的客户端或服务器满意。 如果验证失败,可以将连接尝试设置为引发异常。 验证由底层 OpenSSL 框架自动完成; 应用程序不需要关心它的机制。 但是应用程序通常需要提供证书集来允许这个过程发生。

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

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

18.2.4.1. 证书链

包含证书的 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-----

18.2.4.2. CA证书

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


18.2.4.3. 组合密钥和证书

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

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

18.2.4.4. 自签名证书

如果要创建提供 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
%

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


18.2.5. 例子

18.2.5.1. 测试 SSL 支持

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

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

18.2.5.2. 客户端操作

此示例使用客户端套接字的推荐安全设置创建 SSL 上下文,包括自动证书验证:

>>> context = ssl.create_default_context()

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

>>> context = ssl.SSLContext()
>>> context.verify_mode = ssl.CERT_REQUIRED
>>> context.check_hostname = True
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")

(此代码段假定您的操作系统将所有 CA 证书的捆绑包放置在 /etc/ssl/certs/ca-bundle.crt 中;否则,您将收到错误并必须调整位置)

当您使用上下文连接到服务器时,CERT_REQUIRED 验证服务器证书:它确保服务器证书是使用 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'']

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


18.2.5.3. 服务端操作

对于服务器操作,通常您需要在一个文件中拥有一个服务器证书和私钥。 您将首先创建一个包含密钥和证书的上下文,以便客户端可以检查您的真实性。 然后您将打开一个套接字,将其绑定到一个端口,在其上调用 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.mydomain.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

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


18.2.6. 关于非阻塞套接字的注意事项

SSL 套接字的行为与非阻塞模式下的常规套接字略有不同。 使用非阻塞套接字时,您需要注意以下几点:

  • 如果 I/O 操作会阻塞,大多数 SSLSocket 方法将引发 SSLWantWriteErrorSSLWantReadError 而不是 BlockingIOErrorSSLWantReadError 如果需要对底层套接字进行读操作,将引发 SSLWantWriteError 对底层套接字进行写操作。 请注意,尝试向 SSL 套接字 写入 可能需要首先从底层套接字 读取 ,而尝试从 SSL 套接字 读取 可能需要事先 ]write 到底层套接字。

    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 模块支持 [X37X] 非阻塞 SSL 套接字 并提供更高级别的 API。 它使用 selectors 模块轮询事件并处理 SSLWantWriteErrorSSLWantReadErrorBlockingIOError 异常。 它也异步运行 SSL 握手。


18.2.7. 内存生物支持

3.5 版中的新功能。


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

  • SSL 协议处理
  • 网络IO

网络 IO API 与 socket.socket 提供的相同,SSLSocket 也从中继承。 这允许使用 SSL 套接字作为常规套接字的直接替代品,从而非常容易地向现有应用程序添加 SSL 支持。

将 SSL 协议处理和网络 IO 结合使用通常效果很好,但在某些情况下则不然。 一个例子是异步 IO 框架想要使用不同的 IO 多路复用模型,而不是由 socket.socket 和内部 OpenSSL 套接字假定的“文件描述符上的选择/轮询”(基于就绪性)模型IO 例程。 这主要与此模型效率不高的 Windows 等平台相关。 为此,提供了 SSLSocket 的缩小范围变体,称为 SSLObject

class ssl.SSLObject

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

此类在 OpenSSL 实现的低级 SSL 对象之上实现了一个接口。 该对象捕获 SSL 连接的状态,但本身不提供任何网络 IO。 IO 需要通过单独的“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。 所有违反协议的文件结束条件都通过 SSLEOFError 异常报告。

  • 方法 unwrap() 调用不返回任何内容,这与它返回底层套接字的 SSL 套接字不同。

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

使用 SSLObject 的一些注意事项:

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

class ssl.MemoryBIO

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

pending

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

eof

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

read(n=- 1)

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

write(buf)

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

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

write_eof()

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


18.2.8. SSL 会话

3.6 版中的新功能。


class ssl.SSLSession

session 使用的会话对象。

id
time
timeout
ticket_lifetime_hint
has_ticket


18.2.9. 安全考虑

18.2.9.1. 最佳默认值

对于客户端使用,如果您对您的安全策略没有任何特殊要求,强烈建议您使用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 上下文,则默认情况下不会启用证书验证或主机名检查。 如果您这样做,请阅读以下段落以达到良好的安全级别。


18.2.9.2. 手动设置

18.2.9.2.1。 验证证书

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

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


18.2.9.2.2. 协议版本

SSL 版本 2 和 3 被认为是不安全的,因此使用起来很危险。 如果您希望客户端和服务器之间具有最大的兼容性,建议使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 作为协议版本。 默认情况下禁用 SSLv2 和 SSLv3。

>>> client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> client_context.options |= ssl.OP_NO_TLSv1
>>> client_context.options |= ssl.OP_NO_TLSv1_1

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


18.2.9.2.3。 密码选择

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


18.2.9.3. 多处理

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


18.2.10. LibreSSL 支持

LibreSSL 是 OpenSSL 1.0.1 的一个分支。 ssl 模块对 LibreSSL 的支持有限。 使用 LibreSSL 编译 ssl 模块时,某些功能不可用。