如何修复 (SSLException) 收到致命警报:protocol_version in Java
How to fix (SSLException) Received fatal alert: protocol_version in Java
我正在尝试对网站 https://www.facebookbrand.com/ 执行 get 调用,但在这样做时我收到了错误 (SSLException) Received fatal alert: protocol_version。有问题的网站正在使用 TLSv1.2,如果我设置我的 ConnectionFactory 以专门支持此协议,它可以正常工作,但我需要它适用于每个网站,并且根据我的理解,将其硬编码为 TLSv1.2 会使它无法工作对于其他不使用 TLSv1.2 的 https 网站。我还尝试包含一个协议列表("TLSv1.2"、"TLSv1.1"、"TLSv1"、"SSLv3"、"SSLv2Hello"),但随后我收到错误 Unrecognized SSL message, plaintext联系。我不关心它有多安全,我只需要能够基本上连接到每个网站,并试图找到最好的方法来做到这一点。下面我包含了我的代码。
这是我设置 httpClient 等的代码:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
PlainConnectionSocketFactory pcsf = new PlainConnectionSocketFactory();
Registry socketFactoryRegistry = RegistryBuilder.create().register("https", sslsf).register("http", pcsf).build();
PoolingHttpClientConnectionManager multiConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
multiConnectionManager.setMaxTotal(100);
multiConnectionManager.setDefaultMaxPerRoute(10);
httpClientBuilder.setConnectionManager(multiConnectionManager);
this.httpClient = httpClientBuilder.disableContentCompression().setSSLSocketFactory(sslsf).build();
这是通过httpClient获取请求和执行的代码:
HttpGet call = new HttpGet(url);
LinkPullerContentHandler handler = new LinkPullerContentHandler();
PageLinks pl = new PageLinks(url, 200, null);
HttpResponse getResponse;
try {
call.addHeader("User-Agent","Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)");
call.addHeader("Accept","text/html");
call.setConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build());
try {
getResponse = httpClient.execute(call);
} catch (SocketTimeoutException ste) {
LOG.error(String.format("Socket timeout pulling URL %s; skipping",url));
pl.setHttpStatusCode(408);
return pl;
} catch (ConnectTimeoutException cte) {
LOG.warn(String.format("Connect timeout pulling URL %s; skipping", url));
pl.setHttpStatusCode(408);
return pl;
} catch (UnknownHostException uhe) {
LOG.warn("Unable to resolve host for URL {}; skipping", url);
pl.setHttpStatusCode(404);
return pl;
} catch (Exception e) {
LOG.error(String.format("Unable to load link %s: (%s) %s",
url,e.getClass().getSimpleName(),e.getMessage()),e);
pl.setHttpStatusCode(420);
return pl;
}
int code = getResponse.getStatusLine().getStatusCode();
为了尝试专门包含 TLSv1.2 协议,我将 SSLConnectionSocketFactory 设置代码修改为:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(),
new String[] { "TLSv1.2" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
这适用于有问题的网站,但我觉得它在其他一些网站上会失败,因为它被硬编码为 TLSv1.2。或者,这是我尝试包含其他协议时的样子:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(),
new String[] { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello"},
null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
最终得到异常 "Unrecognized SSL message, plaintext connection." 我也试图包含一个 cipherSuites 列表以查看这是否有帮助,但我还没有取得任何成功。
有人可以提供任何帮助来帮助我弄清楚如何在不牺牲能够访问其他站点的情况下完成这项工作,那就太棒了。
提前致谢
不要使用 SSLv2Hello。
该站点显然不支持 v2 格式 hello;即使它会在 v3+ 格式 hello 上成功协商 TLSv1.2,如果我发送(使用 openssl)支持升级到 TLSv1.2(有线版本 3.3)的 v2 格式,它会以 HTML body 响应(甚至不是 HTTP headers)在明文中说 "Service unavailable" 和 "Site unavailable"。这当然不是 v2 协议或 v3+ 协议的有效响应。
如果您配置了从 SSLv3 到 TLSv1.2 的所有协议,但没有配置 SSLv2Hello(无论如何这是 Java8 的默认设置),您将能够与支持低至 SSLv3 的任何其他站点进行协商,假设没有其他问题,例如该站点使用了无效的证书。 (除了任何实施 仅 草案 TLSv1.3 而没有任何标准的网站。)如果有任何服务器仍然存在,那么旧或蹩脚它仅支持 SSLv2,Java 可以无论如何都要与它实际沟通; SSLv2Hello 的唯一 "benefit" 是 JSSE 抛出关于 "SSLv2 not supported" 的特定异常,而不是更难调试的各种且通常模棱两可的通信错误。
我正在尝试对网站 https://www.facebookbrand.com/ 执行 get 调用,但在这样做时我收到了错误 (SSLException) Received fatal alert: protocol_version。有问题的网站正在使用 TLSv1.2,如果我设置我的 ConnectionFactory 以专门支持此协议,它可以正常工作,但我需要它适用于每个网站,并且根据我的理解,将其硬编码为 TLSv1.2 会使它无法工作对于其他不使用 TLSv1.2 的 https 网站。我还尝试包含一个协议列表("TLSv1.2"、"TLSv1.1"、"TLSv1"、"SSLv3"、"SSLv2Hello"),但随后我收到错误 Unrecognized SSL message, plaintext联系。我不关心它有多安全,我只需要能够基本上连接到每个网站,并试图找到最好的方法来做到这一点。下面我包含了我的代码。
这是我设置 httpClient 等的代码:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
PlainConnectionSocketFactory pcsf = new PlainConnectionSocketFactory();
Registry socketFactoryRegistry = RegistryBuilder.create().register("https", sslsf).register("http", pcsf).build();
PoolingHttpClientConnectionManager multiConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
multiConnectionManager.setMaxTotal(100);
multiConnectionManager.setDefaultMaxPerRoute(10);
httpClientBuilder.setConnectionManager(multiConnectionManager);
this.httpClient = httpClientBuilder.disableContentCompression().setSSLSocketFactory(sslsf).build();
这是通过httpClient获取请求和执行的代码:
HttpGet call = new HttpGet(url);
LinkPullerContentHandler handler = new LinkPullerContentHandler();
PageLinks pl = new PageLinks(url, 200, null);
HttpResponse getResponse;
try {
call.addHeader("User-Agent","Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)");
call.addHeader("Accept","text/html");
call.setConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build());
try {
getResponse = httpClient.execute(call);
} catch (SocketTimeoutException ste) {
LOG.error(String.format("Socket timeout pulling URL %s; skipping",url));
pl.setHttpStatusCode(408);
return pl;
} catch (ConnectTimeoutException cte) {
LOG.warn(String.format("Connect timeout pulling URL %s; skipping", url));
pl.setHttpStatusCode(408);
return pl;
} catch (UnknownHostException uhe) {
LOG.warn("Unable to resolve host for URL {}; skipping", url);
pl.setHttpStatusCode(404);
return pl;
} catch (Exception e) {
LOG.error(String.format("Unable to load link %s: (%s) %s",
url,e.getClass().getSimpleName(),e.getMessage()),e);
pl.setHttpStatusCode(420);
return pl;
}
int code = getResponse.getStatusLine().getStatusCode();
为了尝试专门包含 TLSv1.2 协议,我将 SSLConnectionSocketFactory 设置代码修改为:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(),
new String[] { "TLSv1.2" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
这适用于有问题的网站,但我觉得它在其他一些网站上会失败,因为它被硬编码为 TLSv1.2。或者,这是我尝试包含其他协议时的样子:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build(),
new String[] { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello"},
null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
最终得到异常 "Unrecognized SSL message, plaintext connection." 我也试图包含一个 cipherSuites 列表以查看这是否有帮助,但我还没有取得任何成功。
有人可以提供任何帮助来帮助我弄清楚如何在不牺牲能够访问其他站点的情况下完成这项工作,那就太棒了。
提前致谢
不要使用 SSLv2Hello。
该站点显然不支持 v2 格式 hello;即使它会在 v3+ 格式 hello 上成功协商 TLSv1.2,如果我发送(使用 openssl)支持升级到 TLSv1.2(有线版本 3.3)的 v2 格式,它会以 HTML body 响应(甚至不是 HTTP headers)在明文中说 "Service unavailable" 和 "Site unavailable"。这当然不是 v2 协议或 v3+ 协议的有效响应。
如果您配置了从 SSLv3 到 TLSv1.2 的所有协议,但没有配置 SSLv2Hello(无论如何这是 Java8 的默认设置),您将能够与支持低至 SSLv3 的任何其他站点进行协商,假设没有其他问题,例如该站点使用了无效的证书。 (除了任何实施 仅 草案 TLSv1.3 而没有任何标准的网站。)如果有任何服务器仍然存在,那么旧或蹩脚它仅支持 SSLv2,Java 可以无论如何都要与它实际沟通; SSLv2Hello 的唯一 "benefit" 是 JSSE 抛出关于 "SSLv2 not supported" 的特定异常,而不是更难调试的各种且通常模棱两可的通信错误。