ssl --- socket 物件的 TLS/SSL 包裝器

原始碼:Lib/ssl.py


這個模組向客戶端及伺服器端提供了對於網路 socket 的傳輸層安全性協定(或稱為「安全通訊協定 (Secure Sockets Layer)」)加密及身分驗證功能。這個模組使用 OpenSSL 套件,它可以在所有的 Unix 系統、Windows、macOS、以及其他任何可能的平台上使用,只要事先在該平台上安裝 OpenSSL。

備註

由於呼叫了作業系統的 socket APIs,有些行為會根據平台而有所不同。OpenSSL 的安裝版本也會對模組的運作產生影響。例如,OpenSSL 版本 1.1.1 附帶 TLSv1.3。

警告

在使用此模組之前,請閱讀 安全考量。如果不這樣做,可能會產生錯誤的安全性認知,因為 ssl 模組的預設設定未必適合你的應用程式。

適用: 非 Emscripten、非 WASI。

此模組在 WebAssembly 平台 wasm32-emscriptenwasm32-wasi 上無法作用或無法使用。有關更多資訊,請參閱 WebAssembly 平台

這個章節記錄了 ssl 模組的物件及函式;關於 TSL、SSL、以及憑證的更多資訊,可以去參考此章節底部的「詳情」部分。

此模組提供了一個 ssl.SSLSocket 類別,它是從 socket.socket 衍生出來的,並且提供類似 socket 的包裝器,讓使用 SSL 進行資料傳輸時,可以進行資料的加密及解密。它也提供了一些額外的方法,如 getpeercert(),用於取得連結另一端的憑證,以及 cipher(),用於搜尋用於安全連接的密碼 (cipher)。

對於更複雜的應用程式,ssl.SSLContext 類別有助於管理設定及認證,然後可以透過 SSLContext.wrap_socket() 方法建立的 SSL socket 繼承這些設定和認證。

在 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 以上的版本才能使用。

使用已經被棄用的常數或函式將會導致棄用警示。

函式、常數與例外

Socket 建立

SSLSocket 實例必須使用 SSLContext.wrap_socket() 方法來建立。輔助函式 create_default_context() 會回傳有安全預設設定的新語境 (context)。

使用預設語境及 IPv4/IPv6 雙協定堆疊的客戶端 socket 範例:

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 的客戶端 socket範例:

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())

在本地 IPv4 上監聽伺服器 socket 的範例:

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 有更高的安全性。

cafile, capath, cadata 是用來選擇用於憑證認證的 CA 憑證,就像 SSLContext.load_verify_locations() 一樣。如果三個值都是 None,此函式會自動選擇系統預設的 CA 憑證。

這些設定包含:PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVEROP_NO_SSLv2、以及 OP_NO_SSLv3,使用高加密密碼套件但不包含 RC4 和未經身份驗證的密碼套件。如果將 purpose 設定為 SERVER_AUTH,則會把 verify_mode 設為 CERT_REQUIRED 並使用設定的 CA 憑證(當 cafilecapathcadata 其中一個值有被設定時) 或使用預設的 CA 憑證 SSLContext.load_default_certs()

當系統有支援 keylog_filename 並且有設定環境變數 SSLKEYLOGFILEcreate_default_context() 會啟用密鑰日誌記錄 (logging)。

備註

協定、選項、密碼和其它設定可以在不捨棄舊值的情況下直接更改成新的值,這些值代表了在相容性和安全性之間取得的合理平衡。

如果你的應用程式需要特殊的設定,你應該要自行建立一個 SSLContext 並自行調整設定。

備註

如果您發現某些舊的客戶端或伺服器常適用此函式建立的 SSLContext 連線時,收到 "Protocol or cipher suite mismatch" 錯誤,這可能是因為他們的系統僅支援 SSL3.0,然而 SSL3.0 已被此函式用 OP_NO_SSLv3 排除。目前廣泛認為 SSL3.0 已經被完全破解。如果您仍然希望在允許 SSL3.0 連線的情況下使用此函式,可以使用下面的方法:

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

Added in version 3.4.

在 3.4.4 版的變更: 把 RC4 從預設密碼字串中捨棄。

在 3.6 版的變更: 把 ChaCha20/Poly1305 加入預設密碼字串。

把 3DES 從預設密碼字串中捨棄。

在 3.8 版的變更: 增加了 SSLKEYLOGFILE 對密鑰日誌記錄 (logging) 的支援。

在 3.10 版的變更: 當前語境使用 PROTOCOL_TLS_CLIENT 協定或 PROTOCOL_TLS_SERVER 協定而非通用的 PROTOCOL_TLS

例外

exception ssl.SSLError

引發由底層 SSL 實作(目前由 OpenSSL 函式庫提供)所引發的錯誤訊息。這表示在覆蓋底層網路連線的高階加密和身份驗證層中存在一些問題。這項錯誤是 OSError 的一個子型別。SSLError 實例的錯誤程式代碼和訊息是由 OpenSSL 函式庫提供。

在 3.3 版的變更: SSLError 曾經是 socket.error 的一個子型別。

library

一個字串符號 (string mnemonic),用來指定發生錯誤的 OpenSSL 子模組,如:SSLPEMX509。可能值的範圍取決於 OpenSSL 的版本。

Added in version 3.3.

reason

一個字串符號,用來指定發生錯誤的原因,如:CERTIFICATE_VERIFY_FAILED。可能值的範圍取決於 OpenSSL 的版本。

Added in version 3.3.

exception ssl.SSLZeroReturnError

一個 SSLError 的子類別,當嘗試去讀寫已經被完全關閉的 SSL 連線時會被引發。請注意,這並不表示底層傳輸(例如 TCP)已經被關閉。

