Pythonスクリプトを使用して、Pythonのsmtplibを使用してメールを送信したい。
サーバーへの暗号化された接続を確立できる場合、スクリプトは電子メールを送信するだけです。ポート587への接続を暗号化するには、STARTTLSを使用します。
いくつかの例を使用して、次のコードを記述しました。
_smtp_server = smtplib.SMTP(Host, port=port)
context = ssl.create_default_context()
smtp_server.starttls(context)
smtp_server.login(user, password)
smtp_server.send_message(msg)
_
msg、Host、port、user、passwordはスクリプトの変数です。 2つの質問があります。
[編集]
@tintinは、ssl.create_default_context()
が安全でない接続を引き起こす可能性があると説明しました。したがって、次のようにいくつかの例を使用してコードを変更しました。
__DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
'!eNULL:!MD5')
smtp_server = smtplib.SMTP(Host, port=port)
# only TLSv1 or higher
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.set_ciphers(_DEFAULT_CIPHERS)
context.set_default_verify_paths()
context.verify_mode = ssl.CERT_REQUIRED
if smtp_server.starttls(context=context)[0] != 220:
return False # cancel if connection is not encrypted
smtp_server.login(user, password)
_
暗号設定には、ssl.create_default_context()
の最近のバージョンのコードを使用しました。これらの設定は適切ですか?
注:元の質問のコードには1つの間違いがあります。関連する行の正しいバージョンは次のとおりです。
_smtp_server.starttls(context=context)
_
[\ Edit]
接続は常に暗号化されていますか、それともSTRIPTLS攻撃に対して脆弱ですか( https://en.wikipedia.org/wiki/STARTTLS )。
短い話:.starttls()
の応答コードを確認しない場合、starttlsはsmtplib <= py3.5.1rc1 <= py2.7.10から削除できます
悪意のあるMitMがSTARTTLS
コマンドを取り除き、220
以外の応答を偽造してそれをサポートするSMTPサーバーで明示的に.starttls()
を呼び出すと、sslをネゴシエートしません。また、例外を発生させないため、通信を暗号化しないでください-手動で確認しない限り、それを実行するはstriptlsに対して脆弱です.starttls()[0]==220
または内部.sock
への応答は、sslでラップされています。
これはpython 2.7.9 smtplib通信で、サーバーまたはMitMに999 NOSTARTTLS
の代わりに200
を応答させることでstarttlsのネゴシエーションに失敗した例に似ています。クライアントスクリプトで200応答コードを明示的にチェックせず、starttlsの試行が失敗したことによる例外がないため、メール転送は暗号化されません。
220 xx ESMTP
250-xx
250-SIZE 20480000
250-AUTH LOGIN
250-STARTTLS
250 HELP
STARTTLS
999 NOSTARTTLS
mail FROM:<[email protected]> size=686
250 OK
rcpt TO:<[email protected]>
250 OK
data
sTARTTLSをサポートしていないSMTPサーバーで.starttls()
を明示的に呼び出すと、またはサーバーの応答からこの機能を取り除いたMitMでSMTPNotSupportedError
が発生します。以下のコードを参照してください。
一般的な注意:暗号化は、構成された暗号仕様、つまり、ssl.create_default_context()
によって作成されたSSLContextにも依存します。認証するが暗号化しないcipherspecsを許可するようにSSLContextを構成することは完全に有効であることに注意してください(サーバーとクライアントの両方で提供/許可されている場合)。例えば。 TLS_RSA_WITH_NULL_SHA256
。
NULL-SHA256 TLSv1.2 Kx = RSA Au = RSA Enc = None Mac = SHA256
によれば、この答え pythonpre 2.7.9/3.4.3は[〜#〜] not [〜#〜]は、デフォルトのsslコンテキストに対して証明書の検証を強制しようとするため、sslインターセプトに対して脆弱です。 Python2.7.9/3.4.3で始まる証明書の検証は、デフォルトのコンテキストに対して実施されます。これは、pre 2.7.9/3.4.3の証明書検証を手動で有効にする必要があることも意味します(カスタムsslcontextを作成することにより)。信頼されていない証明書が受け入れられる可能性があります。
SMTPオブジェクトのehlo()メソッドを使用する必要がありますか?いくつかの例では、starttls()を呼び出す前後に明示的に呼び出されます。一方、smptlibのドキュメントには、必要に応じてsendmail()がそれを呼び出すように記述されています。
.sendmail()
、.send_message
、および.starttls()
は暗黙的に.ehlo_or_helo_if_needed()
を呼び出すため、明示的に再度呼び出す必要はありません。これもまた以下の source :: smtplib :: starttls(cpython、inofficial github) を参照してください:
def starttls(self, keyfile=None, certfile=None, context=None):
"""Puts the connection to the SMTP server into TLS mode.
If there has been no previous EHLO or HELO command this session, this
method tries ESMTP EHLO first.
If the server supports TLS, this will encrypt the rest of the SMTP
session. If you provide the keyfile and certfile parameters,
the identity of the SMTP server and client can be checked. This,
however, depends on whether the socket module really checks the
certificates.
This method may raise the following exceptions:
SMTPHeloError The server didn't reply properly to
the helo greeting.
"""
self.ehlo_or_helo_if_needed()
if not self.has_extn("starttls"):
raise SMTPNotSupportedError(
"STARTTLS extension not supported by server.")
(resp, reply) = self.docmd("STARTTLS")
if resp == 220:
if not _have_ssl:
raise RuntimeError("No SSL support included in this Python")
if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
if context is None:
context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.sock = context.wrap_socket(self.sock,
server_hostname=self._Host)
self.file = None
# RFC 3207:
# The client MUST discard any knowledge obtained from
# the server, such as the list of SMTP service extensions,
# which was not obtained from the TLS negotiation itself.
self.helo_resp = None
self.ehlo_resp = None
self.esmtp_features = {}
self.does_esmtp = 0
return (resp, reply)