设置和验证 Python MySQL 连接中使用的 SSL/TLS 版本

Posted

技术标签:

【中文标题】设置和验证 Python MySQL 连接中使用的 SSL/TLS 版本【英文标题】:Set and verify SSL/TLS version used in Python MySQL connection 【发布时间】:2020-04-05 13:57:41 【问题描述】:

如何告诉 Python mysql 连接器使用哪个 SSL/TLS 协议?特定(例如 TLS1.2)或最低要求。

如何检查已建立的连接上使用的协议?

我有一个使用 mysql-connector-python (8.0.18) 的应用。我连接这样的东西:

cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz')

通常这不会给我带来麻烦,但最近在一个网络托管服务提供商的服务器上它停止了工作。我现在得到的错误是:

mysql.connector.errors.InterfaceError: 2026 (HY000): SSL connection error: error:1408F10B:SSL routines:ssl3_get_record:wrong version number

并且(通过 Flask-SQLAlchemy 设置连接):

_mysql_connector.MySQLInterfaceError: SSL connection error: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

我可以确认的是,如果我改为使用ssl_disabled=True,如下所示,它会正确连接(但我假设没有 SSL/TLS):

cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', ssl_disabled=True)

我无法更改提供商服务器,但他们说如果我指定要使用的特定版本,例如 TLS1.2,那么它应该可以正确连接。他们还提到使用ssl.OP_NO_SSLv3 flag,但这是 SSLContext 设置的一部分,我不确定如何将其应用于我的连接。

我看到在他们的 MySQL 实例(我无法编辑)上他们没有设置值:

SHOW VARIABLES LIKE 'tls_version' SHOW STATUS LIKE 'Ssl_cipher' SHOW STATUS LIKE 'Ssl_version'

【问题讨论】:

【参考方案1】:

我看了一下我们目前的配置:

测试环境:

mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 5.7.27                       |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| tls_version             | TLSv1,TLSv1.1                |
| version                 | 5.7.27                       |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | Linux                        |
+-------------------------+------------------------------+
8 rows in set (0.04 sec)

产品环境:

mysql> SHOW VARIABLES LIKE "%version%";

+----------------------------------+-----------------------+
| Variable_name                    | Value                 |
+----------------------------------+-----------------------+
| innodb_polar_restore_old_version | OFF                   |
| innodb_version                   | 8.0.13                |
| protocol_version                 | 10                    |
| rds_audit_log_version            | MYSQL_V1              |
| rds_version                      | 13                    |
| slave_type_conversions           |                       |
| tls_version                      | TLSv1,TLSv1.1,TLSv1.2 |
| version                          | 8.0.13                |
| version_comment                  | Source distribution   |
| version_compile_machine          | x86_64                |
| version_compile_os               | Linux                 |
| version_compile_zlib             | 1.2.11                |
+----------------------------------+-----------------------+
12 rows in set (0.98 sec)

之前使用的版本是8.0.16,发行版是正确的。自动更新到8.0.19后,测试环境开始出现上述错误,但生产环境并没有错。

所以我按照上面的操作添加了ssl_disabled = True

ssl_disabled=True

但我不知道它会如何影响那些事情。

我认为这是因为8.0.19版本默认使用TLSv1.2,所以导致我的测试环境出错。但是我没有找到相关文档

【讨论】:

【参考方案2】:

根据我的实验,导入的顺序是问题所在。 以下作品:

import mysql.connector
import ssl

以下不起作用:

import ssl
import mysql.connector

不管你是否设置ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2

如果您使用import requests 而不是import ssl,也会得到相同的结果。

【讨论】:

【参考方案3】:

根据我最近的理解和Andreas answer 的帮助,我最终得到了以下连接:

cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)

查看源代码,显然所有kwargs 提供的也在mysql.connector.constants.DEFAULT_CONFIGURATION 中被接受。这包括the documentation 中未包含的几个,例如ssl_versionssl_cipher。从kwargs 到连接的这种映射似乎发生在MySQLConnectionAbstract.connect 中。请注意,设置 ssl_version 可能还需要一些其他的 kwargs。我需要同时提供ssl_ca(可能会因您的 MySQL 实例在SHOW VARIABLES LIKE '%ssl%' 中的内容而异)。

