MySQL SSL加密连接配置实战:从自签名证书到CVE-2016-2183漏洞修复
2026/9/17 12:52:08 网站建设 项目流程

做数据库运维或者后端开发的人,多半都遇到过这种场景:MySQL部署在内网,跑得好好的,结果安全扫描报告一出来,赫然写着“SSL/TLS协议信息泄露漏洞”或者“MySQL连接未加密”。再一查,业务方、审计、甲方爸爸都盯着你,要求“数据库连接必须加密”。这时候才意识到,MySQL 默认情况下,客户端和服务端之间的数据传输是明文的,用户名、密码、业务数据,在网络上等于“裸奔”。

这篇文章我打算一次性把 MySQL 配置 SSL 加密连接这件事讲透。包括为什么 MySQL 默认不加密、自签名证书怎么生成、服务端如何启用 SSL、客户端(命令行、JDBC、Workbench)怎么配、以及我实际踩过的各种 SSL 报错和排查思路。内容会偏实操,命令和配置直接抄就行,但每个关键步骤我都会解释背后的原理,免得出了问题不知道怎么变通。

1. 为什么 MySQL 需要加密连接:先说清楚“裸奔”的风险

1.1 明文传输到底有多危险

很多刚接触 MySQL 的人有个误区:认为数据库跑在内网,外面访问不到,就是安全的。但实际上内网并不等于可信网络。你想想看,公司内部有没有可能在交换机上抓包的人?有没有可能存在被攻破的跳板机?还有云环境里,同物理机上的其他租户、虚拟网络里的异常流量嗅探,这些都是真实存在的威胁。

MySQL 客户端和服务器之间的协议,默认是不加密的。这意味着用户名、密码、SQL 语句、查询结果,全部以明文形式在网络中传输。只要有人在内网某个节点上抓包,用 Wireshark 或者 tcpdump 就能直接把你的数据库账号密码和人聊天的内容一样看个精光。更要命的是,很多应用配置里数据库密码是硬编码的,泄露一次等于核心资产全部暴露。

1.2 合规和安全扫描的硬性要求

这几年不管是等保还是各类行业安全规范,都对数据传输加密提出了明确要求。安全扫描工具(比如 Nessus、OpenVAS 这类)扫到 3306 端口,如果发现 MySQL 允许明文连接,一般就会报告“MySQL 服务未使用 SSL/TLS 加密”之类的漏洞。你在热搜里看到的ssl/tls协议信息泄露漏洞(cve-2016-2183),其实也是同一类 TLS 安全配置问题的典型代表。

这类问题不去解决,漏洞报告就一直挂着。要真正合规,必须做到两条:一是服务端启用 SSL 能力,二是强制客户端使用加密连接。只生成证书但不强制,等于门装了锁却常年不锁,没有实际意义。

1.3 加密连接的性能代价能不能接受

说不担心性能是假的。SSL 握手环节有非对称加密,数据传输阶段有对称加密,确实会比明文多消耗一些 CPU。但现代服务器的 CPU 基本都有 AES 指令集加速,实测下来,在普通业务压力下,SSL 连接对 QPS 的影响一般在 5% 到 15% 之间,完全在可接受范围内。如果你的业务对性能极度敏感,而且网络环境完全可信(比如本机回环、独立内网物理隔离),那可以不做全局强制,至少保留一种加密连接的能力,让敏感业务显式启用。

2. 生成 SSL 证书:自签名方案与核心参数解析

2.1 证书体系快速扫盲

先别急着敲命令,搞清楚 MySQL SSL 依赖的证书体系很重要。SSL 加密连接通常需要一个 CA 证书(用于签发和验证)、一个服务器证书(配置在 MySQL 服务端),以及对应的私钥。客户端连接时,会拿服务端发来的证书,用自己信任的 CA 去验证真伪。

