java - 如何将 HttpClient 与任何 ssl 证书一起使用,无论 "bad"是多少

标签 java apache-httpclient-4.x

我正在使用 Apache HttpClient在仅用于抓取公共(public)数据的网络爬虫中。

我希望它能够抓取证书无效的网站,无论证书多么无效。

我的爬虫不会传递任何用户名、密码等,也不会发送或接收任何敏感数据。

对于这个用例,我会抓取网站的 http 版本(如果它存在),但有时它当然不存在。

如何使用 Apache 的 HttpClient 完成此操作?

我尝试了一些建议,比如 this one , 但它们仍然因某些无效证书而失败,例如:

failed for url:https://dh480.badssl.com/, reason:java.lang.RuntimeException: Could not generate DH keypair
failed for url:https://null.badssl.com/, reason:Received fatal alert: handshake_failure
failed for url:https://rc4-md5.badssl.com/, reason:Received fatal alert: handshake_failure
failed for url:https://rc4.badssl.com/, reason:Received fatal alert: handshake_failure
failed for url:https://superfish.badssl.com/, reason:Connection reset

请注意,我已经尝试将我的 $JAVA_HOME/jre/lib/security/java.security 文件的 jdk.tls.disabledAlgorithms 设置为空,以确保这不是问题,我仍然会遇到类似上述的失败。

最佳答案

您的问题的简短答案是专门信任所有证书,将使用 TrustAllStrategy并做这样的事情:

SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.loadTrustMaterial(null, new TrustAllStrategy());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
        sslContextBuilder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
        socketFactory).build();

但是...无效的证书可能不是您的主要问题。 handshake_failure 的发生可能有多种原因,但根据我的经验,这通常是由于 SSL/TLS 版本不匹配或密码套件协商失败。这并不意味着 ssl 证书是“坏的”,它只是服务器和客户端之间的不匹配。您可以使用 Wireshark (more on that) 等工具准确查看握手失败的位置

虽然 Wireshark 可以很好地发现它的失败之处,但它不会帮助您提出解决方案。每当我过去调试 handshake_failures 时,我发现这个工具特别有用:https://testssl.sh/

您可以将该脚本指向任何失败的网站,以详细了解该目标上可用的协议(protocol)以及您的客户端需要支持哪些协议(protocol)才能建立成功的握手。它还将打印有关证书的信息。

例如(仅显示 testssl.sh 输出的两部分):

./testssl.sh www.google.com
....
 Testing protocols (via sockets except TLS 1.2, SPDY+HTTP2) 

 SSLv2               not offered (OK)
 SSLv3               not offered (OK)
 TLS 1               offered
 TLS 1.1             offered
 TLS 1.2             offered (OK)
 ....
Server Certificate #1
   Signature Algorithm          SHA256 with RSA
   Server key size              RSA 2048 bits
   Common Name (CN)             "www.google.com"
   subjectAltName (SAN)         "www.google.com" 
   Issuer                       "Google Internet Authority G3" ("Google Trust Services" from "US")
   Trust (hostname)             Ok via SAN and CN (works w/o SNI)
   Chain of trust               "/etc/*.pem" cannot be found / not readable
   Certificate Expiration       expires < 60 days (58) (2018-10-30 06:14 --> 2019-01-22 06:14 -0700)
 ....
 Testing all 102 locally available ciphers against the server, ordered by encryption strength 
(Your /usr/bin/openssl cannot show DH/ECDH bits)

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.  Encryption Bits
------------------------------------------------------------------------
xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH       AESGCM    256       
xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH       AESGCM    256       
xc014   ECDHE-RSA-AES256-SHA              ECDH       AES       256       
xc00a   ECDHE-ECDSA-AES256-SHA            ECDH       AES       256       
x9d     AES256-GCM-SHA384                 RSA        AESGCM    256       
x35     AES256-SHA                        RSA        AES       256       
xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH       AESGCM    128       
xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH       AESGCM    128       
xc013   ECDHE-RSA-AES128-SHA              ECDH       AES       128       
xc009   ECDHE-ECDSA-AES128-SHA            ECDH       AES       128       
x9c     AES128-GCM-SHA256                 RSA        AESGCM    128       
x2f     AES128-SHA                        RSA        AES       128       
x0a     DES-CBC3-SHA                      RSA        3DES      168 

因此,使用此输出我们可以看到,如果您的客户端仅支持 SSLv3,则握手将失败,因为服务器不支持该协议(protocol)。提供的协议(protocol)不太可能是问题,但您可以通过获取已启用协议(protocol)的列表来仔细检查您的 Java 客户端支持的内容。您可以从上面的代码片段中提供 SSLConnectionSocketFactory 的覆盖实现,以获取启用/支持的协议(protocol)和密码套件的列表,如下所示 (SSLSocket):

class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {
    @Override
    protected void prepareSocket(SSLSocket socket) throws IOException {
        System.out.println("Supported Ciphers" + Arrays.toString(socket.getSupportedCipherSuites()));
        System.out.println("Supported Protocols" + Arrays.toString(socket.getSupportedProtocols()));
        System.out.println("Enabled Ciphers" + Arrays.toString(socket.getEnabledCipherSuites()));
        System.out.println("Enabled Protocols" + Arrays.toString(socket.getEnabledProtocols()));
    }
}

我经常遇到密码套件协商失败时的handshake_failure。为避免此错误,您的客户端支持的密码套件列表必须至少包含一个与服务器支持的密码套件列表中的密码套件匹配的项。

如果服务器需要基于 AES256 的密码套件,您可能需要 Java 加密扩展 (JCE)。这些图书馆受国家限制,因此美国以外的人可能无法使用。

更多关于加密限制的信息,如果您有兴趣:https://crypto.stackexchange.com/questions/20524/why-there-are-limitations-on-using-encryption-with-keys-beyond-certain-length

关于java - 如何将 HttpClient 与任何 ssl 证书一起使用,无论 "bad"是多少,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53321255/

相关文章:

java - 无法从 Selenium 中的输入元素获取文本

java - 如何将 DocumentListener 分配给 JTextField 数组?

java - Spring JPA 存储库 : prevent update on save

java - HttpMethod.releaseConnection() 和 EntityUtils.consume(entity) 的问题

java - Apache HTTP 客户端 : build simulator using multithreaded environment

java - 除了目标站点凭据之外,如何在 Apache httpclient 中指定代理身份验证凭据?

java - 在android中计算圆周围的点

c# - 2d-bin-packing 将矩形放置在 x,y 位置的算法?

java - 有没有办法通过 NetBeans 发送 GET 和 POST 请求而无需外部库?

noclassdeffounderror - 创建 DefaultHttpClient 导致 NoClassDefFoundError