Python ftplibのFTPSプログラミング:エラー回避とベストプラクティス
ssl_version
の役割
ssl
モジュールとの連携:ssl_version
に設定する値は、Pythonの標準ライブラリであるssl
モジュールで定義されている定数(例:ssl.PROTOCOL_TLSv1_2
,ssl.PROTOCOL_TLSv1_3
など)を使用します。- 互換性とセキュリティ: サーバーがサポートするプロトコルバージョンとクライアントが指定するバージョンが一致しない場合、接続が失敗する可能性があります。また、古いSSL/TLSバージョン(例: SSLv2, SSLv3)はセキュリティ上の脆弱性があるため、より新しいバージョンを指定することが推奨されます。
- SSL/TLSプロトコルの選択: FTPサーバーとの安全な通信を確立するために、どのSSL/TLSプロトコルバージョン(例: TLSv1.2, TLSv1.3など)を使用するかをクライアント側で指定します。
使用例
通常、ftplib.FTP_TLS
オブジェクトを初期化する際に、context
引数を使ってssl.SSLContext
オブジェクトを渡すのが推奨されています。このSSLContext
オブジェクト内で、使用するSSL/TLSプロトコルのバージョンを含め、より詳細なSSL/TLS設定を制御できます。
しかし、簡単なケースや特定のプロトコルバージョンを直接指定したい場合は、ssl_version
属性を使用することもできます。
import ftplib
import ssl
# FTPS接続を試みる
try:
# FTP_TLSインスタンスを作成
ftps = ftplib.FTP_TLS('your_ftp_server.com')
# TLSv1.2 を使用するように指定
# Pythonのsslモジュールで定義されているプロトコル定数を使用
ftps.ssl_version = ssl.PROTOCOL_TLSv1_2
# 制御チャネルをセキュアにする (AUTH TLSコマンドを送信)
ftps.auth()
# ログイン
ftps.login('username', 'password')
# データ接続もセキュアにする (PROT Pコマンドを送信)
ftps.prot_p()
# ファイルリストを取得する例
print(ftps.nlst())
# 接続を閉じる
ftps.quit()
except ftplib.all_errors as e:
print(f"FTP エラーが発生しました: {e}")
except ssl.SSLError as e:
print(f"SSL/TLS エラーが発生しました: {e}")
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
ssl.SSLContext
の利用推奨: より高度なセキュリティ設定(証明書の検証、許可される暗号スイートなど)を行いたい場合は、ssl.create_default_context()
などを用いてssl.SSLContext
オブジェクトを作成し、それをFTP_TLS
コンストラクタのcontext
引数に渡すことが推奨されます。ssl_version
はより限定的な設定に留まります。- サーバーのサポート: サーバーが指定された
ssl_version
をサポートしていない場合、接続は確立されません。 - デフォルトの挙動:
ssl_version
を明示的に設定しない場合、ftplib.FTP_TLS
はデフォルトで最適な(最新の)SSL/TLSプロトコルバージョンをネゴシエートしようとします。多くの場合はこのデフォルトの挙動で問題ありません。
ftplib.FTP_TLS.ssl_version
に関連する一般的なエラーとトラブルシューティング
エラー: ssl.SSLError: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:XXXX)
- トラブルシューティング:
- プロトコルバージョンの変更:
- まず、より新しいTLSバージョン(
ssl.PROTOCOL_TLSv1_2
やssl.PROTOCOL_TLSv1_3
)を試してみてください。 - それでもだめな場合は、サーバーが比較的古いSSL/TLSバージョンしかサポートしていない可能性も考慮し、
ssl.PROTOCOL_TLSv1_1
などを試すことも最終手段として考えられます。ただし、セキュリティの観点からは推奨されません。 - 推奨:
ssl.create_default_context()
を使用し、明示的にminimum_version
を設定しないことで、デフォルトのネゴシエーションに任せるのが最も堅牢な方法です。
ssl.SSLContext
の利用:ssl_version
を直接設定する代わりに、ssl.SSLContext
オブジェクトを使用してください。これにより、SSL/TLSハンドシェイクの制御をより詳細に行うことができます。import ftplib import ssl context = ssl.create_default_context() # 必要であれば、ここで context の設定を変更する # 例: 古いサーバーに対応するため、最低バージョンを下げる場合 (非推奨だが、互換性のため) # context.minimum_version = ssl.TLSVersion.TLSv1_1 ftps = ftplib.FTP_TLS('your_ftp_server.com', context=context) ftps.auth() ftps.login('username', 'password') ftps.prot_p() print(ftps.nlst()) ftps.quit()
- PythonとOpenSSLのバージョン確認: 使用しているPython環境のOpenSSLライブラリが最新であるか確認してください。古いバージョンでは、新しいTLSプロトコルや暗号スイートがサポートされていない場合があります。
- まず、より新しいTLSバージョン(
- プロトコルバージョンの変更:
- 考えられる原因:
- 古いサーバーが新しいTLSバージョン(例: TLSv1.3)をサポートしていない。
- 新しいサーバーが古いSSLバージョン(例: SSLv3)をすでに無効にしている。
- PythonのバージョンやOpenSSLのバージョンが古く、特定のTLSバージョンをサポートしていない。
- エラーの状況: これは、指定したSSL/TLSプロトコルバージョンをサーバーがサポートしていないか、クライアントのSSL/TLSライブラリ(OpenSSLなど)がそのバージョンをサポートしていない場合に発生します。
エラー: ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:XXXX)
- トラブルシューティング:
- 証明書の検証を無効にする(開発・テスト目的のみ、本番環境では非推奨):
セキュリティリスクを伴うため、本番環境では絶対に行わないでください。デバッグ目的で一時的に証明書の検証をスキップする場合にのみ使用します。
import ftplib import ssl # 証明書の検証を無効にするSSLコンテキストを作成 context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE # これが検証を無効にする設定 ftps = ftplib.FTP_TLS('your_ftp_server.com', context=context) ftps.auth() ftps.login('username', 'password') ftps.prot_p() print(ftps.nlst()) ftps.quit()
- 正しいCA証明書の設定:
- サーバーの証明書を発行したCAのルート証明書や中間証明書を、クライアント側の信頼できる証明書ストア(例:
certifi
パッケージが提供するCAバンドル、またはシステム全体の証明書ストア)に追加します。 ssl.SSLContext
を作成する際に、cafile
やcapath
引数でCA証明書のパスを指定します。
import ftplib import ssl import certifi # pip install certifi でインストール context = ssl.create_default_context(cafile=certifi.where()) # または、特定のCA証明書ファイルがある場合 # context = ssl.create_default_context(cafile="/path/to/your/ca_bundle.pem") ftps = ftplib.FTP_TLS('your_ftp_server.com', context=context) # ...
- サーバーの証明書を発行したCAのルート証明書や中間証明書を、クライアント側の信頼できる証明書ストア(例:
- サーバー管理者に確認: サーバーのSSL証明書が正しく設定されているか、有効期限が切れていないかなどをサーバー管理者に確認してください。
- 証明書の検証を無効にする(開発・テスト目的のみ、本番環境では非推奨):
セキュリティリスクを伴うため、本番環境では絶対に行わないでください。デバッグ目的で一時的に証明書の検証をスキップする場合にのみ使用します。
- 考えられる原因:
- サーバーの証明書が自己署名されている、または信頼できない認証局(CA)によって署名されている。
- クライアント側の証明書ストアに、サーバーの証明書を発行したCAのルート証明書が含まれていない。
- 証明書が期限切れ、または無効である。
- エラーの状況: これは
ssl_version
自体に直接関係するエラーではありませんが、FTPS接続で頻繁に発生するSSL/TLS関連のエラーです。サーバーのSSL証明書をクライアントが検証できない場合に発生します。
エラー: ftplib.error_perm: 530 Login authentication failed. または ftplib.error_perm: 534 Policy requires SSL.
- トラブルシューティング:
- FTPSとして正しく接続:
ftplib.FTP_TLS
を使用していることを確認し、必ずftps.auth()
を呼び出して制御チャネルをセキュアにし、その後ftps.prot_p()
を呼び出してデータチャネルもセキュアにするようにしてください。import ftplib import ssl ftps = ftplib.FTP_TLS('your_ftp_server.com') ftps.auth() # 制御チャネルをTLSで保護 ftps.login('username', 'password') ftps.prot_p() # データチャネルをTLSで保護 (これが重要) print(ftps.nlst()) ftps.quit()
- ユーザー名とパスワードの再確認: 当然ですが、これが最も単純な間違いであることも多いです。
- FTPSとして正しく接続:
- 考えられる原因:
- ユーザー名やパスワードが間違っている(これはSSL/TLSとは直接関係ないが、一般的なエラー)。
- FTPSサーバーがSSL/TLSによる暗号化接続を必須としているにもかかわらず、平文のFTP接続を試みている。
ftps.auth()
を呼び出していない、またはftps.prot_p()
を呼び出す前にデータ接続(例:nlst()
,retrbinary()
など)を試みている。
- エラーの状況: ログインに失敗したり、SSL/TLSの使用がポリシーによって要求されているというエラーが表示されたりする場合。
- トラブルシューティング:
ssl_version
を削除または変更: 明示的にssl_version
を設定するのをやめて、デフォルトのネゴシエーションに任せるか、異なるバージョンを試してください。- ネットワークの確認: FTP(21番ポート)およびFTPSのデータポート(通常は20番ポート、またはPASVモードでランダムなポート)がファイアウォールによってブロックされていないか確認してください。
- デバッグレベルの引き上げ:
ftplib
には直接的なデバッグ機能は少ないですが、ssl
モジュールやネットワークツール(Wiresharkなど)を使用して、SSL/TLSハンドシェイクの詳細を調べることができます。
- 考えられる原因:
- 指定したプロトコルバージョンがサーバーによってサポートされていないため、ハンドシェイクが完了しない。
- ファイアウォールやネットワーク設定が、特定のポートやプロトコルをブロックしている。
- 状況: 特定の
ssl_version
を設定すると、接続がハングアップしたり、タイムアウトしたりすることがあります。
- FTPクライアントツールでテスト: FileZillaなどのGUIベースのFTPクライアントツールでFTPS接続を試してみてください。これらのツールが接続できる場合、Pythonコードに問題がある可能性が高いです。FileZillaのログは、どのTLSバージョンが使用されたか、証明書検証の結果などが表示されるため、デバッグに非常に役立ちます。
- サーバー管理者に連絡: 最も確実な方法は、FTPSサーバーの管理者に連絡し、どのSSL/TLSプロトコルバージョンと暗号スイートがサポートされているか、証明書の状況などを確認することです。
ssl.create_default_context()
の活用: ほとんどの場合、ftplib.FTP_TLS
に直接ssl_version
を設定するよりも、ssl.create_default_context()
で作成したSSLContext
オブジェクトをcontext
引数として渡す方が良い選択です。これにより、Pythonは適切なデフォルト設定とセキュリティプラクティスを適用してくれます。- エラーメッセージをよく読む: Pythonのエラーメッセージは非常に役立ちます。
ssl.SSLError
の詳細や、含まれているOpenSSLのエラーコード(例:_ssl.c:XXXX
)を確認してください。
ここでは、両方のアプローチについて説明し、それぞれどのような場合に役立つかを解説します。
ftplib.FTP_TLS.ssl_version
のプログラミング例
例1: ssl_version
属性を直接使用する(基本的な使い方)
この例は、特定のSSL/TLSプロトコルバージョンをssl_version
属性に直接設定する方法を示します。これは、サーバーが非常に特定の古いプロトコルバージョンしかサポートしていない場合や、デバッグ目的で特定のバージョンを強制したい場合に限定的に使用されます。
注意
古いSSL/TLSバージョン(例: SSLv3, TLSv1.0, TLSv1.1)はセキュリティ上の脆弱性があるため、本番環境では使用を避けるべきです。
import ftplib
import ssl
import sys
# FTPサーバーの情報(ダミーです。実際のサーバーに置き換えてください)
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
print(f"--- 接続テスト: {FTP_HOST} ---")
try:
# ----------------------------------------------------
# ftplib.FTP_TLS インスタンスを作成
# ----------------------------------------------------
ftps = ftplib.FTP_TLS(FTP_HOST)
# ----------------------------------------------------
# SSL/TLSプロトコルバージョンを明示的に指定
# ここではTLSv1.2を指定していますが、サーバーに合わせて変更可能
# より新しいTLSv1.3があればそちらを推奨
# (ssl.PROTOCOL_TLSv1_3 は Python 3.6+ で利用可能)
# ----------------------------------------------------
if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
# 多くのモダンなサーバーがサポート
ftps.ssl_version = ssl.PROTOCOL_TLSv1_2
print(f"使用するSSL/TLSバージョン: TLSv1.2 (ssl.PROTOCOL_TLSv1_2)")
elif hasattr(ssl, 'PROTOCOL_TLSv1_3'):
# Python 3.6以降で利用可能、最新の推奨バージョン
ftps.ssl_version = ssl.PROTOCOL_TLSv1_3
print(f"使用するSSL/TLSバージョン: TLSv1.3 (ssl.PROTOCOL_TLSv1_3)")
else:
# フォールバックとして、Pythonのデフォルトに任せる
print("ssl.PROTOCOL_TLSv1_2またはTLSv1_3が見つかりませんでした。デフォルトのネゴシエーションを使用します。")
# 制御チャネルをセキュアにする (AUTH TLSコマンドを送信)
# 多くのFTPSサーバーではこれが必須
print("AUTH TLSコマンドを送信中...")
ftps.auth()
print("認証成功。")
# ログイン
print(f"ログイン中: {FTP_USER}")
ftps.login(FTP_USER, FTP_PASS)
print("ログイン成功。")
# データチャネルもセキュアにする (PROT Pコマンドを送信)
# ファイル転送なども暗号化される
print("PROT Pコマンドを送信中...")
ftps.prot_p()
print("データチャネルも保護されました。")
# ファイルリストを取得する例
print("\nファイルリスト:")
files = ftps.nlst()
for f in files:
print(f" - {f}")
# 接続を閉じる
print("\n接続を閉じます。")
ftps.quit()
print("接続終了。")
except ftplib.all_errors as e:
print(f"\n[FTP エラー]: {e}", file=sys.stderr)
except ssl.SSLError as e:
print(f"\n[SSL/TLS エラー]: {e}", file=sys.stderr)
print("ヒント: サーバーが指定されたSSL/TLSバージョンをサポートしているか確認してください。", file=sys.stderr)
print(" または、ssl.create_default_context() の使用を検討してください。", file=sys.stderr)
except Exception as e:
print(f"\n[予期せぬエラー]: {e}", file=sys.stderr)
このコードのポイント
ftps.prot_p()
でデータチャネルもTLSで保護します。これはファイル転送などを行う際に重要です。ftps.login()
で認証します。ftps.auth()
で制御チャネルをTLSで保護します。ftps.ssl_version = ssl.PROTOCOL_TLSv1_2
のように、ssl
モジュールで定義されているプロトコル定数を使ってバージョンを指定します。ftplib.FTP_TLS(FTP_HOST)
でFTPS接続を開始します。
例2: ssl.SSLContext
を使用してcontext
引数を渡す(推奨される方法)
この方法は、ftplib.FTP_TLS
のcontext
引数にssl.SSLContext
オブジェクトを渡します。SSLContext
は、SSL/TLSのバージョン選択、証明書検証の設定、許容される暗号スイートなど、より詳細なセキュリティ設定をカプセル化できます。
ssl.create_default_context()
を使用することで、Pythonは安全で互換性のあるデフォルト設定を自動的に適用してくれます。これにより、通常はssl_version
を明示的に設定する必要がなくなります。
import ftplib
import ssl
import sys
import certifi # pip install certifi でインストール (信頼できるCA証明書バンドルを提供)
# FTPサーバーの情報(ダミーです。実際のサーバーに置き換えてください)
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
print(f"--- 接続テスト (SSLContext使用): {FTP_HOST} ---")
try:
# ----------------------------------------------------
# SSLコンテキストの作成 (推奨される方法)
# certifi.where() を使用して、システムにインストールされている信頼できるCA証明書バンドルを指定
# これにより、サーバーの証明書検証が正しく行われる
# ----------------------------------------------------
context = ssl.create_default_context(cafile=certifi.where())
# --- 必要に応じてSSLコンテキストの設定を調整 ---
# 例1: 特定のTLSバージョンのみを許可する場合 (通常は不要、デフォルトで最適なバージョンがネゴシエートされる)
# context.minimum_version = ssl.TLSVersion.TLSv1_2
# context.maximum_version = ssl.TLSVersion.TLSv1_2
# 例2: ホスト名の検証を無効にする場合 (セキュリティリスクあり、デバッグ目的以外では非推奨)
# context.check_hostname = False
# 例3: 証明書の検証自体を無効にする場合 (重大なセキュリティリスクあり、絶対的な最終手段)
# context.verify_mode = ssl.CERT_NONE
# ----------------------------------------------------
# ftplib.FTP_TLS インスタンスを作成し、context引数にSSLコンテキストを渡す
# ----------------------------------------------------
ftps = ftplib.FTP_TLS(FTP_HOST, context=context)
# 制御チャネルをセキュアにする
print("AUTH TLSコマンドを送信中...")
ftps.auth()
print("認証成功。")
# ログイン
print(f"ログイン中: {FTP_USER}")
ftps.login(FTP_USER, FTP_PASS)
print("ログイン成功。")
# データチャネルもセキュアにする
print("PROT Pコマンドを送信中...")
ftps.prot_p()
print("データチャネルも保護されました。")
# ファイルリストを取得する例
print("\nファイルリスト:")
files = ftps.nlst()
for f in files:
print(f" - {f}")
# 接続を閉じる
print("\n接続を閉じます。")
ftps.quit()
print("接続終了。")
except ftplib.all_errors as e:
print(f"\n[FTP エラー]: {e}", file=sys.stderr)
except ssl.SSLError as e:
print(f"\n[SSL/TLS エラー]: {e}", file=sys.stderr)
print("ヒント: サーバーの証明書が信頼できるか、またはSSLContextの設定を確認してください。", file=sys.stderr)
except Exception as e:
print(f"\n[予期せぬエラー]: {e}", file=sys.stderr)
- 必要に応じて
context.minimum_version
やcontext.check_hostname
などのプロパティを設定して、より詳細な制御が可能です。ただし、デフォルト設定が最も安全で互換性が高いことがほとんどです。 ssl_version
を明示的に設定する必要がないため、Pythonがサーバーとの互換性とセキュリティを考慮して最適なプロトコルバージョンを自動的にネゴシエートします。ftps = ftplib.FTP_TLS(FTP_HOST, context=context)
のように、コンストラクタにcontext
引数を渡します。context = ssl.create_default_context(cafile=certifi.where())
でデフォルトのSSLContext
を作成します。certifi.where()
は、Pythonが信頼するCA証明書のバンドルのパスを返します。これにより、サーバーの証明書が自動的に検証されます。
ftplib.FTP_TLS.ssl_version
を直接設定するのは、特定のレガシーサーバーとの互換性のために、どうしても特定のTLSバージョンを強制する必要がある場合にのみ検討すべきです。その場合でも、セキュリティ上のリスクを十分に理解し、最新のプロトコルバージョンを使用するように努めるべきです。- ほとんどの場合、
ssl.create_default_context()
を使用してcontext
引数を渡す方法が推奨されます。- これは最も安全で、最新のセキュリティプラクティスに従い、多くのサーバーとの互換性を確保します。
- 証明書の検証が自動的に行われるため、MITM(中間者)攻撃を防ぐのに役立ちます。
ftplib.FTP_TLS.ssl_version
属性を直接設定する方法は、特定のSSL/TLSプロトコルバージョンを強制する手段として存在しますが、Pythonのssl
モジュールが提供するより柔軟で堅牢な方法が存在します。これは主にssl.SSLContext
オブジェクトを介したアプローチで、現代のFTPSクライアントプログラミングにおいて推奨される代替手段です。
なぜ代替方法が推奨されるのか?
ssl_version
を直接設定することにはいくつかの欠点があります。
- 柔軟性の欠如: プロトコルバージョンのみを制御し、証明書検証、許容される暗号スイート、SNI(Server Name Indication)などの詳細なSSL/TLS設定を制御できません。
- セキュリティリスク: 古いプロトコルバージョン(例: SSLv3, TLSv1.0, TLSv1.1)を強制すると、既知の脆弱性に晒される可能性があります。
- 互換性の問題: サーバーが指定したバージョンをサポートしていない場合、接続が失敗します。
これらの問題を解決するために、ssl.SSLContext
を使用します。
最も主要な代替方法は、ssl.SSLContext
オブジェクトを作成し、それをftplib.FTP_TLS
クラスのコンストラクタのcontext
引数に渡すことです。
ssl.create_default_context()を使用する(最も推奨される方法)
これは最も簡単で、ほとんどのユースケースで推奨される方法です。ssl.create_default_context()
は、オペレーティングシステムの信頼ストアやcertifi
パッケージ(インストールされている場合)などから信頼できるCA証明書を自動的にロードし、安全なデフォルト設定でSSLContext
オブジェクトを生成します。
import ftplib
import ssl
import certifi # pip install certifi でインストール推奨
# FTPサーバー情報 (実際の情報に置き換えてください)
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
try:
# ----------------------------------------------------
# 1. デフォルトのSSLContextを作成
# - OSの信頼ストアまたはcertifiからCA証明書をロードし、サーバー証明書の検証を自動的に行います。
# - 安全なデフォルト設定 (最新のTLSバージョンネゴシエーション、強力な暗号スイートなど) が適用されます。
# ----------------------------------------------------
context = ssl.create_default_context(cafile=certifi.where())
# ----------------------------------------------------
# 2. ftplib.FTP_TLS インスタンスを初期化し、context 引数を渡す
# ----------------------------------------------------
with ftplib.FTP_TLS(FTP_HOST, context=context) as ftps:
# 制御チャネルをセキュアにする (AUTH TLSコマンドを送信)
ftps.auth()
# ログイン
ftps.login(FTP_USER, FTP_PASS)
# データチャネルもセキュアにする (PROT Pコマンドを送信)
ftps.prot_p()
# ファイルリストを取得する例
print("ファイルリスト:")
files = ftps.nlst()
for f in files:
print(f" - {f}")
# 接続は 'with' ステートメントの終了時に自動的に閉じられます (ftplib 3.9+ の場合)
# 以前のバージョンでは ftps.quit() を明示的に呼び出す必要があります
print("\n接続終了。")
except ftplib.all_errors as e:
print(f"FTPエラーが発生しました: {e}")
except ssl.SSLError as e:
print(f"SSL/TLSエラーが発生しました: {e}")
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
利点
- 簡潔さ: ほとんどの設定をPythonに任せるため、コードがシンプルになります。
- 証明書検証: サーバーの証明書を自動的に検証し、Man-in-the-Middle攻撃を防ぎます。
- 自動的なセキュリティ: 最新のTLSプロトコルバージョンを優先し、既知の脆弱性がある設定を避けます。
ssl.SSLContextをカスタマイズする(より詳細な制御が必要な場合)
特定の要件がある場合、ssl.create_default_context()
で作成したSSLContext
オブジェクトをさらにカスタマイズできます。これは、ssl_version
を直接設定するよりもはるかに強力な代替手段です。
import ftplib
import ssl
import certifi
# FTPサーバー情報
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
# 証明書のパス(例: 特定のCA証明書を使用する場合)
# CA_CERT_PATH = '/path/to/your/custom_ca.pem'
try:
# ----------------------------------------------------
# 1. SSLContextの作成とカスタマイズ
# ----------------------------------------------------
context = ssl.create_default_context(cafile=certifi.where()) # デフォルトの安全な設定で開始
# カスタマイズ例1: 許可するTLSバージョンの範囲を厳密に指定する
# 例: TLSv1.2 のみを許可する場合 (Python 3.7+ の TLSVersion 列挙型を使用)
# context.minimum_version = ssl.TLSVersion.TLSv1_2
# context.maximum_version = ssl.TLSVersion.TLSv1_2
# カスタマイズ例2: ホスト名の検証を無効にする (セキュリティリスクあり、デバッグ/テスト環境のみで利用)
# context.check_hostname = False
# カスタマイズ例3: 証明書の検証自体を無効にする (重大なセキュリティリスクあり、緊急のデバッグ/テスト環境のみで利用)
# context.verify_mode = ssl.CERT_NONE
# カスタマイズ例4: 特定のCA証明書ファイルを追加する
# context.load_verify_locations(cafile=CA_CERT_PATH)
# カスタマイズ例5: 許可する暗号スイートを指定する (より高度なセキュリティ要件がある場合)
# context.set_ciphers('ECDHE+AESGCM:CHACHA20:!RC4:!aNULL:!eNULL:!MD5')
# ----------------------------------------------------
# 2. ftplib.FTP_TLS インスタンスを初期化し、カスタマイズされた context を渡す
# ----------------------------------------------------
with ftplib.FTP_TLS(FTP_HOST, context=context) as ftps:
ftps.auth()
ftps.login(FTP_USER, FTP_PASS)
ftps.prot_p()
print("ファイルリスト:")
files = ftps.nlst()
for f in files:
print(f" - {f}")
print("\n接続終了。")
except ftplib.all_errors as e:
print(f"FTPエラーが発生しました: {e}")
except ssl.SSLError as e:
print(f"SSL/TLSエラーが発生しました: {e}")
print("ヒント: SSLContextの設定を確認してください。特に証明書検証、ホスト名チェック、TLSバージョン/暗号スイートの設定。", file=sys.stderr)
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
利点
- 特定の環境への対応: 自己署名証明書や特殊なCA証明書を持つサーバーなど、標準設定では接続できない特殊な環境に対応できます。
- セキュリティの強化: 厳格なセキュリティポリシーを適用できます(例: 弱い暗号スイートの禁止)。
- きめ細やかな制御: SSL/TLSハンドシェイクのほぼすべての側面を制御できます。
ftplib.FTP_TLS
は明示的FTPS (Explicit FTPS) に対応しています。これは、まず平文のFTP接続を確立し、次にAUTH TLS
またはAUTH SSL
コマンドを送信してSSL/TLSハンドシェイクを開始する方式です。
一方、FTPには暗黙的FTPS (Implicit FTPS) という別のモードもあります。これは、接続開始時から特定のポート(通常は990/TCP)でSSL/TLSハンドシェイクが開始される方式です。ftplib
モジュールは、直接的には暗黙的FTPSをサポートしていません。もし暗黙的FTPSサーバーに接続する必要がある場合は、socket
モジュールとssl
モジュールを組み合わせて低レベルで実装するか、より高機能なサードパーティライブラリ(例: PyFtps
など)の利用を検討する必要があります。
ftplib.FTP_TLS.ssl_version
を直接使用する代わりに、ssl.create_default_context()
を用いてssl.SSLContext
オブジェクトを作成し、それをftplib.FTP_TLS
のcontext
引数に渡す方法が強く推奨されます。これにより、ほとんどのFTPS接続要件に対応でき、安全性と互換性を最大化できます。