Sending email from Python using STARTTLS
我想通过使用Python的smtplib发送带有Python脚本的电子邮件。
如果可以建立到服务器的加密连接,则脚本仅应发送电子邮件。
要加密到端口587的连接,我想使用STARTTLS。
使用一些示例,我编写了以下代码:
1 2 3 4 5 | 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) |
信息,主机,端口,用户,密码是我脚本中的变量。
我有两个问题:
- 连接是始终加密的还是容易受到STRIPTLS攻击(https://en.wikipedia.org/wiki/STARTTLS)。
- 我应该使用SMTP对象的ehlo()方法吗?在某些示例中,在调用starttls()之前和之后都对其进行显式调用。在smptlib文档的另一面中,如果需要的话,sendmail()将调用它。
[编辑]
@tintin解释说,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | _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) |
对于密码设置,我使用了
注意:在我原始问题的代码中,有一个错误。这是有关行的正确版本:
1 | smtp_server.starttls(context=context) |
[编辑]
Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).
长话短说:如果您不检查
-
在带有恶意MitM的smtp服务器上显式调用
.starttls() 并使用恶意MitM剥离STARTTLS 命令并伪造非220 响应将不会协商ssl,也不会引发异常,因此您的通信未加密-因此,它容易受到striptls除非您手动验证对.starttls()[0]==220 或内部.sock 的响应是否包含ssl包装。这是python 2.7.9 smtplib通信,与您的示例类似,该示例通过让服务器或MitM答复
999 NOSTARTTLS 而不是200 未能协商starttls。没有显式检查客户端脚本中的200响应代码,由于starttls尝试失败而没有异常,因此邮件传输未加密:1
2
3
4
5
6
7
8
9
10
11
12
13220 xx ESMTP
250-xx
250-SIZE 20480000
250-AUTH LOGIN
250-STARTTLS
250 HELP
STARTTLS
999 NOSTARTTLS
mail FROM: size=686
250 OK
rcpt TO:
250 OK
data -
在不支持STARTTLS的smtp服务器上显式调用
.starttls() -或从服务器响应中剥离此功能的MitM-将引发SMTPNotSupportedError 。参见下面的代码。 -
一般说明:加密还取决于配置的密码规范,即您的SSLContext(在您的情况下是由
ssl.create_default_context() 创建的)。请注意,将SSLContext配置为允许进行身份验证但不加密的密文是完全有效的(如果服务器和客户端均提供/允许)。例如。TLS_RSA_WITH_NULL_SHA256 。NULL-SHA256 TLSv1.2 Kx = RSA Au = RSA Enc =无Mac = SHA256
-
根据这个答案,python 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.
-
.sendmail() ,.send_message 和.starttls() 将隐式调用.ehlo_or_helo_if_needed() ,因此无需再次显式调用它。这也是
请参见下面的源代码:: smtplib :: starttls(cpython,inofficial github):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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) |