使用STARTTLS从Python发送电子邮件

use*_*068 3 python email encryption smtplib starttls

我想使用Python的smtplib发送带有Python脚本的电子邮件.

如果可以建立与服务器的加密连接,则脚本应仅发送电子邮件.要加密到端口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)
Run Code Online (Sandbox Code Playgroud)

msg,host,port,user,password是我脚本中的变量.我有两个问题:

  • 连接是否始终加密或是否容易受到STRIPTLS攻击(https://en.wikipedia.org/wiki/STARTTLS).
  • 我应该使用SMTP对象的ehlo()方法吗?在某些示例中,在调用starttls()之前和之后显式调用它.另一方面,在smptlib的文档中写道,如果有必要,sendmail()将调用它.

[编辑]

@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)
Run Code Online (Sandbox Code Playgroud)

对于密码设置,我使用了最近版本的一些代码ssl.create_default_context().这些设置是否合适?

注意:在原始问题的代码中是一个错误.以下是相关行的正确版本:

smtp_server.starttls(context=context)
Run Code Online (Sandbox Code Playgroud)

[\编辑]

tin*_*tin 9

连接是否始终加密或是否容易受到STRIPTLS攻击(https://en.wikipedia.org/wiki/STARTTLS).

长话短说:如果不检查响应代码,可以从smtplib <= py3.5.1rc1 <= py2.7.10中删除starttls .starttls()

  • 显式调用.starttls()的SMTP服务器上有恶意中间人剥皮你支持它STARTTLS的命令和锻造非220响应将无法协商SSL,也没有引发异常,因此留下您的通信加密- ERGO它很容易受到striptls除非你手动验证响应.starttls()[0]==220或内部.sock得到ssl包裹.

    这是一个python 2.7.9 smtplib通信,其中一个类似于你的示例,通过使用服务器或MitM回复999 NOSTARTTLS代替而无法协商starttls 200.没有明确检查客户端脚本中的200响应代码,由于starttls尝试失败而没有异常,因此邮件传输未加密:

    220 xx ESMTP
    250-xx
    250-SIZE 20480000
    250-AUTH LOGIN
    250-STARTTLS
    250 HELP
    STARTTLS
    999 NOSTARTTLS
    mail FROM:<a@b.com> size=686
    250 OK
    rcpt TO:<a@b.com>
    250 OK
    data
    
    Run Code Online (Sandbox Code Playgroud)
  • 明确调用.starttls()不支持STARTTLS的smtp服务器 - 或者从服务器响应中剥离此功能的MitM - 将会提升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),否则可能会接受任何不受信任的证书.

我应该使用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)
Run Code Online (Sandbox Code Playgroud)