之前写过一篇《HTTPS 发送请求出现TLS握手失败》,后续来了;
之前发现https进行握手缺少椭圆曲线,是因为手动删除provider导致的:
static {
Security.removeProvider("SunEC");
}
深入分析之后发现,虽然这里把椭圆曲线删除了,但是其实别的工具类会把
BouncyCastleProvider给加上,代码如下:
static {
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
}
这是一个叫CommonUtil的工具类,在工具类被加载的时候会执行static里面的代码;分析后发现,在请求进来之后,切面会拦截请求,切面里面有个参数校验的方法会调用CommonUtil里面的方法,因此这段静态代码是会被执行的,加断点调试,确实是可以执行的;
通义千问对这个BouncyCastle的解释如下:
Bouncy Castle 是一个用 Java 和 C# 编写的加密库,它提供了广泛的算法实现和协议支持。对于 Java 开发者来说,Bouncy Castle 提供了一个名为 `bcprov-jdk15on`(或针对不同 JDK 版本的其他变体)的 Java 密码工具包,可以用来增强标准 Java 安全性框架的功能。
在 Bouncy Castle 中,你可以找到许多标准和非标准的加密算法,包括但不限于:
- 对称加密算法,如 AES, DES, 3DES, Blowfish 等。
- 非对称加密算法,如 RSA, DSA, ECDSA, ElGamal 等。
- 消息摘要算法,如 SHA-1, SHA-256, SHA-512, MD5 等。
- 密钥协商和密钥交换协议,如 DH (Diffie-Hellman) 和 ECDH (Elliptic Curve Diffie-Hellman)。
- 数字签名算法,如 PSS (Probabilistic Signature Scheme) 和 PKCS#1 v1.5。
- 随机数生成器和安全随机数生成器。
- 加密模式,如 CBC (Cipher Block Chaining),CFB (Cipher Feedback),CTR (Counter Mode) 等。
- MACs (Message Authentication Codes),如 CMAC, HMAC 等。要使用 Bouncy Castle 的 Java 密码包,你需要将其添加到你的项目依赖中。如果你使用 Maven 或 Gradle 构建系统,可以在你的 `pom.xml` 或 `build.gradle` 文件中添加相应的依赖项。例如,在 Maven 中,你可能需要添加以下依赖:
```xml
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>版本号</version>
</dependency>
```将 `版本号` 替换为最新或所需的 Bouncy Castle 版本。
一旦你添加了依赖项,你就可以在你的代码中使用 Bouncy Castle 提供的类和方法来执行各种加密操作。例如,创建一个 RSA 密钥对、加密数据或验证数字签名等。务必确保你的应用程序正确地初始化了 Bouncy Castle 安全提供程序,以便使用其功能。这通常通过调用 `Security.addProvider(new BouncyCastleProvider());` 来完成。
由此可见,这个包是包含常用的一些加密方法的,同时也包含国密的加密套件,问过写这个代码的同事,他说当时是因为有些项目需要使用国密的签名方法,所以使用这个包的Provider替换了原有的SunEC Provider,避免了名称的冲突;
问题就来了,既然已经加入了可以替换原有的加密套件的新的更强大的套件,那为啥还没握手失败呢?
后面加断点调试,以及分析SSL Handshake报文,发现,在执行外部接口的post请求之前,会有一次handshake的过程, 报文如下:
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "06 A8 9E AD 6D 16 42 E1 69 0B F7 A7 24 00 F0 BE 29 CC 12 1D 2C DA 3A 5C A8 EE FA 13 BB 05 F6 CF",
"session id" : "3A 6B 87 F9 FC 27 80 F8 EE B5 9A AB 5F C7 23 16 B8 D1 56 CB F0 DB 59 4C 47 F0 F0 A7 78 B4 6C FF",
"cipher suites" : "[TLS_AES_256_GCM_SHA384(0x1302), TLS_AES_128_GCM_SHA256(0x1301), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(0x00A3), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(0x00A2), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(0x006A), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040), TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(0xC02E), TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(0xC032), TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(0xC02D), TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(0xC031), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(0xC026), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(0xC02A), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(0xC025), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(0xC029), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(0xC005), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(0xC00F), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(0xC004), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F)]",
"compression methods" : "00",
"extensions" : [
"supported_groups (10)": {
"versions": [secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
},
"ec_point_formats (11)": {
"formats": [uncompressed]
},
"signature_algorithms (13)": {
"signature schemes": [ed25519, ed448, ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
},
"signature_algorithms_cert (50)": {
"signature schemes": [ed25519, ed448, ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
},
"extended_master_secret (23)": {
<empty>
},
"supported_versions (43)": {
"versions": [TLSv1.3, TLSv1.2]
},
"psk_key_exchange_modes (45)": {
"ke_modes": [psk_dhe_ke]
},
"key_share (51)": {
"client_shares": [
{
"named group": secp256r1
"key_exchange": {
0000: 04 D6 83 24 55 B1 EC F4 50 9D E6 FD 57 14 EC 68 ...$U...P...W..h
0010: 5D 7C 40 C8 4F B0 47 9D 3C B9 0F 5C 49 48 52 CF ].@.O.G.<..\IHR.
0020: 81 ED 8B FF 57 BF CD 6D E9 05 61 0C 95 DD 62 66 ....W..m..a...bf
0030: 78 9D 8E E5 7A 28 8F B9 FA A3 C3 90 EC B8 7F 65 x...z(.........e
0040: 14
}
},
]
},
"renegotiation_info (65,281)": {
"renegotiated connection": [<no renegotiated connection>]
}
]
}
)
javax.net.ssl|FINE|01 04|Druid-ConnectionPool-Create-491712|2024-07-09 10:30:10.007 CST|ServerHello.java:863|Consuming ServerHello handshake message (
"ServerHello": {
"server version" : "TLSv1.2",
"random" : "BF BB 99 F7 7A 01 93 1D 44 7D 09 E6 9B 2E 61 E9 C1 2C F0 7B 74 D2 A9 E5 1E 3B 35 61 47 BD D1 4A",
"session id" : "36 9D 0D 19 32 B6 2B AC 3B E7 55 12 B9 50 82 36 F8 A8 3B C7 13 73 71 C5 FD 0C 8F 51 A8 1C 2A 2C",
"cipher suite" : "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030)",
"compression methods" : "00",
"extensions" : [
"renegotiation_info (65,281)": {
"renegotiated connection": [<no renegotiated connection>]
},
"ec_point_formats (11)": {
"formats": [uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2]
},
"extended_master_secret (23)": {
<empty>
}
]
}
)
为什么会这样呢?同事的解释是这样的:
那次handshake就已经让sslcontext完成了初始化,而为什么随着spring bean生命周期的可以,是因为在接受任何请求前,也就是没有将sslcontext初始化,所以加了provider能够生效,因为之前有bean已经删了sunEC的provider这时已经没有支持快签接口的套件了,由于commonutil初始化前已经进行了handshake并完成了sslcontext的初始化
先看一下sslcontext的源码:
public class SSLContext {
private final Provider provider;
private final SSLContextSpi contextSpi;
private final String protocol;
/**
* Creates an SSLContext object.
*
* @param contextSpi the delegate
* @param provider the provider
* @param protocol the protocol
*/
protected SSLContext(SSLContextSpi contextSpi, Provider provider,
String protocol) {
this.contextSpi = contextSpi;
this.provider = provider;
this.protocol = protocol;
}
private static SSLContext defaultContext;
也就是说sslcontext类里面有个静态的SSLContext:
private static SSLContext defaultContext;
SSLContext sslcontext =ssLcontext.getDefault();
httpclient密码套件通过这个获取的:
这个里面会初始化provider,也就是说,第一个handshake请求触发的时候,就会触发getInstance方法,从而实例化provider,这样在handshake完成之后再执行下面的语句是没有用的:
static {
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
}
已经实例化了的provider是没有更新的,它不会把新的provider加到已经实例化的静态的instance下的provider成员变量里面;除非调用httpclient那段代码使用新的sslconext;
所以,正确的解决方式是,在被spring容器管理的某个bean中执行:
Security.addProvider(new BouncyCastleProvider());这样在需要使用加密套件的地方就会有provider;
所以,我这里正确的解决方式是,在之前只是删了provider的类的静态代码块里加语句:
原代码:
static {
Security.removeProvider("SunEC");
}
修改后的代码:
static {
Security.removeProvider("SunEC");
Security.addProvider(new BouncyCastleProvider());
}
这样可以解决问题,同时也不会对原有的代码造成影响!到这里,这个问题暂告一段落了,但是实际上这个问题还没有彻底解决;为啥子呢?因为对方服务的运维说他们的服务器最近没有修改过,TLSv1.2以上的版本要求是从2年前就这样的了,而我们这边的代码最近也没有改动到这一块,那这个问题到底是因为什么而触发的,不得而知,是个谜!同事分析说可能是对方服务器最近更新了密码套件之类引起的,因为以前也出现过这样的情况,但是我没遇到过,我只能问一下他们确认一下了, 但是他们说没改动过的,这就无解了,没办法,暂时先这样吧,问题是解决了,就是不知道引发问题的原因。
解释得有点乱:(