【问题标题】:Python connect to MySQL failed with error "dh key too small"Python 连接到 MySQL 失败,出现错误“dh key too small”
【发布时间】:2016-01-11 04:42:42
【问题描述】:

我正在尝试连接到公司的 MySQL 数据库。政策是我使用 SSL 连接。

我收到了usernameCA certificatecertificateprivate key

如果我使用HeidiSQL,我可以毫无问题地连接。

但是,我无法使用 Python 2.7.11mysql-connector-python-rf (v2.1.3) 连接。

这是我的简单连接测试程序:

from __future__ import print_function, division, unicode_literals

import mysql.connector
from mysql.connector.constants import ClientFlag

cnx = mysql.connector.connect(
    user='myusername',
    host='myserver.example.com',
    port=3306,
    client_flags=[ClientFlag.SSL],
    ssl_ca='/path/to/ca.crt',
    ssl_cert='/path/to/user.crt',
    ssl_key='/path/to/user.key'
)

cnx.close()

总是以这个例外结束:

mysql.connector.errors.InterfaceError: 2055: Lost connection to MySQL server at
'myserver.example.com:3306', system error: 1 [SSL: SSL_NEGATIVE_LENGTH] dh key too small
(_ssl.c:590)

我尝试过寻找答案,但除了更改服务器端的设置之外似乎没有其他解决方案,这简直是不行。

如何解决和/或解决此问题?


更多信息更新:我正在使用 PyCharm 5.0.3

在 Windows 10 上创建我的程序

【问题讨论】:

    标签: python mysql ssl


    【解决方案1】:

    在撰写本文时,使用 Oracle 的 mysql-connector-python 2.1.4 纯 python 模块(您使用的是版本 2.1.3 的一个分支)支持连接字符串中未记录的连接配置 kwarg ssl_cipher (因为它基本上将它传递给 python 的 ssl 模块)。因此,从密码列表中删除所有 Diffie-Hellman 密码,您可能能够解决此问题,具体取决于您的 mysql 服务器是否支持非 Diffie-Hellman 密码。以下适用于官方 Python 2.7.12 amd64 win32 针对 MySQL 5.6.17-65.0-rel65.0-log 。 注意:这仅适用于处理 pure python 版本的模块!。如果使用编译好的C扩展模块,可能不接受ssl_cipher, YMMV。

    import mysql.connector
    import getpass
    
    dsn = {
        'database': 'INFORMATION_SCHEMA',
        'host': 'mysqlserver',
        'port': '3306',
        'ssl_ca': '', 
        # ^^^ this sets cert_reqs = ssl.CERT_NONE
        # in mysql/connector/network.py:415
        # so no server cert verification is attempted
    
        'use_pure': True 
        # setting ssl_cipher may only work with the pure python driver
        # but this is the default anyway
    }
    
    dsn['user'] = raw_input('Enter Username: ')
    dsn['password'] = getpass.getpass('Enter password: ')
    
    try:
        dbconn = mysql.connector.connect(**dsn)
        # this will raise the 'Weak DH key' exception
    except mysql.connector.errors.InterfaceError as e:
        print e
    
    dsn['ssl_cipher'] = 'HIGH:!DH:!aNULL'
    
    # this is a standard openssl ciphersuite string
    # where !DH and !aNULL means don't use any DH ciphers or null ciphers
    # this option is officially undocumented
    
    try:
        dbconn = mysql.connector.connect(**dsn)
    except mysql.connector.errors.InterfaceError:
        raise
    else:
        assert isinstance(dbconn, mysql.connector.connection.MySQLConnection)
    

    香肠的制作方法

    在 mysql-connector-python 2.1.4 中,模块源代码中的以下行显示了它是如何工作的: mysql/connector/abstracts.py: Lines 298 - 313:

    for key, value in config.items():
        try:
           DEFAULT_CONFIGURATION[key]
        except KeyError:
            raise AttributeError("Unsupported argument '{0}'".format(key))
        # SSL Configuration
        if key.startswith('ssl_'):
            set_ssl_flag = True
            self._ssl.update({key.replace('ssl_', ''): value})
        else:
            attribute = '_' + key
            try:
                setattr(self, attribute, value.strip())
            except AttributeError:
                setattr(self, attribute, value)
    

    然后在mysql/connector/connection.py lines 130-134:

    if client_flags & ClientFlag.SSL and ssl_options:
        packet = self._protocol.make_auth_ssl(charset=charset,
                                              client_flags=client_flags)
        self._socket.send(packet)
        self._socket.switch_to_ssl(**ssl_options)
    

    _socket.switch_to_ssl() 位于mysql/connector/network.py lines 406-421

    def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None):
        """Switch the socket to use SSL"""
        if not self.sock:
            raise errors.InterfaceError(errno=2048)
    
        try:
            if verify_cert:
                cert_reqs = ssl.CERT_REQUIRED
            else:
                cert_reqs = ssl.CERT_NONE
    
            self.sock = ssl.wrap_socket(
                self.sock, keyfile=key, certfile=cert, ca_certs=ca,
                cert_reqs=cert_reqs, do_handshake_on_connect=False,
                ssl_version=ssl.PROTOCOL_TLSv1, ciphers=cipher)
            self.sock.do_handshake()
    

    【讨论】:

      【解决方案2】:

      这是一个安全问题,服务器端受到weak DH key 的影响。当前版本的 OpenSSL 强制使用最小长度的 DH 密钥来防止使用弱 DH 密钥的攻击。

      如果您的公司真的对安全感兴趣,而不是仅仅相信神奇地散布一些(不安全的)SSL 就可以做到这一点,那么他们应该在服务器端解决问题。要在客户端解决该问题,您的 Python 需要与尚未强制最小 DH 密钥长度的旧版本 OpenSSL 链接。

      【讨论】:

      • 啊,我明白了。所以这是因为我这边的 Python 的 OpenSSL 比服务器端的 OpenSSL 更安全。只是一个问题:如果我们在服务器端升级OpenSSL,是否需要更换证书和密钥?
      • @pepoluan:不,您可以保留现有的证书和密钥。
      • 是否有另一种方法可以在客户端上覆盖此行为? (通过在openssl中设置一个覆盖它的环境变量?)(我在问官方的python windows发行版)。我看到stackoverflow.com/questions/38015537/… 用于urllib,我想知道在官方的mysql python 连接器中是否有类似的hack 来支持它
      • @cowbert:鉴于documentation,我看不到限制客户端密码的方法。
      • @SteffenUllrich 我查看了来自 oracle 的 mysql-connector-python 2.1.4 的源代码,并基于 mysql/connector/abstracts.py 第 307 行和 mysql/connector/connection.py 行134 和 mysql/connector/network.py 第 406-420 行,您确实可以将 ssl_cipher 传递到连接字符串中。如果我将 ssl_cipher 设置为 'HIGH:!DH:!aNULL'
      【解决方案3】:

      无奈之下,我安装了模块MySQL-python,并更换了连接方式如下:

      import MySQLdb
      
      ssl = {
          'cert': '/path/to/user.crt',
          'key': '/path/to/user.key'
      }
      
      cnx = MySQLdb.connect(
          host='myserver.example.com', port=3306, user='myusername',
          ssl=ssl
      )
      

      ...你瞧!我对DH没有任何问题!好用!!!

      因此,如果有人遇到此问题,另一种解决方案是更改用于连接的模块。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-25
        • 2021-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多