Python 连接到 MySQL 失败,出现错误"dh key too small"



我正在尝试连接到公司的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)

我试着搜索答案,但除了更改服务器端的设置外,似乎没有其他解决方案,这只是一个否定。

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


更新以获取更多信息:我正在Windows 10上创建程序,使用PyCharm 5.0.3

在撰写本文时,使用mysql连接器,Oracle的python 2.1.4纯python模块(您正在使用的是2.1.3版本的分支)支持连接字符串中未记录的连接配置kwarg ssl_cipher(因为它基本上将其传递给python的ssl模块)。因此,从密码列表中删除所有Diffie-Hellman密码,您就可以解决这个问题,这取决于您的mysql服务器是否支持非Diffie-Hell man密码。以下内容适用于官方Python 2.7.12 amd64 win32,针对MySQL 5.6.7-65.0-log。注意:这只适用于处理纯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()

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

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

出于绝望,我安装了模块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没有任何问题!!它有效!!!

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

最新更新