生产环境里正规的做法是找企业级 CA(比如阿里云 SSL 证书、内部 PKI 系统)签发。但很多内部系统没这个条件,最常用的方案就是用 OpenSSL 自建 CA,然后签发服务器证书。MySQL 其实也内置了一个工具mysql_ssl_rsa_setup,一条命令就能把整套证书生成出来,适合快速测试;但生产环境我更推荐手动用 OpenSSL,因为可控性更强,能指定有效期、密钥长度等关键参数。

2.2 用 OpenSSL 自建 CA 并签发服务器证书

整个过程可以拆成四步:生成 CA 私钥、生成 CA 证书、生成服务器私钥和证书请求、用 CA 签发服务器证书。下面是我常用的命令序列,直接用即可。

# 1. 创建目录,统一存放证书文件,后续路径好管理 mkdir -p /data/mysql-ssl cd /data/mysql-ssl # 2. 生成 CA 私钥,aes256 加密,密钥长度 2048 起步(生产建议 4096) openssl genrsa -aes256 -out ca-key.pem 4096 # 3. 生成 CA 自签名证书,有效期 10 年,注意 CN 是标识,可按公司域名来 openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca.pem \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyMySQL-CA" # 4. 生成服务器私钥,这里用 -nodes 表示不加密私钥,否则 MySQL 启动时需要输入密码,维护麻烦 openssl genrsa -out server-key.pem 2048 # 5. 生成服务器证书签名请求 openssl req -new -key server-key.pem -out server-req.pem \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=mysql-server" # 6. 用 CA 签发服务器证书,这里写入了 SAN 扩展,支持 IP 和域名访问,很重要 openssl x509 -req -in server-req.pem -days 3650 \ -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem \ -extfile <(printf "subjectAltName=IP:127.0.0.1,DNS:localhost,DNS:mysql-server")

有几个坑我实际中踩过,必须提醒一下:

  • 私钥文件权限必须严格控制。MySQL 会检查私钥文件权限,太宽松直接启动失败。建议chmod 600 server-key.pemchmod 644 ca.pem server-cert.pem
  • 第 6 步的-extfile参数,如果不加,生成的证书没有 SAN 扩展。客户端如果用--ssl-mode=VERIFY_IDENTITY验证,就会因为证书里的主机名和连接地址不匹配而报错。
  • 密钥长度 2048 是底线,如果扫描工具对你的 TLS 强度有要求,直接上 4096。

2.3 使用 mysql_ssl_rsa_setup 快速生成方案

如果你只是想先快速把这个能力跑起来,MySQL 官方提供的工具是最省事的。MySQL 安装后一般自带:

mysql_ssl_rsa_setup --datadir=/data/mysql-ssl

它会自动生成ca.pemserver-cert.pemserver-key.pem等整套文件。但要注意,这个工具生成的证书默认有效期是 10 年,密钥是 2048 位,证书的 CN 是MySQL_Server_xxx_Auto_Generated_Server_Certificate,带有自动生成标识。公网环境不建议用这种方式,内部测试完全没问题。

3. MySQL 服务端配置:启用 SSL 并强制加密

3.1 配置 my.cnf 并验证 SSL 状态

证书准备好后,把文件放到 MySQL 能读到的地方,然后在my.cnf[mysqld]段配置下面几行:

[mysqld] ssl-ca=/data/mysql-ssl/ca.pem ssl-cert=/data/mysql-ssl/server-cert.pem ssl-key=/data/mysql-ssl/server-key.pem

如果你的 MySQL 版本是 5.7 及以上,其实只要设置了任意一个 ssl 参数,MySQL 就会自动启用 SSL 支持。但只启用还不够,还需要考虑是否强制所有连接都必须走加密。MySQL 8.0.16 之后(5.7.5 实验性质),提供了require_secure_transport这个开关,设置为 ON 之后,所有非加密连接都会被拒绝。

[mysqld] require_secure_transport = ON

改完配置文件后重启 MySQL,然后登录进去检查状态:

SHOW VARIABLES LIKE '%ssl%'; SHOW STATUS LIKE 'Ssl%';