考虑到这一点,我得到了以下 MVCE 代码:

import mysql.connector
import ssl

cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)
cursor = cnx.cursor()
cursor.execute("SELECT 1")

for (number,) in cursor:
    print('Number:', number)
print('SSL active:', cnx._ssl_active)
print('Connection SSL version:', cnx._ssl.get("version"))
print("Socket SSL version:", cnx._socket.sock.ssl_version)

cursor.close()
cnx.close()

对我来说输出:

Number: 1
SSL active: True
Connection SSL version: _SSLMethod.PROTOCOL_TLSv1_2
Socket SSL version: _SSLMethod.PROTOCOL_TLSv1_2

如果我在不指定 ssl_version 的情况下进行相同的连接,我会得到:

Number: 1
SSL active: True
Connection SSL version: None
Socket SSL version: _SSLMethod.PROTOCOL_TLS

如果您将ssl.PROTOCOL_TLSv1_2 替换为ssl.PROTOCOL_SSLv23,它至少应该尝试不同的协议,但如果出现问题(例如不支持)可能会失败。

【讨论】:

【参考方案4】:

在源代码中我看到了对ssl_options.get('version', None)的引用:https://github.com/mysql/mysql-connector-python/blob/b034f25ec8037f5d60015bf2ed4ee278ec12fd17/lib/mysql/connector/connection.py#L197

此变量在switch_to_ssl 中引用并传递给ssl.wrap_socket。这里的值可以是ssl模块中定义的任何PROTOCOL_*常量-https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLS

_do_auth方法是从_open_connection调用的,又是从connect方法调用的,ssl_options的值为self._ssl:https://github.com/mysql/mysql-connector-python/blob/b034f25ec8037f5d60015bf2ed4ee278ec12fd17/lib/mysql/connector/connection.py#L286-L288

似乎没有办法通过connect 函数控制connection._ssl,所以我们必须自己构造对象:

import ssl
try:
    from mysql.connector.connection_cext import CMySQLConnection as MySQLConnection
except ImportError:
    from mysql.connector.connection import MySQLConnection

connection = MySQLConnection()
connection._ssl['version'] = ssl.PROTOCOL_TLSv1_2
connection.connect(host='localhost', user='root', password='', database='example')

上面的代码是针对这样启动的 mysql Docker 容器进行测试的:

docker run --rm -it -e MYSQL_ALLOW_EMPTY_PASSWORD=true -e MYSQL_DATABASE=example -p 127.0.0.1:3306:3306 mysql

【讨论】:

感谢您的回答。通过这次挖掘,我找到了一种适合我的方法,因此我不会将其标记为已接受的答案,因为我最终使用了一种略有不同的方法,但它为我提供了宝贵的投入,值得赏金。【参考方案5】:

根据 MySQL 文档 here,现在 8.0.18 中有一个“tls-versions”选项,允许您指定 TLS 版本。

连接应该如下所示。

cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', tls-versions='tls1.2')

我尚未验证 tls-versions 的实际值,因此您可能需要尝试几个不同的值。

【讨论】:

mysql 文档不一定与 python 代码 1:1 匹配。 - 不是关键字参数的有效字符,您提供的 Python 语法无效。 这是正确的语法cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', tls_versions=['TLSv1.1', 'TLSv1.2'])

以上是关于设置和验证 Python MySQL 连接中使用的 SSL/TLS 版本的主要内容,如果未能解决你的问题,请参考以下文章

设置更改mysqlroot密码,连接mysql,mysql常用命令

python+appium从Mysql数据库中获取验证码

python+appium从Mysql数据库中获取验证码

[python] 连接MySQL,以及多线程多进程连接MySQL续

P8:python和MySQL在一起啦!

mysql 用户账号权限与密码问题