使用 STARTTLS 从 Python 发送电子邮件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33857698/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Sending email from Python using STARTTLS
提问by user5415068
I want to send emails with a Python script by using Python's smtplib.
我想使用 Python 的 smtplib 发送带有 Python 脚本的电子邮件。
The script should only send the email, if an encrypted connection to the server can be established. To encrypt the connection to port 587 I want to use STARTTLS.
如果可以建立到服务器的加密连接,脚本应该只发送电子邮件。要加密到端口 587 的连接,我想使用 STARTTLS。
Using some examples I have written the following code:
使用一些示例,我编写了以下代码:
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 are variables in my script. I have two questions:
msg、主机、端口、用户、密码是我脚本中的变量。我有两个问题:
- Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).
- Should I use the ehlo() method of the SMTP object? In some examples it is called explicitly before and after calling starttls(). On the other side in the documentation of smptlib it is written, that sendmail() will call it, if it is necessary.
- 连接是始终加密还是容易受到 STRIPTLS 攻击(https://en.wikipedia.org/wiki/STARTTLS)。
- 我应该使用 SMTP 对象的 ehlo() 方法吗?在某些示例中,它在调用 starttls() 之前和之后被显式调用。另一方面,在 smptlib 的文档中,如果有必要, sendmail() 会调用它。
[Edit]
[编辑]
@tintin explained, that ssl.create_default_context()
can possibly lead to insecure connections. Thus I have changed the code using some examples in the following way:
@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)
For the cipher setting I used some code of a recent version of ssl.create_default_context()
. Are these settings appropriate?
对于密码设置,我使用了一些最新版本的ssl.create_default_context()
. 这些设置合适吗?
Note:In the code of my original question is one mistake. Here is the correct version of the concerned line:
注意:在我原来的问题的代码中是一个错误。这是相关行的正确版本:
smtp_server.starttls(context=context)
[\Edit]
[\编辑]
采纳答案by tintin
Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).
连接是始终加密还是容易受到 STRIPTLS 攻击(https://en.wikipedia.org/wiki/STARTTLS)。
long story short: starttls can be stripped from smtplib <=py3.5.1rc1 <=py2.7.10 if you do not check response codes for .starttls()
长话短说:如果不检查响应代码,可以从 smtplib <=py3.5.1rc1 <=py2.7.10 中剥离 starttls .starttls()
explicitly calling
.starttls()
on smtp servers supporting it with a malicious MitM stripping yourSTARTTLS
command and forging a non220
response will NOT negotiate ssl, nor raise an exceptionand therefore leave your communication unencrypted - ergo it is vulnerable to striptlsunless you manually verify that the response to.starttls()[0]==220
or the internal.sock
got ssl wrapped.Here's a python 2.7.9 smtplib communication with an example similar to yours that failed to negotiate starttls by having the server or a MitM reply
999 NOSTARTTLS
instead of the200
. No explicit check for the 200 response code in the client script, no exception due to a failed starttls attempt therefore mail transport not encrypted: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
explicitly calling
.starttls()
on smtp servers not supporting STARTTLS - or a MitM stripping this capability from the servers response - will raiseSMTPNotSupportedError
. see code below.general note: encryption also depends on the configured cipherspec i.e. your SSLContext which in your case is created by
ssl.create_default_context()
. Note that it is totally valid to configure your SSLContext to allow cipherspecs that authenticate but do not encrypt (if offered/allowed by both server and client). E.g.TLS_RSA_WITH_NULL_SHA256
.NULL-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=None Mac=SHA256
According to this answerpython pre 2.7.9/3.4.3does NOTattempt to enforce certificate validation for the default ssl context and therefore is vulnerable to ssl interception. Starting with Python 2.7.9/3.4.3certificate validation is enforced for the default context. This also means, that you'll have to manually enable certificate validation for pre 2.7.9/3.4.3(by creating a custom sslcontext) otherwise any untrusted certificate might be accepted.
显式调用
.starttls()
支持它的 smtp 服务器,恶意中间人剥离您的STARTTLS
命令并伪造不220
响应将不会协商 ssl,也不会引发异常,因此使您的通信未加密 - 因此它很容易受到 striptls 的攻击,除非您手动验证对.starttls()[0]==220
或内部.sock
得到了 ssl 包裹。这是一个 python 2.7.9 smtplib 通信,其中包含一个类似于您的示例,该示例无法通过让服务器或中间人回复
999 NOSTARTTLS
而不是200
. 没有对客户端脚本中的 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()
不支持 STARTTLS 的 smtp 服务器 - 或从服务器响应中剥离此功能的中间人 - 将引发SMTPNotSupportedError
. 见下面的代码。一般注意事项:加密还取决于配置的密码规范,即您的 SSLContext,在您的情况下,它是由
ssl.create_default_context()
. 请注意,将 SSLContext 配置为允许进行身份验证但不加密的密码规范是完全有效的(如果服务器和客户端都提供/允许)。例如TLS_RSA_WITH_NULL_SHA256
。NULL-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=无 Mac=SHA256
根据这个答案蟒蛇预2.7.9 / 3.4.3确实不试图强制执行默认的SSL上下文证书验证,因此很容易受到SSL拦截。从 Python 2.7.9/3.4.3开始,对默认上下文强制执行证书验证。这也意味着,您必须手动启用2.7.9/3.4.3 之前的证书验证(通过创建自定义 sslcontext),否则可能会接受任何不受信任的证书。
Should I use the ehlo() method of the SMTP object? In some examples it is called explicitly before and after calling starttls(). On the other side in the documentation of smptlib it is written, that sendmail() will call it, if it is necessary.
我应该使用 SMTP 对象的 ehlo() 方法吗?在某些示例中,它在调用 starttls() 之前和之后被显式调用。另一方面,在 smptlib 的文档中,如果有必要, sendmail() 会调用它。
.sendmail()
,.send_message
and.starttls()
will implicitly call.ehlo_or_helo_if_needed()
therefore there is no need to explicitly call it again. This is also
.sendmail()
,.send_message
并且.starttls()
会隐式调用,.ehlo_or_helo_if_needed()
因此不需要再次显式调用它。这也是
see source::smtplib::starttls (cpython, inofficial github)below:
请参阅下面的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)