看到have_sslYES,且Ssl_cipher字段不为空(表示当前连接确实用了加密),说明服务端 SSL 已经生效。如果你是登录后执行第二条语句,Ssl_cipher会显示当前会话的加密套件,非空就是好的。

3.2 按用户级别控制加密:需要灵活的另一种方式

全局强制在某些场景下太粗暴了。比如你有一个数据仓库报表任务,用的是很老版本的客户端,不支持 SSL,这时候如果全局强制,会导致整个业务不可用。更合适的做法是:全局启用 SSL 能力,但不对所有用户强制,只对敏感账号做精细管控。

在 MySQL 里,可以对单个用户设置 SSL 要求:

-- 强制该用户使用 SSL 连接 ALTER USER 'app_user'@'%' REQUIRE SSL; -- 更严格:要求用户必须提供合法客户端证书(双向认证) ALTER USER 'app_user'@'%' REQUIRE X509; -- 取消限制 ALTER USER 'app_user'@'%' REQUIRE NONE;

REQUIRE SSL是要求使用加密连接即可,客户端不需要提供证书。REQUIRE X509则要求客户端必须持有由受信任 CA 签发的客户端证书,实现双向认证,安全性更高,但客户端配置也相对复杂。业务上如果只是防嗅探,REQUIRE SSL足够。

3.3 禁用老版本 TLS,规避 CVE-2016-2183

热搜词里频繁出现的ssl/tls协议信息泄露漏洞(cve-2016-2183)【原理扫描】,很多管理员看了就慌。其实这个漏洞本身是针对 TLS 协议中的三重 DES 加密套件(3DES)的,扫描器认为服务器允许使用弱加密算法,存在信息泄露风险。MySQL 这边对应的处理方式,就是显式限制 TLS 版本,把老旧的 TLSv1、TLSv1.1 禁掉。

在 MySQL 8.0 中,配置方式如下:

[mysqld] tls_version = TLSv1.2,TLSv1.3

MySQL 8.0 默认就只支持 TLSv1.2 和 TLSv1.3,如果你用的是 MySQL 5.7,默认支持列表会包含 TLSv1、TLSv1.1,需要显式配置成上面的值。配置之后再用扫描器扫,这个漏洞基本就能消掉了。

4. 客户端连接配置:命令行、JDBC、Workbench 全覆盖

4.1 命令行客户端:mysql 命令连接测试

我先说命令行,因为这是最直观的验证方式。MySQL 客户端的 SSL 连接参数在不同版本里有差异,5.7 及之前常用--ssl-ca--ssl-mode是在 5.7.11 之后的版本引入的,8.0 全系列可用。

# 方式一:最简方式,要求加密但不验证服务端证书(适合自签名证书场景) mysql -h 127.0.0.1 -u app_user -p --ssl-mode=REQUIRED # 方式二:要求加密并验证服务端证书,需要指定 CA 文件 mysql -h 127.0.0.1 -u app_user -p \ --ssl-mode=VERIFY_CA --ssl-ca=/data/mysql-ssl/ca.pem # 方式三:最严格,验证 CA 还要验证证书中的主机名与当前连接主机一致 mysql -h mysql-server -u app_user -p \ --ssl-mode=VERIFY_IDENTITY --ssl-ca=/data/mysql-ssl/ca.pem

连接成功后,执行这条 SQL 检查当前会话是否真的走加密:

SHOW STATUS LIKE 'Ssl_cipher';

如果返回类似TLS_AES_256_GCM_SHA384这样的加密套件名称,说明当前的连接是加密的。如果返回空字符串,说明虽然服务端支持 SSL,但当前连接没走加密通道。

很多人在用--ssl-mode=VERIFY_CA连接自签名证书时,遇到报错SSL certificate verification failed就直接放弃验证了。这里我要多说一句:如果是自建 CA 签发的证书,把--ssl-ca指向你的ca.pem就能解决。如果连了 CA 还报错,大概率是时间不同步或者证书格式不对,逐项排查,别图省事直接跳过验证。

