在Android 5.0 Lollipop中,HttpClient失败并且Handshake Failed

时间:2014-11-24 18:53:41

标签: android ssl apache-httpclient-4.x android-5.0-lollipop handshake

Android 5.0 Lollipop中的DefaultHttpClient似乎被打破了。它无法设置与先前版本的Android成功设置的某些网站的连接。

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

//Create httpclient like in https://stackoverflow.com/questions/18523784/ssl-tls-protocols-and-cipher-suites-with-the-androidhttpclient
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(设备和模拟器)上失败,错误连接被对等关闭。 当然这是可以理解的,因为Android 5.0尝试将这个旧服务器与TLSv1.2和现代密码连接起来,并且它不支持它们。

好的,使用SSL/TLS protocols and cipher suites with the AndroidHttpClient中的示例代码,我们将协议和密码限制为 TLSv1 SSL_RSA_WITH_RC4_128_MD5 。现在它失败并出现不同的错误:

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连接。

2 个答案:

答案 0 :(得分:5)

更新:结果是后端的一个错误,而不是android 5 ,但确实存在问题密码。

我遇到了同样的问题。对我来说,它原来是密码TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,它是从android 5的(更新的)默认密码集中选择的。

只要我从可接受的密码的客户端列表中删除它,连接就会再次运行。

android 5 change log提及:

  
      
  • AES-GCM(AEAD)密码套件现已启用,
  •   

我很确定这是罪魁祸首。一旦首选TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(由服务器),连接将失败。

请注意TLS_DHE_RSA_WITH_AES_128_GCM_SHA256有效。

我的猜测是,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384的Android实现是错误的,或者是您正在与之交谈的服务器之一。

解决方案:

  1. 从服务器上的可用密码中删除TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(无需重新部署应用程序)。
  2. 从客户提供的密码列表中删除TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(在CLIENT_HELLO期间)。
  3. 您可以通过实现自己的SSLSocketFactory并调用

    在客户端执行此操作
    sslSocket.setEnabledCipherSuites(String[] suites);
    

    关于SSLSocket的创建。

    编辑请注意,这不一定是一个android bug,可能是服务器实现有问题。如果您的问题确实是由密码引起的,请在android bug跟踪器上留下评论](https://code.google.com/p/android/issues/detail?id=81603)。谢谢!

答案 1 :(得分:0)

我尝试在自定义套接字工厂中更改cipherSuites,但这没有帮助。就我而言,我必须从套接字的EnabledProtocols中删除TLSv1.1和TLSv1.2协议。似乎一些较旧的服务器不能很好地处理新协议的协议协商。有各种用于创建自定义套接字工厂的示例,例如How to override the cipherlist sent to the server by Android when using HttpsURLConnection?,以及用于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);
}