Android 5.0 Lollipop中的HttpClient握手失败问题

15

在Android 5.0 Lollipop中,DefaultHttpClient似乎存在问题。它无法连接一些之前版本的Android已成功连接的网站。

例如,我尝试连接到https://uralsg.megafon.ru

//Create httpclient like in https://dev59.com/eHbZa4cB1Zd3GeqPD0eZ
HttpClient client = new DefaultHttpClient(manager, params);
HttpGet httpGet = new HttpGet("https://uralsg.megafon.ru");
HttpResponse client = httpclient.execute(httpGet);

这段代码在Android 2.3-4.4上能够正常工作,但在Android 5.0上(包括模拟器和设备)因为出现了“Connection closed by peer”错误而无法使用。

显然这是可以理解的,因为Android 5.0尝试使用TLSv1.2和现代密码算法连接这个旧服务器,而这些协议和算法并不被支持。

好的,我们参考SSL/TLS protocols and cipher suites with the AndroidHttpClient中的示例代码,将协议和密码限制为和。现在它出现了不同的错误:

javax.net.ssl.SSLHandshakeException: Handshake failed
caused by 
    error:140943FC:SSL routines:SSL3_READ_BYTES:sslv3 alert bad record mac 
    (external/openssl/ssl/s3_pkt.c:1286 0x7f74c1ef16e0:0x00000003) 
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake

当然,这段代码在Android 2.3-4.4上运行顺畅。

我使用wireshark检查了流量:

302 4002.147873000  192.168.156.30  83.149.32.13    TLSv1   138 Client Hello
303 4002.185362000  83.149.32.13    192.168.156.30  TLSv1   133 Server Hello
304 4002.186700000  83.149.32.13    192.168.156.30  TLSv1   1244    Certificate
305 4002.186701000  83.149.32.13    192.168.156.30  TLSv1   63  Server Hello Done
307 4002.188117000  192.168.156.30  83.149.32.13    TLSv1   364 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
308 4002.240695000  83.149.32.13    192.168.156.30  TLSv1   61  Alert (Level: Fatal, Description: Bad Record MAC)

您可以看到连接已经建立,但是服务器报警,因为它可能无法解码加密的握手消息。

我无法使用 Android 5.0 上的 HttpClient 连接 https://uralsg.megafon.ru。然而,股票浏览器确实可以连接它。Android 2.3-4.4 可以以任何方式连接此站点而无需任何困难。

是否有办法使 HttpClient 能够连接这样的网站?这只是一个例子,我相信还有很多传统服务器无法通过 Android 5.0 和 HttpClient 连接。


我也遇到了同样的问题。如果我知道解决方案,我会添加评论。这真的很烦人。 - cmcromance
2个回答

5
更新:结果证明这是后端的一个错误,而不是Android 5的问题,尽管确实涉及到了该密码。我遇到了相同的问题。对我来说,它被证明是Android 5(更新后)默认密码集中选择的密码TLS_DHE_RSA_WITH_AES_256_GCM_SHA384。
只要我从可接受密码的客户端列表中删除它,连接就可以正常工作了。 Android 5更改日志提到:
  • 现在启用了AES-GCM(AEAD)密码套件,
我非常确定这是罪魁祸首。只要服务器首选TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,连接就会失败。
请注意,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256有效。
我猜测,Android实现的TLS_DHE_RSA_WITH_AES_256_GCM_SHA384可能存在错误,或者您正在与之通信的服务器存在错误。

解决方案:

  1. 从服务器可用的密码列表中移除 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(无需重新部署应用程序)。
  2. 在客户端提供的密码列表(CLIENT_HELLO期间)中移除 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384

您可以通过实现自己的 SSLSocketFactory 并调用以下方法在客户端上执行:

sslSocket.setEnabledCipherSuites(String[] suites);

在创建SSLSocket时。
编辑:请注意,这不一定是Android的错误,可能是服务器实现有问题。如果您的问题确实是由密码引起的,请在Android错误跟踪器上留下评论。谢谢!

1
在我的情况下并非如此。在我的情况下,该网站仅支持SSL_RSA_WITH_RC4_128_MD5。当我建议使用SSL_RSA_WITH_RC4_128_MD5时,在Lollipop上失败,在Jelly Bean及以下版本上成功连接。 - Dmitry Kochin
据我所知,由于安全考虑,SSL_RSA_WITH_RC4_128_MD5 在棒棒糖版本中已被移除!请参见 https://code.google.com/p/android-developer-preview/issues/detail?id=1200#c8 - stefs
我知道,但问题是手动添加并不能解决这个问题。 - Dmitry Kochin
1
去掉那个并没有帮助到我的情况 :( - lostintranslation
@DmitryKochin 你可以尝试启用所有支持的密码套件,这可能会有所帮助。详情请见:http://stackoverflow.com/a/30742240/2055854。 - Orest

0
我尝试在自定义套接字工厂中更改cipherSuites,但这并没有帮助。在我的情况下,我必须从套接字的EnabledProtocols中删除TLSv1.1和TLSv1.2协议。看起来一些较旧的服务器不能很好地处理新协议的协议协商。有各种各样的示例用于创建自定义套接字工厂,例如如何在使用HttpsURLConnection时覆盖Android发送到服务器的密码列表?以及其他用于Apache套接字的示例。完成这样做后,我只需调用以下AdjustSocket方法来删除协议。
private void AdjustSocket(Socket socket)
{
    String[] protocols = ((SSLSocket) socket).getSSLParameters().getProtocols();
    ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(protocols));

    for (int ii = protocolList.size() - 1; ii >= 0; --ii )
        {
        if ((protocolList.get(ii).contains("TLSv1.1")) || (protocolList.get(ii).contains("TLSv1.2")))
            protocolList.remove(ii);
        }

    protocols = protocolList.toArray(new String[protocolList.size()]);
    ((SSLSocket)socket).setEnabledProtocols(protocols);
}

我手动将协议限制为SSLv3、TLSv1、TLSv1.1或更高版本之一,但在我的情况下并没有帮助。 - Dmitry Kochin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接