4.2 JDBC 连接串:Java 应用接入

Java 应用是生产环境里最常见的 MySQL 客户端。JDBC 连接串里关于 SSL 的参数稍微有点绕,而且不同版本的 MySQL Connector/J 默认行为不一样,这里我给一个 8.0 版本的标准配置:

jdbc:mysql://mysql-server:3306/app_db?useSSL=true&requireSSL=true&verifyServerCertificate=true&trustCertificateKeyStoreUrl=file:/path/to/truststore.jks&trustCertificateKeyStorePassword=yourpassword

useSSL=true表示尝试建立 SSL 连接。requireSSL=true表示如果服务端不支持 SSL,直接报错而不是降级为明文。verifyServerCertificate=true表示校验服务端证书。最后两个参数是告诉 JVM 信任哪个 CA,Java 环境下需要把ca.pem导入到 JKS 或 PKCS12 的 truststore 里,不能直接引用 pem 文件。

信任库导入命令:

keytool -import -file /data/mysql-ssl/ca.pem -alias mysql-ca -keystore truststore.jks -storepass changeit

如果你的 Java 应用在云上跑,不方便搞 truststore,也有变通方案:verifyServerCertificate=false配合requireSSL=true,这样连接强制加密但不验证证书。安全性比完整验证弱一点,但至少防住了明文嗅探,适合内部快速改造。

4.3 MySQL Workbench / Navicat 等图形化工具

图形化工具相对简单,但有个隐藏坑。MySQL Workbench 在 “Connection” 配置界面,点击 “SSL” 标签页,选择 “Require SSL” 或者 “Verify CA” 即可。改成 “Verify CA” 后,需要把ca.pem文件路径填到 “CA File” 那一栏。Navicat 则是在连接属性的 “SSL” 选项卡勾选 “使用 SSL” 即可。

但要注意:图形工具连接成功不代表所有客户端都已经走加密了。真正要强制,还是取决于服务端那头的限制策略。如果你的 MySQL 同时被命令行、Java 程序、DBA 的 Workbench 访问,全局强制之前,务必先逐个验证每类客户端都能连上,否则一个不留神,DBA 自己先被锁在外面了。

4.4 Python / Go 等其他语言示例

Python 用 PyMySQL 连接 SSL 也很常见,配置方式是在连接时指定 ssl 字典:

import pymysql conn = pymysql.connect( host="mysql-server", user="app_user", password="yourpassword", database="app_db", ssl={"ca": "/data/mysql-ssl/ca.pem", "check_hostname": False} )

Go 语言用go-sql-driver/mysql时,DSN 里加上 tls 参数:

db, err := sql.Open("mysql", "user:password@tcp(mysql-server:3306)/dbname?tls=preferred")

tls=preferred表示优先使用加密,服务端不支持时降级明文。强制加密用tls=skip-verify或者tls=true,区别在于skip-verify跳过服务端证书验证,true会用系统根证书验证。

5. 常见报错与故障排查:SSL 连接问题从入门到放弃,再到解决

5.1 MySQL 8.0 默认开启 caching_sha2_password 与 SSL 的关系

先讲一个和 SSL 强相关、但非常多人都踩过的坑。MySQL 8.0 把默认认证插件从mysql_native_password换成了caching_sha2_password。这个插件在非 SSL 连接下,首次认证时为了保证密码传输安全,会要求客户端先走 RSA 公钥加密交换。如果你的客户端版本太老,不支持这个流程,就会报错Authentication plugin 'caching_sha2_password' cannot be loaded

解决方案有几个思路:一是升级客户端驱动,让客户端支持新的认证插件,这是治本的办法;二是服务端显式把用户改回旧认证方式,兼容老客户端:

ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'yourpassword';

但在启用 SSL 之后,caching_sha2_password是可以直接正常工作的,因为加密通道已经保证了密码传输安全。所以你会发现,配好 SSL 后这类认证报错会少很多。

5.2 [08001] SSL connection required, but not provided by server

