错误信息
No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
也可能输出为
- javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
从 JDK 8 的各个更新版本到 JDK 25,措辞都一样。JVM 作为服务端时,如果自身的协议全部被禁用,同样的情况会显示为“No appropriate protocol”。
Java (JSSE) — the client itself has no enabled protocol/cipher combination left
含义
JVM 连 ClientHello 都没能构建出来:去掉 jdk.tls.disabledAlgorithms 中列出的所有协议和密码套件之后,应用请求的内容一项都不剩了。通常是代码或配置坚持要用 TLS 1.0、TLS 1.1 或 SSLv3,而当前的 JDK 已全部禁用它们。没有向服务器发送任何数据。
原因
应用强制使用旧协议
常见多年前为某台旧服务器写下的 SSLContext.getInstance("TLSv1")、setEnabledProtocols(new String[]{"TLSv1"}) 或 https.protocols=TLSv1。
JDK 更新禁用了应用所用的协议
常见2021年,Java 8、11 及更高版本的安全更新默认禁用了 TLS 1.0 和 1.1;只能使用这两个版本的应用,升级后就会出问题。
显式指定的密码套件列表里只有已禁用的套件
偶见只列出了少数几个 TLS_RSA_* 套件或 3DES 套件的配置,在这些套件被禁用后就没有可用的了。
修复方法
- 1.
找出在哪里请求了旧协议
在代码和 JVM 选项中搜索显式写出的协议名称;-XshowSettings:security:tls 会显示 JDK 启用了哪些内容。
bash grep -rnE 'SSLContext.getInstance\("(TLSv1|TLSv1\.1|SSL)"\)|setEnabledProtocols|https\.protocols' src/ java -XshowSettings:security:tls -version 2>&1 | sed -n '/Enabled Protocols/,/Enabled Cipher/p' - 2.
请求“TLS”,由 JSSE 自行协商
在当前的 JDK 上,SSLContext.getInstance("TLS") 会启用 TLS 1.3 和 1.2。
java // Let JSSE negotiate the best version (TLS 1.3 / 1.2) SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, null, null); - 3.
最后手段:为单个旧端点做小范围覆盖
如果某台设备无法升级,可以用单独的安全属性文件,为与它通信的那个 JVM 从 jdk.tls.disabledAlgorithms 中去掉 TLSv1/TLSv1.1。以 JDK 当前的完整取值为基础,只修改协议相关的条目,并把这个 JVM 隔离开。
bash # Last resort for ONE legacy endpoint: take this JDK's own value and drop only TLSv1/TLSv1.1 v=$(sed -n '/^jdk.tls.disabledAlgorithms=/,/[^\\]$/p' "$JAVA_HOME/conf/security/java.security" | sed 's/\\$//' | tr -d '\n') printf '%s\n' "$v" | sed -E 's/ TLSv1(\.1)?,//g' > legacy-tls.security java -Djava.security.properties=legacy-tls.security -jar legacy-client.jar