Added in version 3.3.

exception ssl.SSLWantReadError

一個 SSLError 的子類別,當嘗試去讀寫資料前,底層 TCP 傳輸需要先接收更多資料時會由非阻塞的 SSL socket 引發該錯誤。

Added in version 3.3.

exception ssl.SSLWantWriteError

一個 SSLError 的子類別,當嘗試去讀寫資料前,底層 TCP 傳輸需要先發送更多資料時會由非阻塞的 SSL socket 引發該錯誤。

Added in version 3.3.

exception ssl.SSLSyscallError

一個 SSLError 的子類別,當嘗試去操作 SSL socket 時有系統錯誤產生會引發此錯誤。不幸的是,目前沒有任何簡單的方法可以去檢查原本的的 errno 編號。

Added in version 3.3.

exception ssl.SSLEOFError

一個 SSLError 的子類別,當 SSL 連線被突然終止時會引發此錯誤。通常,當此錯誤發生時,你不該再去重新使用底層傳輸。

Added in version 3.3.

exception ssl.SSLCertVerificationError

當憑證驗證失敗時會引發的一個 SSLError 子類別。

Added in version 3.7.

verify_code

一個表示驗證錯誤的錯誤數值編號。

verify_message

一個人類可讀的驗證錯誤字串。

exception ssl.CertificateError

SSLCertVerificationError 的別名。

在 3.7 版的變更: 此例外現在是 SSLCertVerificationError 的別名。

隨機產生

ssl.RAND_bytes(num)

回傳 num 個加密性強的偽隨機位元組。如果 PRNG 未使用足夠的資料做為隨機種子 (seed) 或是目前的 RAND 方法不支持該操作則會導致 SSLError 錯誤。RAND_status() 函式可以用來檢查 PRNG 函式,而 RAND_add() 則可以用來為 PRNG 設定隨機種子。

在幾乎所有的應用程式中,os.urandom() 會是較好的選擇。

請閱讀維基百科的密碼學安全偽隨機數產生器 (CSPRNG)文章來了解密碼學安全偽隨機數產生器的需求。

Added in version 3.3.

ssl.RAND_status()

如果 SSL 偽隨機數產生器已經使用「足夠的」隨機性進行隨機種子生成,則回傳 True ,否則回傳 False。你可以使用 ssl.RAND_egd() 函式和 ssl.RAND_add() 函式來增加偽隨機數產生器的隨機性。

ssl.RAND_add(bytes, entropy)

將給定的 bytes 混進 SSL 隨機偽隨機數產生器中。 entropy 參數(float 值)是指字串中包含熵值的下限(因此你可以將其設為 0.0)。請參閱 RFC 1750 了解有關熵源的更多資訊。

在 3.5 版的變更: 可寫入的類位元組物件現在可被接受。

認證處理

ssl.cert_time_to_seconds(cert_time)

回傳自紀元以來的秒數,給定的 cert_time 字串表示憑證的 "notBefore" 或 "notAfter" 日期,字串採用 "%b %d %H:%M:%S %Y %Z" 格式(C 語言區域設定)。

以下是一個範例:

>>> 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,輸入形式為一個 pair (hostname, port-number),獲取該伺服器的憑證,並以 PEM 編碼字串的形式回傳。如果指定了 ssl_version,則使用指定的 SSL 協議來嘗試與伺服器連線。如果指定 ca_certs,則它應該是一個包含根憑證列表的檔案,並與 SSLContext.load_verify_locations() 中的參數 cafile 所使用的格式相同。此呼叫將嘗試使用該組根憑證對伺服器憑證進行驗證,如果驗證失敗,呼叫將失敗。可以使用 timeout 參數指定超時時間。

在 3.3 版的變更: 此函式現在是與 IPv6 相容的。

在 3.5 版的變更: 預設的 ssl_version 已經從 PROTOCOL_SSLv3 改為 PROTOCOL_TLS,已確保與現今的伺服器有最大的相容性。

在 3.10 版的變更: 新增 timeout 參數。

ssl.DER_cert_to_PEM_cert(DER_cert_bytes)

給定一個以 DER 編碼的位元組 blob 作為憑證,回傳以 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() 使用的相同。回傳值是一個 named tuple DefaultVerifyPaths

  • cafile - 解析後的 cafile 路徑,如果檔案不存在則為 None

  • capath - 解析後的 capath 路徑,如果目錄不存在則為 None

  • openssl_cafile_env - 指向 cafile 的 OpenSSL 環境密鑰,

  • openssl_cafile - hard coded 的 cafile 路徑,

  • openssl_capath_env - 指向 capath 的 OpenSSL 環境密鑰,

  • openssl_capath - hard coded 的 capath 目錄路徑

Added in version 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。

Added in version 3.4.

ssl.enum_crls(store_name)

從 Windows 的系統憑證儲存庫中搜尋 CRLs。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。

Added in version 3.4.

常數

所有的常數現在都是 enum.IntEnumenum.IntFlag 的集合。

Added in version 3.6.

ssl.CERT_NONE

SSLContext.verify_mode 可能的值。除了 SSLContext.verify_mode 外,這是預設的模式。對於客戶端的 sockets,幾乎任何憑證都能被允許。驗證錯誤,像是不被信任或是過期的憑證,會被忽略並不會中止 TLS/SSL 握手。

在伺服器模式下,不會從客戶端請求任何憑證,所以客戶端不用發送任何用於客戶端憑證身分驗證的憑證。

參閱下方 安全考量 的討論。

ssl.CERT_OPTIONAL

SSLContext.verify_mode 可能的值。在客戶端模式下,CERT_OPTIONAL 具有與 CERT_REQUIRED 相同的含意。對於客戶端 sockets 推薦改用 CERT_REQUIRED