这个报错经常出现在新版 JDBC 驱动连接旧版本 MySQL 的场景。客户端要求建立 SSL 连接,但服务端压根没有配置 SSL 能力,于是连接失败。排查思路:

  • 服务端执行SHOW VARIABLES LIKE 'have_ssl',如果是DISABLED,说明 SSL 没启用,需要加证书配置并重启。
  • 如果have_sslYES,再检查你登录用的用户是不是被REQUIRE SSL限制了,以及客户端的连接参数是否真的带了 SSL 配置。

5.3 ERROR 2026 (HY000): SSL connection error: protocol version mismatch 和 certificate verify failed

ERROR 2026 (HY000): SSL connection error是个大杂烩,后面的细节决定具体原因。常见的有两种:

一种是协议版本不匹配。比如老客户端不支持 TLSv1.2,而服务端只开了 TLSv1.2 和 TLSv1.3,两边谈不拢。排查时先确认客户端 MySQL 版本,太老的客户端建议升级;或者临时在服务端tls_version里把老版本加上应急,但稳定后还是应该推动客户端升级。

另一种是证书校验失败。执行命令行时报SSL certificate verification failed,原因很可能就是我前面提到的 SAN 不匹配或者 CA 没对上。解决方式:

# 先强制加密但不验证,确认是不是验证环节出问题 mysql -h 127.0.0.1 -u app_user -p --ssl-mode=REQUIRED # 如果上面能连上,说明服务端 SSL 本身没问题,问题出在证书验证 # 检查 --ssl-ca 的路径是否正确,证书文件是否可读 ls -l /data/mysql-ssl/ca.pem

5.4 安全扫描报 CVE-2016-2183 的处理

这个漏洞报的是 TLS 1.0/1.1 或者弱加密套件的问题。MySQL 5.7 较高版本和 MySQL 8.0 其实已经默认禁用弱套件,但扫描器可能仍然报警。检查服务端:

SHOW VARIABLES LIKE 'tls_version';

如果列表里包含 TLSv1 或者 TLSv1.1,按前面说的改掉:

[mysqld] tls_version = TLSv1.2,TLSv1.3

改完重启后,再用openssl s_client检查服务端的 TLS 能力:

openssl s_client -connect 127.0.0.1:3306 -tls1_2 openssl s_client -connect 127.0.0.1:3306 -tls1_1

第二条如果报错wrong version number或者握手失败,说明 TLSv1.1 确实已经禁用,扫描问题就能消掉。

5.5 Workbench 连接报错:驱动无法通过 SSL 与 SQL Server 建立安全连接

有时代理、堡垒机场景下,MySQL Workbench 或某些客户端会爆出“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接”之类的报错。这类报错乱入 SQL Server 的措辞,其实底层就是 SSL 握手失败。排查顺序建议如下:

  • 检查端口是否真的通,telnet 127.0.0.1 3306测试一下。
  • 看是不是走了代理或跳板机导致 SNI/证书验证错乱。
  • 用命令行客户端在同样的机器上连接,确认服务端 SSL 配置本身是正常的。
  • 最后再回来看图形工具的 SSL 选项,优先选 “Ignore server certificate” 或 “Require SSL”。

6. 实操总结:一次完整的 MySQL SSL 配置过程记录

最后分享一个我最近帮客户做的完整配置过程,按时间线和操作顺序记录,你照着走一遍基本就能掌握全流程。

环境信息:CentOS 7 + MySQL 8.0.32,数据目录/data/mysql,证书目录统一放在/data/mysql-ssl

第一步,确认 MySQL 版本和当前的 SSL 状态:

mysql -uroot -p -e "SHOW VARIABLES LIKE 'version'; SHOW VARIABLES LIKE 'have_ssl';"

第二步,生成证书。我决定用 OpenSSL 走一遍完整流程,因为客户有等保需求,需要存档证书信息。从 2.2 节的命令复制过来,改掉公司和主机的 CN,然后设置好权限:

