前言
在写Java的时候发现的一个报错:java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
经过一番搜索,发现主要是与MySQL版本相关,由TLS连接引起。
处理方式
考虑到有些同学只想“知其然”,先把解决方式贴出来。
引起该报错首先确定MySQL服务器的版本多半大于MySQL 5.7.28
(当然不排除是这之前的版本但手动开启了TLS加密)。并且,多半是由于在连接时禁用了SSL。
考虑以下解决方式:
- 开启SSL连接。
- 添加连接参数
allowPublicKeyRetrieval=true
。 - 提高JDBC版本,与服务器版本匹配(玄学)。
JDBC连接参数:useSSL
JDBC的useSSL是一个连接选项,用于控制在JDBC连接过程中是否启用加密连接,虽然叫SSL,但其实是TLS了。
当useSSL=true
时,JDBC会使用SSL连接到MySQL服务器。在这种情况下,JDBC会在握手过程中验证MySQL服务器的证书,以确保与正确的服务器建立连接。同时,JDBC也会使用SSL协议对数据进行加密和解密,以保护数据传输的安全性。
当useSSL=false
时,JDBC不会使用SSL连接到MySQL服务器。在这种情况下,JDBC会以普通的方式连接到MySQL服务器,并使用明文传输数据。这种情况下的连接可能会存在安全隐患,因为数据在传输过程中可能会被拦截和窃听。
我们可以通过抓包来验证,首先是在开启SSL的情况下:
此时Username不可见,然后关闭SSL再抓:
此时数据以明文的方式传输,但密码仍然经过加密处理(RSA),这是由于MySQL的身份验证机制。
MySQL身份验证
在MySQL 8.0之前,当MySQL客户端连接到启用了SSL加密的MySQL服务器时,会自动从服务器端下载SSL证书的公钥,以便进行SSL握手协议,从而确保客户端与服务器之间通信的机密性和完整性。但是,这个过程可能存在潜在的安全风险,因为MySQL客户端在下载公钥的过程中,可能会遭受中间人攻击。
为了解决这个安全问题,MySQL 8.0引入了Public Key Retrieval特性,它允许MySQL服务器在SSL握手协议中,发送服务器端SSL证书的SHA-256指纹(SHA-256 Fingerprint)而不是公钥。MySQL客户端可以使用这个指纹,从一个可信任的位置(如本地文件系统、内存缓存或PKCS#11设备)获取服务器端SSL证书的公钥,从而避免了中间人攻击。
在 MySQL 8.0 版本中,身份验证插件是一个可插拔的模块,用于控制 MySQL 用户身份验证的方式。在以前的 MySQL 版本中,使用的身份验证插件是 “mysql_native_password”,而在 MySQL 8.0 中,则使用 “caching_sha2_password”,这是一个更强大和更安全的插件,它使用 SHA-256 哈希函数(好像也不一定,只是SHA2)来加密密码,并提供了密码过期和密码历史记录功能,以增强数据库的安全性。
在 MySQL 8.0 中,还提供了另外两个身份验证插件,分别是 “sha256_password” 和 “mysql_old_password”。其中 “sha256_password” 也使用 SHA-256 哈希函数进行密码加密,但它不支持密码过期和密码历史记录功能。而 “mysql_old_password” 是用于向前兼容以前版本的 MySQL,它使用旧的加密方法。
报错分析
对于使用 caching_sha2_password 插件的客户端,在连接到服务器时,密码永远不会以明文形式公开。密码传输的方式取决于是否使用安全连接或 RSA 加密:
如果连接是安全的,则不需要 RSA 密钥对,也不会使用它。这适用于使用 TLS 加密的 TCP 连接,以及 Unix 套接字文件和共享内存连接。密码以明文形式发送,但不能被窥探,因为连接是安全的。
如果连接不安全,则使用 RSA 密钥对。这适用于未使用 TLS 加密的 TCP 连接和命名管道连接。RSA 仅用于客户端和服务器之间的密码交换,以防止密码窥探。当服务器收到加密的密码时,它会对其进行解密。在加密中使用加扰来防止重复攻击。
由于笔者使用的MySQL为8.0版本,默认使用”caching_sha2_password”插件进行身份验证,当关闭SSL时,该插件发现连接未加密,因此要求使用 RSA 加密传输密码。但是服务端并没有将公钥发送给客户端,而客户端又无法在可信位置检索到公钥,所以无法加密密码,导致报错。
基于以上分析,可以提出两种解决方案:
- 改用加密连接。
- 在不用加密连接的同时,让客户端从服务器获取公钥。
实验
MySQL服务器版本:8.0.32,在每次成功连接后重启服务器,否则连接成功后会出现登录缓存,导致错误无法复现。
mysql-connector-java | useSSL | allowPublicKeyRetrieval | success |
---|---|---|---|
5.1.47 | false | false | |
5.1.47 | true | ||
5.1.47 | false | true | true |
8.0.23 | false | false | |
8.0.23 | true | ||
8.0.23 | false | true | true |
8.0.32 | false | false | |
8.0.32 | true | ||
8.0.32 | false | true | true |
根据实验结果,基本上可以验证之前的想法,且可以判断JDBC连接默认开启SSL并关闭allowPublicKeyRetrieval。
参考
[1] https://dev.mysql.com/doc/refman/8.0/en/caching-sha2-pluggable-authentication.html
[2] https://silencezheng.top/2022/05/26/article41/
[3] https://cloud.tencent.com/developer/news/791903
[4] https://dev.mysql.com/doc/refman/8.0/en/encrypted-connection-protocols-ciphers.html
后记
首发于 silencezheng.top,转载请注明出处。