在伺服器模式下,客戶憑證請求會被發送給客戶端。客戶端可以選擇忽略請求或是選擇發送憑證來執行 TLS 客戶端憑證身分驗證。如果客戶端選擇發送憑證,則會對其進行驗證。任何驗證錯誤都會立刻終止 TLS 握手。

使用此設定需要將一組有效的 CA 憑證傳送給 SSLContext.load_verify_locations()

ssl.CERT_REQUIRED

SSLContext.verify_mode 可能的值。在這個模式下,需要從 socket 連線的另一端獲取憑證;如果未提供憑證或是驗證失敗,則將會導致 SSLError。此模式 不能 在客戶端模式下對憑證進行驗證,因為它無法去配對主機名稱。check_hostname 也必須被開起來來驗證憑證的真實性。PROTOCOL_TLS_CLIENT 會使用 CERT_REQUIRED 並預設開啟 check_hostname

對於 socket 伺服器,此模式會提供強制的 TLS 客戶端憑證驗證。客戶端憑證請求會被發送給客戶端並且客戶端必須提供有效且被信任的憑證。

使用此設定需要將一組有效的 CA 憑證傳送給 SSLContext.load_verify_locations()

class ssl.VerifyMode

enum.IntEnum 為 CERT_* 常數的一個集合。

Added in version 3.6.

ssl.VERIFY_DEFAULT

SSLContext.verify_flags 可能的值。在此模式下,不會檢查憑證吊銷列表 (CRLs)。預設的 OpenSSL 並不會請求及驗證 CRLs。

Added in version 3.4.

ssl.VERIFY_CRL_CHECK_LEAF

SSLContext.verify_flags 可能的值。在此模式下,只會檢查同等的憑證而不會去檢查中間的 CA 憑證。此模式需要提供由對等憑證發行者 (它的直接上級 CA) 的有效的 CRL 簽名。如果沒有用 SSLContext.load_verify_locations 載入適當的 CRL,則會驗證失敗。

Added in version 3.4.

ssl.VERIFY_CRL_CHECK_CHAIN

SSLContext.verify_flags 可能的值。在此模式下,會檢查對等憑證鍊中所有憑證的 CRLs。

Added in version 3.4.

ssl.VERIFY_X509_STRICT

SSLContext.verify_flags 可能的值,用來禁用已損壞的 X.509 憑證的解決方法。

Added in version 3.4.

ssl.VERIFY_ALLOW_PROXY_CERTS

SSLContext.verify_flags 可能的值,用來啟用憑證代理驗證。

Added in version 3.10.

ssl.VERIFY_X509_TRUSTED_FIRST

SSLContext.verify_flags 可能的值。它指示 OpenSSL 在構建信任鍊來驗證憑證時會優先使用被信任的憑證。此旗標預設開啟。

Added in version 3.4.4.

ssl.VERIFY_X509_PARTIAL_CHAIN

SSLContext.verify_flags 可能的值。它指示 OpenSSL 接受信任存儲中的中間 CAs 作為信任錨,就像自簽名的根 CA 憑證。這樣就能去信任中間 CA 所頒發的憑證,而不一定非要去信任其祖先的根 CA。

Added in version 3.10.

class ssl.VerifyFlags

enum.IntFlag 為 VERIFY_* 常數的其中一個集合。

Added in version 3.6.

ssl.PROTOCOL_TLS

選擇客戶端及伺服器均可以支援最高協定版本。儘管名稱只有 「TLS」,但實際上「SSL」和「TLS」均可以選擇。

Added in version 3.6.

在 3.10 版之後被棄用: TLS 的客戶端及伺服器端需要不同的預設值來實現安全通訊。通用的 TLS 協定常數已被廢除,並改用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER

ssl.PROTOCOL_TLS_CLIENT

自動協商客戶端和服務器都支援的最高協議版本,並配置客戶端語境連線。該協定預設啟用 CERT_REQUIREDcheck_hostname

Added in version 3.6.

ssl.PROTOCOL_TLS_SERVER

自動協商客戶端和服務器都支援的最高協議版本,並配置客戶端語境連線。

Added in version 3.6.

ssl.PROTOCOL_SSLv23

PROTOCOL_TLS 的別名。

在 3.6 版之後被棄用: 請改用 PROTOCOL_TLS

ssl.PROTOCOL_SSLv3

選擇第三版的 SSL 做為通道加密協定。

如果 OpenSSL 是用 no-ssl3 編譯的,則此項協議無法使用。

警告

第三版的 SSL 是不安全的,強烈建議不要使用。

在 3.6 版之後被棄用: OpenSSL 已經終止了所有特定版本的協定。請改用預設的 PROTOCOL_TLS_SERVER 協定或帶有 SSLContext.minimum_versionSSLContext.maximum_versionPROTOCOL_TLS_CLIENT

ssl.PROTOCOL_TLSv1

選擇 1.0 版的 TLS 做為通道加密協定。

在 3.6 版之後被棄用: OpenSSL 已經將所有版本特定的協定棄用。

ssl.PROTOCOL_TLSv1_1

選擇 1.1 版的 TLS 做為通道加密協定。只有在 1.0.1 版本以上的 OpenSSL 才可以選用。

Added in version 3.4.

在 3.6 版之後被棄用: OpenSSL 已經將所有版本特定的協定棄用。

ssl.PROTOCOL_TLSv1_2

選擇 1.2 版的 TLS 做為通道加密協定。只有在 1.0.1 版本以上的 OpenSSL 才可以選用。

Added in version 3.4.

在 3.6 版之後被棄用: OpenSSL 已經將所有版本特定的協定棄用。

ssl.OP_ALL