chmod 600 /data/mysql-ssl/server-key.pem chmod 644 /data/mysql-ssl/ca.pem /data/mysql-ssl/server-cert.pem

第三步,修改 my.cnf:

[mysqld] ssl-ca=/data/mysql-ssl/ca.pem ssl-cert=/data/mysql-ssl/server-cert.pem ssl-key=/data/mysql-ssl/server-key.pem tls_version=TLSv1.2,TLSv1.3 require_secure_transport = ON

这里我直接开了require_secure_transport,因为客户明确要求全局强制加密,并且我已经确认了所有连接 MySQL 的应用都支持 SSL 连接。

第四步,重启验证:

systemctl restart mysqld mysql -uroot -p -e "SHOW VARIABLES LIKE '%ssl%'; SHOW STATUS LIKE 'Ssl%';"

实际输出里have_sslYES,随便开个会话SHOW STATUS LIKE 'Ssl_cipher'能看到加密套件,比如TLS_AES_256_GCM_SHA384,全局强制加密生效。

第五步,应用侧改造。客户的应用是 Java Spring Boot,改 JDBC 连接串:

spring.datasource.url=jdbc:mysql://mysql-server:3306/app_db?useSSL=true&requireSSL=true&verifyServerCertificate=true&trustCertificateKeyStoreUrl=file:/data/ssl/truststore.jks&trustCertificateKeyStorePassword=changeit

同时将ca.pem导入 truststore,重启应用,日志无报错。为了确认应用连接确实走加密,我在 MySQL 里查了一下:

SELECT user, host, ssl_type, ssl_cipher FROM performance_schema.session_status WHERE variable_name='Ssl_cipher';

或者简单点:

SHOW PROCESSLIST;

Info列旁边没有直接显示加密状态,但performance_schema里的ssl_cipher可以查。确认几行都有加密套件值后,说明业务连接全部走 SSL 了。

第六步,安全扫描复查。扫描器再跑一轮,CVE-2016-2183 消失,SSL 相关的未加密连接报告也消除了。

整个过程中遇到的一个小插曲:有个老旧的报表任务用的 Python 脚本,用的 PyMySQL 版本比较老,连接时没带 ssl 配置,全局强开 SSL 后直接连不上。后来在连接参数里补上ssl={"ca": "/data/mysql-ssl/ca.pem"}就恢复了。这也说明,全局强制之前,最好先在测试环境完整验证所有业务连接。像这种老脚本,如果不能在测试环境发现,线上出问题就是事故。

7. 写在最后的经验与建议

配置 MySQL SSL 不是一句 “装上证书打开开关” 就完事的事情。从证书体系到客户端兼容性,从全局策略到用户粒度控制,每一步都需要和实际业务形态对齐。我个人在做这类改造时,习惯按下面的顺序思考和落地:

先搞清楚谁在连数据库、通过什么方式连、用的什么版本。把所有连接类型列一个清单,命令行、JDBC、Python、BI 工具、监控系统,一个都不能漏。然后生成证书,先在测试环境全局强制,逐项验证所有连接,最后再上生产。这个顺序看着麻烦,但能帮你避免最尴尬的场景——生产数据库强制加密后,监控系统连不上了,报警器响了一宿。

另外,证书有效期这个事一定要在日历上记好。自签名证书一般设 10 年,看着很久,但时间一晃就过期。MySQL 8.0 的 SSL 证书过期后,新连接会握手失败,但服务不会自动重启,很容易出现某个早上突然大面积连接报错的情况。建议证书到期前一个月就在运维日历上设提醒,预留足够时间替换。

最后再分享一个小技巧:如果你有多个 MySQL 实例,尽量用同一套 CA 来签发所有实例的服务器证书,客户端只需要信任一个 CA 文件,维护成本直线下降。如果每个实例各搞一套 CA,光分发和管理 truststore 就够你喝一壶的。这个经验是我在一次多机房数据库大版本升级时得到的教训,希望你能少走这段弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询