使用STARTTL从Python发送电子邮件

2024-05-23 20:47:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我想用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)

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)

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

注意:在我最初问题的代码中是一个错误。以下是相关行的正确版本:

smtp_server.starttls(context=context)

[\Edit]


Tags: 代码脚本defaulthostsslserverportcontext
1条回答
网友
1楼 · 发布于 2024-05-23 20:47:03

Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).

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

  • 在支持它的smtp服务器上显式调用.starttls(),使用恶意MitM剥离您的STARTTLS命令并伪造非220响应不会协商ssl,也不会引发异常,因此保持通信未加密-因此,除非手动验证对.starttls()[0]==220的响应或内部.sock的响应已被ssl包装,否则它容易受到striptls的攻击。

    下面是一个python 2.7.9smtplib通信示例,它与您的示例类似,通过让服务器或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:<a@b.com> size=686
    250 OK
    rcpt TO:<a@b.com>
    250 OK
    data
    
  • 在不支持STARTTLS的smtp服务器上显式调用.starttls()或MitM从服务器响应中剥离此功能将引发SMTPNotSupportedError。见下面的代码。

  • 一般注意:加密还取决于配置的cipherspec,即SSLContext,在您的情况下,它是由ssl.create_default_context()创建的。注意,将SSLContext配置为允许进行身份验证但不加密的cipherspec(如果服务器和客户端都提供/允许)是完全有效的。E、 g.TLS_RSA_WITH_NULL_SHA256

    NULL-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=None Mac=SHA256

  • 根据this answerpythonpre 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(),因此无需再次显式调用它。这也是

请参见下面的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)

相关问题 更多 >