啟用對 SSL 實作時所產生的各種錯誤的緩解措施。此選項預設被設定。它不一定設定與 OpenSSL 的 SSL_OP_ALL 常數相同的旗標。

Added in version 3.2.

ssl.OP_NO_SSLv2

防止 SSLv2 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級 (peer)選用 SSLv2 做為協定版本。

Added in version 3.2.

在 3.6 版之後被棄用: SSLv2 已被棄用

ssl.OP_NO_SSLv3

防止 SSLv3 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級選用 SSLv3 做為協定版本。

Added in version 3.2.

在 3.6 版之後被棄用: SSLv3 已被棄用

ssl.OP_NO_TLSv1

防止 TLSv1 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級選用 TLSv1 做為協定版本。

Added in version 3.2.

在 3.7 版之後被棄用: 該選項自從 OpenSSL 1.1.0 以後已被棄用,請改用新的 SSLContext.minimum_versionSSLContext.maximum_version 代替。

ssl.OP_NO_TLSv1_1

防止 TLSv1.1 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級選用 TLSv1.1 做為協定版本。只有 1.0.1 版後的 OpenSSL 版本才能使用。

Added in version 3.4.

在 3.7 版之後被棄用: 此選項自 OpenSSL 1.1.0 版已被棄用。

ssl.OP_NO_TLSv1_2

防止 TLSv1.2 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級選用 TLSv1.2 做為協定版本。只有 1.0.1 版後的 OpenSSL 版本才能使用。

Added in version 3.4.

在 3.7 版之後被棄用: 此選項自 OpenSSL 1.1.0 版已被棄用。

ssl.OP_NO_TLSv1_3

防止 TLSv1.3 連線。此選項只可以跟 PROTOCOL_TLS 一起使用。它會防止同級選用 TLSv1.3 做為協定版本。TSL1.3 只適用於 1.1.1 版以後的 OpenSSL。當使用 Python 編譯舊版的 OpenSSL 時,該標志預設為 0

Added in version 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 及更早版本的重協商 (renegotiation)。不發送 HelloRequest 訊息,並忽略通過 ClientHello 的重協商請求。

此選項僅適用於 OpenSSL 1.1.0h 及更新版本。

Added in version 3.7.

ssl.OP_CIPHER_SERVER_PREFERENCE

使用伺服器的密碼排序優先順序,而不是客戶端的。此選項並不會影響到客戶端及 SSLv2 伺服器的 sockets。

Added in version 3.3.

ssl.OP_SINGLE_DH_USE

防止對不同的 SSL 會談重複使用相同的 DH 密鑰。這會加強向前保密但需要更多的運算資源。此選項只適用於伺服器 sockets。

Added in version 3.3.

ssl.OP_SINGLE_ECDH_USE

防止對不同的 SSL 會談重複使用相同的 ECDH 密鑰。這會加強向前保密但需要更多的運算資源。此選項只適用於伺服器 sockets。

Added in version 3.3.

ssl.OP_ENABLE_MIDDLEBOX_COMPAT

在 TLS 1.3 握手中發送虛擬的變更密碼規範 (CCS) 消息,以使 TLS 1.3 連接看起來更像 TLS 1.2 連線。

此選項僅適用於 OpenSSL 1.1.1 及更新版本。

Added in version 3.8.

ssl.OP_NO_COMPRESSION

在 SSL 通道上禁用壓縮。如果應用程序協定支援自己的壓縮方案,這會很有用。

Added in version 3.3.

class ssl.Options

enum.IntFlag 為 OP_* 常數中的一個集合。

ssl.OP_NO_TICKET

防止客戶端請求會談票據。

Added in version 3.6.

ssl.OP_IGNORE_UNEXPECTED_EOF

忽略意外關閉的 TLS 連線。

此選項僅適用於 OpenSSL 3.0.0 及更新版本。

Added in version 3.10.

ssl.OP_ENABLE_KTLS

允許使用 TLS 核心。要想受益於該功能,OpenSSL 必須編譯為支援該功能,並且密碼協商套件及擴充套件也必須被該功能支援 (該功能所支援的列表可能會因平台及核心而有所差異)。

請注意當允許使用 TLS 核心時,一些加密操作將直接由核心執行而不是經由任何由可用的 OpenSSL 所提供的程序,而這可能並非你所想使用的,例如:當應用程式要求所有的加密操作由 FIPS 提供執行。

此選項僅適用於 OpenSSL 3.0.0 及更新版本。

Added in version 3.12.

ssl.OP_LEGACY_SERVER_CONNECT

只允許 OpenSSL 與未修補的伺服器進行遺留 (legacy) 不安全重協商。

Added in version 3.12.

ssl.HAS_ALPN

OpenSSL 函式庫是否內建支援 應用層協定協商 TLS 擴充套件,該擴充套件描述在 RFC 7301 中。

Added in version 3.5.

ssl.HAS_NEVER_CHECK_COMMON_NAME

OpenSSL 函式庫是否內建支援不檢查主題通用名稱及 SSLContext.hostname_checks_common_name 是否可寫。

Added in version 3.7.

ssl.HAS_ECDH

OpenSSL 函式庫是否內建支援基於橢圓曲線的 (Elliptic Curve-based) Diffie-Hellman 金鑰交換。此回傳值應該要為 true 除非發布者明確禁用此功能。

Added in version 3.3.

ssl.HAS_SNI

OpenSSL 函式庫是否內建支援 伺服器名稱提示 擴充套件 (在 RFC 6066 中定義)。

Added in version 3.2.

ssl.HAS_NPN

OpenSSL 函式庫是否內建支援 下一代協定協商 該功能在應用層協定協商 中有描述。當此值為 true 時,你可以使用 SSLContext.set_npn_protocols() 方法來公告你想支援的協定。

