pepoluan
pepoluan

Reputation: 6780

Python connect to MySQL failed with error "dh key too small"

I'm trying to connect to the company's MySQL database. The policy was that I am to connect only using SSL.

I am provided the username, CA certificate, certificate, and private key.

If I use HeidiSQL, I can connect without problem.

However, I cannot connect using Python 2.7.11 with the mysql-connector-python-rf (v2.1.3).

Here's my simple connection tester program:

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()

I always end up with this exception:

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)

I've tried searching for answers, but there doesn't seem to be a solution except changing the settings on the server side, which is simply a no-no.

How can I fix and/or work around this issue?


Update for More Info: I'm creating my program on Windows 10, using PyCharm 5.0.3

Upvotes: 3

Views: 5934

Answers (3)

cowbert
cowbert

Reputation: 3442

As of this writing, with mysql-connector-python 2.1.4 pure python module from Oracle (the one you are using is a fork of version 2.1.3) supports the undocumented connection configuration kwarg ssl_cipher in your connection string (since it basically passes it to python's ssl module). So strip all Diffie-Hellman ciphers from the cipher list and you may be able to work around this problem, depending on whether your mysql server supports non-Diffie-Hellman ciphers. The following works on official Python 2.7.12 amd64 win32 against MySQL 5.6.17-65.0-rel65.0-log . Note: This only works when dealing with the pure python version of the module!. If you use the compiled C extension module, it may not accept 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)

How the sausage is made

In mysql-connector-python 2.1.4, the following lines in the module's source show how this works: 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)

Then in 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() is found in 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()

Upvotes: 4

pepoluan
pepoluan

Reputation: 6780

Out of desperation, I installed the module MySQL-python and replaced the connection method as follows:

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
)

... lo and behold! I have no problems with DH whatsoever!! It works!!!

So one alternative solution if someone happens upon this problem, is to change the module used to connect.

Upvotes: 0

Steffen Ullrich
Steffen Ullrich

Reputation: 123270

This is a security issue and the server side is affected by a weak DH key. Current versions of OpenSSL enforce a minimal length of the DH key to protect against attacks using weak DH keys.

If your company is really interested in security and not just believe that magically sprinkling some (insecure) SSL around will do it, then they should fix the problem on the server side. To work around the problem on the client side your Python would need to be linked against an older version of OpenSSL which does not yet enforce a minimal DH key length.

Upvotes: 3

Related Questions