Python ftplibのFTPSプログラミング:エラー回避とベストプラクティス

2025-06-06

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)

  • トラブルシューティング:
    1. プロトコルバージョンの変更:
      • まず、より新しいTLSバージョン(ssl.PROTOCOL_TLSv1_2ssl.PROTOCOL_TLSv1_3)を試してみてください。
      • それでもだめな場合は、サーバーが比較的古いSSL/TLSバージョンしかサポートしていない可能性も考慮し、ssl.PROTOCOL_TLSv1_1などを試すことも最終手段として考えられます。ただし、セキュリティの観点からは推奨されません。
      • 推奨: ssl.create_default_context()を使用し、明示的にminimum_versionを設定しないことで、デフォルトのネゴシエーションに任せるのが最も堅牢な方法です。
      1. 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()
        
      2. PythonとOpenSSLのバージョン確認: 使用しているPython環境のOpenSSLライブラリが最新であるか確認してください。古いバージョンでは、新しい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)

  • トラブルシューティング:
    1. 証明書の検証を無効にする(開発・テスト目的のみ、本番環境では非推奨): セキュリティリスクを伴うため、本番環境では絶対に行わないでください。デバッグ目的で一時的に証明書の検証をスキップする場合にのみ使用します。
      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()
      
    2. 正しいCA証明書の設定:
      • サーバーの証明書を発行したCAのルート証明書や中間証明書を、クライアント側の信頼できる証明書ストア(例: certifiパッケージが提供するCAバンドル、またはシステム全体の証明書ストア)に追加します。
      • ssl.SSLContextを作成する際に、cafilecapath引数で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)
      # ...
      
    3. サーバー管理者に確認: サーバーのSSL証明書が正しく設定されているか、有効期限が切れていないかなどをサーバー管理者に確認してください。
  • 考えられる原因:
    • サーバーの証明書が自己署名されている、または信頼できない認証局(CA)によって署名されている。
    • クライアント側の証明書ストアに、サーバーの証明書を発行したCAのルート証明書が含まれていない。
    • 証明書が期限切れ、または無効である。
  • エラーの状況: これはssl_version自体に直接関係するエラーではありませんが、FTPS接続で頻繁に発生するSSL/TLS関連のエラーです。サーバーのSSL証明書をクライアントが検証できない場合に発生します。

エラー: ftplib.error_perm: 530 Login authentication failed. または ftplib.error_perm: 534 Policy requires SSL.

  • トラブルシューティング:
    1. 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()
      
    2. ユーザー名とパスワードの再確認: 当然ですが、これが最も単純な間違いであることも多いです。
  • 考えられる原因:
    • ユーザー名やパスワードが間違っている(これはSSL/TLSとは直接関係ないが、一般的なエラー)。
    • FTPSサーバーがSSL/TLSによる暗号化接続を必須としているにもかかわらず、平文のFTP接続を試みている。
    • ftps.auth()を呼び出していない、またはftps.prot_p()を呼び出す前にデータ接続(例: nlst(), retrbinary()など)を試みている。
  • エラーの状況: ログインに失敗したり、SSL/TLSの使用がポリシーによって要求されているというエラーが表示されたりする場合。
  • トラブルシューティング:
    1. ssl_versionを削除または変更: 明示的にssl_versionを設定するのをやめて、デフォルトのネゴシエーションに任せるか、異なるバージョンを試してください。
    2. ネットワークの確認: FTP(21番ポート)およびFTPSのデータポート(通常は20番ポート、またはPASVモードでランダムなポート)がファイアウォールによってブロックされていないか確認してください。
    3. デバッグレベルの引き上げ: 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_TLScontext引数に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_versioncontext.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を直接設定することにはいくつかの欠点があります。

  1. 柔軟性の欠如: プロトコルバージョンのみを制御し、証明書検証、許容される暗号スイート、SNI(Server Name Indication)などの詳細なSSL/TLS設定を制御できません。
  2. セキュリティリスク: 古いプロトコルバージョン(例: SSLv3, TLSv1.0, TLSv1.1)を強制すると、既知の脆弱性に晒される可能性があります。
  3. 互換性の問題: サーバーが指定したバージョンをサポートしていない場合、接続が失敗します。

これらの問題を解決するために、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_TLScontext引数に渡す方法が強く推奨されます。これにより、ほとんどのFTPS接続要件に対応でき、安全性と互換性を最大化できます。