Added in version 3.3.

ssl.HAS_SSLv2

此 OpenSSL 函式庫是否內建支援 SSL 2.0 協定。

Added in version 3.7.

ssl.HAS_SSLv3

此 OpenSSL 函式庫是否內建支援 SSL 3.0 協定。

Added in version 3.7.

ssl.HAS_TLSv1

此 OpenSSL 函式庫是否內建支援 TLS 1.0 協定。

Added in version 3.7.

ssl.HAS_TLSv1_1

此 OpenSSL 函式庫是否內建支援 TLS 1.1 協定。

Added in version 3.7.

ssl.HAS_TLSv1_2

此 OpenSSL 函式庫是否內建支援 TLS 1.2 協定。

Added in version 3.7.

ssl.HAS_TLSv1_3

此 OpenSSL 函式庫是否內建支援 TLS 1.3 協定。

Added in version 3.7.

ssl.CHANNEL_BINDING_TYPES

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

Added in version 3.3.

ssl.OPENSSL_VERSION

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

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

Added in version 3.2.

ssl.OPENSSL_VERSION_INFO

代表 OpenSSL 库的版本信息的五个整数所组成的元组:

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

Added in version 3.2.

ssl.OPENSSL_VERSION_NUMBER

OpenSSL 库的原始版本号,以单个整数表示:

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

Added in version 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() 中的回调函数的返回值。

Added in version 3.4.

class ssl.AlertDescription

enum.IntEnum 為 ALERT_DESCRIPTION_* 常數中的一個集合。

Added in version 3.6.

Purpose.SERVER_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的参数。表示上下文可用于验证网络服务器(因此,它将被用于创建客户端套接字)。

Added in version 3.4.

Purpose.CLIENT_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的参数。 表示上下文可用于验证网络客户(因此,它将被用于创建服务器端套接字)。

Added in version 3.4.

class ssl.SSLErrorNumber

enum.IntEnum 為 SSL_ERROR_* 常數中的一個集合。

Added in version 3.6.

class ssl.TLSVersion

SSLContext.maximum_versionSSLContext.minimum_version 中的 SSL 和 TLS 版本的 enum.IntEnum 多项集。

Added in version 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 成员,除 TLSVersion.TLSv1_2TLSVersion.TLSv1_3 之外均已废弃。

SSL Sockets

class ssl.SSLSocket(socket.socket)

SSL 套接字提供了 Socket 物件 的下列方法:

但是,由于 SSL(和 TLS)协议在 TCP 之上具有自己的框架,因此 SSL 套接字抽象在某些方面可能与常规的 OS 层级套接字存在差异。 特别是要查看 非阻塞型套接字说明

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。这些函数支持读取和写入大于 2GB 的数据。写入零长数据不再出现违反协议的错误。

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() 尚未被调用。

通常你应当使用套接字 API 方法例如 recv()send() 来代替这些方法。

SSLSocket.do_handshake()

执行 SSL 设置握手。

在 3.4 版的變更: 当套接字的 contextcheck_hostname 属性为真值时此握手方法还会执行 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 实例。 如果证书未通过验证,则字典将为空。 如果证书通过验证,它将返回由多个密钥组成的字典,其中包括 subject (证书颁发给的主体) 和 issuer (颁发证书的主体)。 如果证书包含一个 Subject Alternative Name 扩展的实例 (see RFC 3280),则字典中还将有一个 subjectAltName 键。

subjectissuer 字段都是包含在证书中相应字段的数据结构中给出的相对专有名称(RDN)序列的元组,每个 RDN 均为 name-value 对的序列。 这里是一个实际的示例:

{'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 扩展条目例如 crlDistributionPoints, caIssuersOCSP URI。

在 3.9 版的變更: IPv6 地址字符串不再附带末尾换行符。

SSLSocket.cipher()

返回由三个值组成的元组,其中包含所使用的密码名称,定义其使用方式的 SSL 协议版本,以及所使用的加密比特位数。 如果尚未建立连接,则返回 None

SSLSocket.shared_ciphers()

返回在客户端和服务器均可用的密码列表。 所返回列表的每个条目都是由三个值组成的元组其中包含密码名称、定义其使用方式的 SSL 协议版本,以及密码所使用的加密比特位数量。 如果连接尚未建立或套接字为客户端套接字则 shared_ciphers() 将返回 None

Added in version 3.5.

SSLSocket.compression()

以字符串形式返回所使用的压缩算法,或者如果连接没有使用压缩则返回 None

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

Added in version 3.3.

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

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

cb_type 形参允许选择需要的通道绑定类型。 有效的通道绑定类型在 CHANNEL_BINDING_TYPES 列表中列出。 目前只支持由 RFC 5929 所定义的 'tls-unique' 通道绑定。 如果请求了一个不受支持的通道绑定类型则将引发 ValueError

Added in version 3.3.

SSLSocket.selected_alpn_protocol()

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

Added in version 3.5.

SSLSocket.selected_npn_protocol()

返回在Return the higher-level protocol that was selected during the TLS/SSL 握手期间所选择的高层级协议。 如果 SSLContext.set_npn_protocols() 未被调用,或者如果另一方不支持 NPN,或者如果握手尚未发生,则将返回 None

Added in version 3.3.

在 3.10 版之後被棄用: NPN 已被 ALPN 取代。

SSLSocket.unwrap()

执行 SSL 关闭握手,这会从下层的套接字中移除 TLS 层,并返回下层的套接字对象。 这可被用来通过一个连接将加密操作转为非加密。 返回的套接字应当总是被用于同连接另一方的进一步通信,而不是原始的套接字。

SSLSocket.verify_client_post_handshake()

向一个 TLS 1.3 客户端请求握手后身份验证(PHA)。 只有在初始 TLS 握手之后且双方都启用了 PHA 的情况下才能为服务器端套接字的 TLS 1.3 连接启用 PHA,参见 SSLContext.post_handshake_auth

此方法不会立即执行证书交换。 服务器端会在下一次写入事件期间发送 CertificateRequest 并期待客户端在下一次读取事件期间附带证书进行响应。

如果有任何前置条件未被满足(例如非 TLS 1.3,PHA 未启用),则会引发 SSLError

備註

仅在 OpenSSL 1.1.1 且 TLS 1.3 被启用时可用。 没有 TLS 1.3 支持,此方法将引发 NotImplementedError

Added in version 3.8.

SSLSocket.version()

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

Added in version 3.5.

SSLSocket.pending()

返回在连接上等待被读取的已解密字节数。

SSLSocket.context

该 SSL 套接字所关联的 SSLContext 对象。

Added in version 3.2.

SSLSocket.server_side

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

Added in version 3.2.

SSLSocket.server_hostname

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

Added in version 3.2.

在 3.7 版的變更: 现在该属性将始终为 ASCII 文本。 当 server_hostname 为一个国际化域名(IDN)时,该属性现在会保存为 A 标签形式 ("xn--pythn-mua.org") 而非 U 标签形式 ("pythön.org")。

SSLSocket.session

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

Added in version 3.6.

SSLSocket.session_reused

Added in version 3.6.

SSL 上下文

Added in version 3.2.

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

class ssl.SSLContext(protocol=None)

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

这个表显示了客户端(横向)的哪个版本能够连接服务器(纵向)的哪个版本。

client / server

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_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE, OP_NO_SSLv2OP_NO_SSLv3 (PROTOCOL_SSLv3 除外)。 初始密码套件列表只包含 HIGH 密码,而不包含 NULL 密码和 MD5 密码。

在 3.10 版之後被棄用: 不带协议参数的 SSLContext 已废弃。将来,上下文类会要求使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 协议。

在 3.10 版的變更: 现在默认的密码套件只包含安全的 AES 和 ChaCha20 密码,具有前向保密性和安全级别2。禁止使用少于 2048 位的 RSA 和 DH 密钥以及少于 224 位的ECC密钥。 PROTOCOL_TLSPROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 至少使用 TLS 1.2 版本。

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

SSLContext.cert_store_stats()

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

具有一个 CA 证书和一个其他证书的上下文示例:

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

Added in version 3.4.

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

加载一个私钥及对应的证书。 certfile 字符串必须为以 PEM 格式表示的单个文件路径,该文件中包含证书以及确立证书真实性所需的任意数量的 CA 证书。 如果存在 keyfile 字符串,它必须指向一个包含私钥的文件。 否则私钥也将从 certfile 中提取。 请参阅 证书 中的讨论来了解有关如何将证书存储至 certfile 的更多信息。

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 证书。

Added in version 3.4.

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

verify_mode 不为 CERT_NONE 时加载一组用于验证其他对等方证书的 "证书颁发机构" (CA) 证书。 必须至少指定 cafilecapath 中的一个。

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

如果存在 cafile 字符串,它应为 PEM 格式的级联 CA 证书文件的路径。 请参阅 证书 中的讨论来了解有关如何处理此文件中的证书的更多信息。

如果存在 capath 字符串,它应为包含多个 PEM 格式的 CA 证书的目录的路径,并遵循 OpenSSL 专属布局

如果存在 cadata 对象,它应为一个或多个 PEM 编码的证书的 ASCII 字符串或者 DER 编码的证书的 bytes-like object。 与 capath 一样 PEM 编码的证书之外的多余行会被忽略,但至少要有一个证书。

在 3.4 版的變更: 新增可选参数 cadata

SSLContext.get_ca_certs(binary_form=False)

获取已离开法人 "证书颁发机构" (CA) 证书列表。 如果 binary_form 形参为 False 则每个列表条目都是一个类似于 SSLSocket.getpeercert() 输出的字典。 在其他情况下此方法将返回一个 DER 编码的证书的列表。 返回的列表不包含来自 capath 的证书,除非 SSL 连接请求并加载了一个证书。

備註

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

Added in version 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'}]

Added in version 3.6.

SSLContext.set_default_verify_paths()

从构建 OpenSSL 库时定义的文件系统路径中加载一组默认的 "证书颁发机构" (CA) 证书。 不幸的是,没有一种简单的方式能知道此方法是否执行成功:如果未找到任何证书也不会返回错误。 不过,当 OpenSSL 库是作为操作系统的一部分被提供时,它的配置应当是正确的。

SSLContext.set_ciphers(ciphers)

为使用此上下文创建的套接字设置可用密码。 它应当为 OpenSSL 密码列表格式 的字符串。 如果没有可被选择的密码(由于编译时选项或其他配置禁止使用所指定的任何密码),则将引发 SSLError

備註

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

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_ALPNFalse 则此方法将引发 NotImplementedError

Added in version 3.5.

SSLContext.set_npn_protocols(protocols)

指定在Specify which protocols the socket should advertise during the SSL/TLS 握手期间套接字应当通告的协议。 它应为由字符串组成的列表,例如 ['http/1.1', 'spdy/2'],按首选顺序排列。 协议的选择将在握手期间发生,并将依据 应用层协议协商 来执行。 在握手成功后,SSLSocket.selected_npn_protocol() 方法将返回已达成一致的协议。

如果 HAS_NPNFalse 则此方法将引发 NotImplementedError

Added in version 3.3.

在 3.10 版之後被棄用: NPN 已被 ALPN 取代。

SSLContext.sni_callback

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

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

此回调函数将附带三个参数来调用;第一个参数是 ssl.SSLSocket,第二个参数是代表客户端准备与之通信的服务器的字符串 (或者如果 TLS Client Hello 不包含服务器名称则为 None) 而第三个参数是原来的 SSLContext。 服务器名称参数为文本形式。 对于国际化域名,服务器名称是一个 IDN A 标签 ("xn--pythn-mua.org")。

此回调的一个典型用法是将 ssl.SSLSocketSSLSocket.context 属性修改为一个 SSLContext 类型的新对象,该对象代表与服务器相匹配的证书链。

Due to the early negotiation phase of the TLS connection, only limited methods and attributes are usable like SSLSocket.selected_alpn_protocol() and SSLSocket.context. The SSLSocket.getpeercert(), SSLSocket.cipher() and SSLSocket.compression() methods require that the TLS connection has progressed beyond the TLS Client Hello and therefore will not return meaningful values nor can they be called safely.

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

如果从 sni_callback 函数引发了异常,则 TLS 连接将终止并发出 TLS 致命警告消息 ALERT_DESCRIPTION_HANDSHAKE_FAILURE

如果 OpenSSL library 库在构建时定义了 OPENSSL_NO_TLSEXT 则此方法将返回 NotImplementedError

Added in version 3.7.

SSLContext.set_servername_callback(server_name_callback)

这是被保留用于向下兼容的旧式 API。 在可能的情况下,你应当改用 sni_callback。 给出的 server_name_callback 类似于 sni_callback,不同之处在于当服务器主机名是 IDN 编码的国际化域名时,server_name_callback 会接收到一个已编码的 U 标签 ("pythön.org")。

如果发生了服务器名称解码错误。 TLS 连接将终止并向客户端发出 ALERT_DESCRIPTION_INTERNAL_ERROR 最严重 TLS 警告消息。

Added in version 3.4.

SSLContext.load_dh_params(dhfile)

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

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

Added in version 3.3.

SSLContext.set_ecdh_curve(curve_name)

为基于椭圆曲线的 Elliptic Curve-based Diffie-Hellman (ECDH) 密钥交换设置曲线名称。 ECDH 显著快于常规 DH 同时据信同样安全。 curve_name 形参应为描述某个知名椭圆曲线的字符串,例如受到广泛支持的曲线 prime256v1

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

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

Added in version 3.3.

也參考

SSL/TLS & Perfect Forward Secrecy

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_side 为真值则指定 server_hostname 将引发 ValueError

形参 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,它将向调用方引发异常。

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。 该属性可以在类实例上被重载以便返回自定义的 SSLSocket 的子类。

Added in version 3.7.

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

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

server_side, server_hostnamesession 形参具有与 SSLContext.wrap_socket() 中相同的含义。

在 3.6 版的變更: 新增 session 引數。

在 3.7 版的變更: 此方法返回 SSLContext.sslobject_class 的实例而不是硬编码的 SSLObject

SSLContext.sslobject_class

SSLContext.wrap_bio() 的返回类型,默认为 SSLObject。 该属性可以在类实例上被重载以便返回自定义的 SSLObject 的子类。

Added in version 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_NONEPROTOCOL_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))

