見出し画像

SSLEOFErrorを解決するためpython3で暗号化方式とTLSを指定する

pythonのコーディングで、TLSのバージョンと暗号化方式を同時に指定することが必要になり、ハマッてしまいました。
忘れない様に備忘録も兼ねて残しておきます。

Pythonは、3.6.8を利用しています。
ちょっと古いですね(笑


原因

とあるサーバーにhttps接続すると「SSLEOFError 」になる症状がありました。

エラーメッセージは次のようなものでした。
([URL]には実際のURLが入ると思ってください。)
その1
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='[URL]', port=443): Max retries exceeded with url: [URL](Caused by SSLError(SSLEOFError(8,'[SSL:UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1002)')))

その2
SSLError: HTTPSConnectionPool(host='[URL]', port=443): Max retries exceeded with url: [URL] (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1007)')))

接続先サーバーが許容するssl通信の設定と接続に行くクライアント(エラーがでるクライアント)の使うssl通信の設定が合わない場合にも発生するようです。

私が聞いた事例は、Mac上のエミュレーション環境でpythonを動作させているものでした。何も環境を変更していないのに、ある日突「SSLEOFError 」が出て接続できなくなったとのことでした。

推測ですが、OS側で自動アップデートが行われたのでしょう。それで新しいTLSバージョンと暗号化方式に更新されてしまったのだろうと思います。

サーバー側が最新のものに対応していない場合には、ssl通信を継続できないです。それでクライアント側が「SSLEOFError 」になるのでしょう。

今回は、この解決のためにクライアント側でTLSバージョンと暗号化方式を指定する方法を探しました。

基本のコード

使っている接続方法はurllib3で、基本のコードは以下です。
設定したurlにアクセスして、内容を取得するだけのコードです。

# -*- coding: utf-8 -*-
import urllib3

# 機能: ホームページデータの取得。
# 備考: 'str_url'で指定した先に接続し、応答データを取得する。

# ページを指定
str_url = 'https://[接続先url]/'

# 指定urlに接続
http = urllib3.PoolManager()
req = http.request('GET', str_url)


まずTLSのバージョン指定のみを試す

調べると、指定urlに接続するためのオブジェクトを作る
http = urllib3.PoolManager()
の部分を

http = urllib3.PoolManager(ssl_version=ssl.PROTOCOL_TLSv1_2)

に変更することで、特定のバージョンを指定できました。
この例の場合は、TLS1.2を指定しています。

TLSのバージョンの記法は以下のようです。
TLS1.0: ssl.PROTOCOL_TLSv1
TLS1.1: ssl.PROTOCOL_TLSv1_1
TLS1.2: ssl.PROTOCOL_TLSv1_2
TLS1.3: ssl.PROTOCOL_TLSv1_3

暗号化方式の指定

次は暗号化方式の指定です。
ここで難航しました。
DEFAULT_CIPHERSの属性がないよ〜とかエラー続出。

最後は、またChatGPT様に聞いてしまいました。
困った時のChatGPT様頼みです(笑

TLSバージョンを単独で指定する方法は使えず、別の方法が必要でした。
ChatGPTからの解答も紆余曲折があったのですが、
最終的には次のコードに落ち着きました。

# -*- coding: utf-8 -*-
import urllib3
import ssl

# 機能: ホームページデータの取得。
# 備考: 'str_url'で指定した先に接続し、応答データを取得する。

# ページを指定
str_url = 'https://[接続先url]/'

# カスタムのSSLコンテキストを作成
ssl_context = ssl.create_default_context()

# ソケットで利用する暗号方式を指定
ssl_context.set_ciphers('RSA+AES')
# TLSのバージョンを指定
ssl_context.protocol = ssl.PROTOCOL_TLSv1_2


# urllib3のPoolManagerを作成し、カスタムのSSLコンテキストを渡す
http = urllib3.PoolManager(ssl_context=ssl_context)
req = http.request('GET', str_url)

変更点の解説

1)まず、sslライブラリのインポートです。
import ssl

2)次にカスタムのSSLコンテキストを作成
ssl_context = ssl.create_default_context()

3)作成したカスタムのsslコンテキストで、ソケットで利用する暗号方式を指定
ssl_context.set_ciphers('RSA+AES')

暗号化方式の書式のサンプルはssl_.pyの以下の部分を覗いてみると分かるかと思います。
DEFAULT_CIPHERS = ':'.join([
'TLS13-AES-256-GCM-SHA384',
'TLS13-CHACHA20-POLY1305-SHA256',
'TLS13-AES-128-GCM-SHA256',
'ECDH+AESGCM',
'ECDH+CHACHA20',
'DH+AESGCM',
'DH+CHACHA20',
'ECDH+AES256',
'DH+AES256',
'ECDH+AES128',
'DH+AES',
'RSA+AESGCM',
'RSA+AES',
'!aNULL',
'!eNULL',
'!MD5',
])

4)TLSのバージョンを指定します。
ssl_context.protocol = ssl.PROTOCOL_TLSv1_2

なぜかcipherの設定方式 [object].set_ciphers('str_sample')
と書式が全く違います。
????となってしまいますが、調べてもこれで良いようです。

5)urllib3のPoolManagerを作成するときに、カスタムのSSLコンテキストを渡します。
http = urllib3.PoolManager(ssl_context=ssl_context)

以上でTLSのバージョンと暗号化方式(ciphers)の同時指定が可能になりました。

最後に

https接続して「SSLEOFError 」となったときには、特定のTLSバージョンとciphersを指定することで解決できる場合が有るかもしれません。ご参考になれば幸いです。

他にも色々「SSLEOFerror」が発生するパターンがあるようです。解決には、これだと決めてかからず、色々調べてみてください。

解決の糸口は、「なぜSSL通信が突然遮断されるのか」と考え、原因を探るところにあるかと思います。

この記事が気に入ったらサポートをしてみませんか?