Added in version 3.4.

在 3.7 版的變更: 现在当主机名检查被启用且 verify_modeCERT_NONEverify_mode 会自动更改为 CERT_REQUIRED。 在之前版本中同样的操作将失败并引发 ValueError

SSLContext.keylog_filename

每当生成或接收到密钥时,将 TLS 密钥写入到一个密钥日志文件。 密钥日志文件的设计仅适用于调试目的。 文件的格式由 NSS 指明并为许多流量分析工具例如 Wireshark 所使用。 日志文件会以追加模式打开。 写入操作会在线程之间同步,但不会在进程之间同步。

Added in version 3.8.

SSLContext.maximum_version

一个代表所支持的最高 TLS 版本的 TLSVersion 枚举成员。 该值默认为 TLSVersion.MAXIMUM_SUPPORTED。 这个属性对于 PROTOCOL_TLS, PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 以外的其他协议来说都是只读的。

maximum_version, minimum_versionSSLContext.options 等属性都会影响上下文所支持的 SSL 和 TLS 版本。 这个实现不会阻止无效的组合。 例如一个 optionsOP_NO_TLSv1_2maximum_version 设为 TLSVersion.TLSv1_2 的上下文将无法建立 TLS 1.2 连接。

Added in version 3.7.

SSLContext.minimum_version

SSLContext.maximum_version 类似,区别在于它是所支持的最低版本或为 TLSVersion.MINIMUM_SUPPORTED

Added in version 3.7.

SSLContext.num_tickets

控制 TLS_PROTOCOL_SERVER 上下文的 TLS 1.3 会话凭据数量。这个设置不会影响 TLS 1.0 - 1.2 的连接。

Added in version 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 版之後被棄用: 自 OpenSSL 1.1.0 起,所有 OP_NO_SSL*OP_NO_TLS* 选项已被弃用,请改用新的 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 操作后再进行。

Added in version 3.8.

SSLContext.protocol

构造上下文时所选择的协议版本。 这个属性是只读的。

SSLContext.hostname_checks_common_name

在没有目标替代名称扩展的情况下 check_hostname 是否要回退为验证证书的通用名称(默认为真值)。

Added in version 3.7.

在 3.10 版的變更: 此旗标在 OpenSSL 1.1.1l 之前的版本上不起作用。 Python 3.8.9, 3.9.3 和 3.10 包括了针对之前版本的变通处理。

SSLContext.security_level

整数值,代表上下文的 安全级别。 本属性只读。

Added in version 3.10.

SSLContext.verify_flags

证书验证操作的标志位。可以用“或”的方式组合在一起设置 VERIFY_CRL_CHECK_LEAF 这类标志。默认情况下,OpenSSL 既不需要也不验证证书吊销列表(CRL)。

Added in version 3.4.

在 3.6 版的變更: SSLContext.verify_flags 返回 VerifyFlags 旗标:

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

是否要尝试验证其他对等方的证书以及如果验证失败应采取何种行为。 该属性值必须为 CERT_NONE, CERT_OPTIONALCERT_REQUIRED 之一。

在 3.6 版的變更: SSLContext.verify_mode 返回 VerifyMode 枚举:

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

证书

总的来说证书是公钥/私钥系统的一个组成部分。 在这个系统中,每 个 主体 (可能是一台机器、一个人或者一个组织) 都会分配到唯一的包含两部分的加密密钥。 一部分密钥是公开的,称为 公钥;另一部分密钥是保密的,称为 私钥。 这两个部分是互相关联的,就是说如果你用其中一个部分来加密一条消息,你将能用并且 只能 用另一个部分来解密它。

在一个证书中包含有两个主体的相关信息。 它包含 目标方 的名称和目标方的公钥。 它还包含由第二个主体 颁发方 所发布的声明:目标方的身份与他们所宣称的一致,包含的公钥也确实是目标方的公钥。 颁发方的声明使用颁发方的私钥进行签名,该私钥的内容只有颁发方自己才知道。 但是,任何人都可以找到颁发方的公钥,用它来解密这个声明,并将其与证书中的其他信息进行比较来验证颁发方声明的真实性。 证书还包含有关其有效期限的信息。 这被表示为两个字段,即 "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() 自动完成。

合并的密钥和证书

私钥往往与证书存储在相同的文件中;在此情况下,只需要将 certfile 形参传给 SSLContext.load_cert_chain()。 如果私钥是与证书一起存储的,则它应当放在证书链的第一个证书之前:

-----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 套接字的行为与常规套接字略有不同。 当使用非阻塞模式时,你需要注意下面这些事情:

  • 如果一个 I/O 操作会阻塞,大多数 SSLSocket 方法都将引发 SSLWantWriteErrorSSLWantReadError 而非 BlockingIOError。 如果有必要在下层套接字上执行读取操作将引发 SSLWantReadError,在下层套接字上执行写入操作则将引发 SSLWantWriteError。 请注意尝试 写入 到 SSL 套接字可能需要先从下层套接字 读取,而尝试从 SSL 套接字 读取 则可能需要先向下层套接字 写入

    在 3.5 版的變更: 在较早的 Python 版本中,SSLSocket.send() 方法会返回零值而非引发 SSLWantWriteErrorSSLWantReadError

  • 调用 select() 将告诉你可以从 OS 层级的套接字读取(或向其写入),但这并不意味着在上面的 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 模块来轮询事件并处理 SSLWantWriteError, SSLWantReadErrorBlockingIOError 等异常。 它还会异步地执行 SSL 握手。

内存 BIO 支持

Added in version 3.5.

自从 SSL 模块在 Python 2.6 起被引入之后,SSLSocket 类提供了两个互相关联但彼此独立的功能分块:

  • SSL 协议处理

  • 网络 IO

网络 IO API 与 socket.socket 所提供的功能一致,SSLSocket 也是从那里继承而来的。 这允许 SSL 套接字被用作常规套接字的替代,使得向现有应用程序添加 SSL 支持变得非常容易。

将 SSL 协议处理与网络 IO 结合使用通常都能运行良好,但在某些情况下则不能。 此情况的一个例子是 async IO 框架,该框架要使用不同的 IO 多路复用模型而非 (基于就绪状态的) "在文件描述器上执行选择/轮询" 模型,该模型是 socket.socket 和内部 OpenSSL 套接字 IO 例程正常运行的假设前提。 这种情况在该模型效率不高的 Windows 平台上最为常见。 为此还提供了一个 SSLSocket 的简化形式,称为 SSLObject

class ssl.SSLObject

SSLSocket 的简化形式,表示一个不包含任何网络 IO 方法的 SSL 协议实例。 这个类通常由想要通过内存缓冲区为 SSL 实现异步 IO 的框架作者来使用。

这个类在低层级 SSL 对象上实现了一个接口,与 OpenSSL 所实现的类似。 此对象会捕获 SSL 连接的状态但其本身不提供任何网络 IO。 IO 需要通过单独的 "BIO" 对象来执行,该对象是 OpenSSL 的 IO 抽象层。

这个类没有公有构造器。 SSLObject 实例必须使用 wrap_bio() 方法来创建。 此方法将创建 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 套接字那样返回下层的套接字。

  • server_name_callback 回调被传给 SSLContext.set_servername_callback() 时将获得一个 SSLObject 实例而非 SSLSocket 实例作为其第一个形参。

有关 SSLObject 用法的一些说明:

在 3.7 版的變更: SSLObject 的实例必须使用 wrap_bio() 来创建。 在较早的版本中,直接创建该实例是可能的。 但这从未被写入文档或是被正式支持。

SSLObject 会使用内存缓冲区与外部世界通信。 MemoryBIO 类提供了可被用于此目的的内存缓冲区。 它包装了一个 OpenSSL 内存 BIO (Basic 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 will 在缓冲区当前的所有数据都被读取之后将变为真值。

SSL 会话

Added in version 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 的内部随机数字生成器并不能正确处理分叉的进程。 应用程序必须修改父进程的 PRNG 状态,如果它们要使用任何包含 os.fork() 的 SSL 特征的话。 任何对 RAND_add()RAND_bytes() 的成功调用都可以做到这一点。

TLS 1.3

Added in version 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 客户端证书请求、签名算法配置和密钥重生成等 TLS 1.3 特性尚未被支持。