安卓Https Web服务通信(SSL/TLS 1.2)

9
在我的Android应用程序中,我需要与https web服务通信并读取响应。
我已经告知服务器配置了TLS 1.2的SSL。
我正在使用以下示例代码连接服务(https get请求),但只有运行Android 5.0或更高版本的设备才能成功通信和读取响应...
所有低于该版本(Android 5.0)的其他设备都无法通信,并在尝试建立连接时抛出IOException...
    HttpResponse response = null;
    try 
    {        
        HttpClient client = new DefaultHttpClient();

        HttpGet request = new HttpGet();
        request.setURI(new URI("https://domain.co.uk/services/pay.aspx?param1=val1&param2=val2"));

        response = client.execute(request);

        HttpEntity entity = response.getEntity();
        String responseString = EntityUtils.toString(entity); 
        String decodedResStr = URLDecoder.decode(responseString, "UTF-8");  

        Log.v("AppState", "Response: " + decodedResStr);
    }  
    catch (Exception e) 
    {
        e.printStackTrace();
        Log.v("AppState", "Exception: " + e.getMessage() )
    }

或者

    // HttpURLConnection urlConnection = null;
    HttpsURLConnection urlConnection = null;

    try
    {
        URL url = new URL("https://domain.co.uk/services/pay.aspx?param1=val1&param2=val2");
        //urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection = (HttpsURLConnection) url.openConnection();

        InputStream in = new BufferedInputStream(urlConnection.getInputStream());

    }
    catch (Exception e)
    {
        e.printStackTrace();
        Log.v("AppState", "Exception: " + e.getMessage() )
    }
    finally
    {
        urlConnection.disconnect();
    }

问题1

我想知道我在这里是否做错了什么(如果我的代码缺少任何其他参数来支持早于5.0版本的Android以支持TLS 1.2 Web服务通信)?

问题2

我刚刚谷歌搜索并找到了这个文档

上面说,在ANDROID中与TLS 1.2通信的最低支持浏览器是“GOOGLE Android 5.0 OS Browser”。那么当尝试通过代码(应用程序)连接时,是否应用相同的限制?

如果是这样的话,如果想要与此Web服务通信,我应该支持的最低Android版本是多少(支持TLS 1.2 Web服务的最低Android版本)?

示例异常堆栈跟踪如下

Android 2.2模拟器

    11-06 12:51:01.885: W/System.err(352): java.io.IOException: SSL handshake failure: I/O error during system call, Unknown error: 0
    11-06 12:51:01.895: W/System.err(352):  at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.nativeconnect(Native Method)
    11-06 12:51:01.895: W/System.err(352):  at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:316)
    11-06 12:51:01.895: W/System.err(352):  at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.getSecureSocket(HttpConnection.java:168)
    11-06 12:51:01.905: W/System.err(352):  at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:399)
    11-06 12:51:01.915: W/System.err(352):  at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1152)
    11-06 12:51:01.915: W/System.err(352):  at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:253)
    11-06 12:51:01.915: W/System.err(352):  at com.serviceapp.WSHelperHttpURLConnection.executeAndroid(WSHelperHttpURLConnection.java:93)
    11-06 12:51:01.915: W/System.err(352):  at com.serviceapp.HttpPage$1$1.run(HttpPage.java:69)
    11-06 12:51:01.915: W/System.err(352):  at java.lang.Thread.run(Thread.java:1096)

Android 3.0模拟器

11-06 12:56:22.917: W/System.err(447): javax.net.ssl.SSLException: Connection closed by peer
11-06 12:56:22.927: W/System.err(447):  at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
11-06 12:56:22.927: W/System.err(447):  at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:485)
11-06 12:56:22.927: W/System.err(447):  at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:323)
11-06 12:56:22.927: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.setupSecureSocket(HttpConnection.java:167)
11-06 12:56:22.937: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:479)
11-06 12:56:22.937: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeConnection(HttpsURLConnectionImpl.java:428)
11-06 12:56:22.937: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1038)
11-06 12:56:22.937: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:523)
11-06 12:56:22.937: W/System.err(447):  at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:283)
11-06 12:56:22.947: W/System.err(447):  at com.serviceapp.WSHelperHttpURLConnection.executeAndroid(WSHelperHttpURLConnection.java:93)
11-06 12:56:22.947: W/System.err(447):  at com.serviceapp.HttpPage$1$1.run(HttpPage.java:69)
11-06 12:56:22.947: W/System.err(447):  at java.lang.Thread.run(Thread.java:1020)

编辑

当使用Robert的MySSLSocketFactory类实现与Android 4.4.2和Android 5.1.1设备时,这是完整的堆栈跟踪。

11-06 14:26:46.962: W/System.err(14700): java.lang.IllegalArgumentException: protocol TLS1.2 is not supported
11-06 14:26:46.985: W/System.err(14700):    at com.android.org.conscrypt.NativeCrypto.checkEnabledProtocols(NativeCrypto.java:879)
11-06 14:26:46.985: W/System.err(14700):    at com.android.org.conscrypt.OpenSSLSocketImpl.setEnabledProtocols(OpenSSLSocketImpl.java:807)
11-06 14:26:46.985: W/System.err(14700):    at com.serviceapp.MySSLSocketFactory.createSocket(WSURlCon.java:99)
11-06 14:26:46.986: W/System.err(14700):    at com.serviceapp.MySSLSocketFactory.createSocket(WSURlCon.java:1)
11-06 14:26:46.986: W/System.err(14700):    at com.android.okhttp.Connection.upgradeToTls(Connection.java:131)
11-06 14:26:46.986: W/System.err(14700):    at com.android.okhttp.Connection.connect(Connection.java:107)
11-06 14:26:46.986: W/System.err(14700):    at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
11-06 14:26:46.987: W/System.err(14700):    at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
11-06 14:26:46.988: W/System.err(14700):    at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
11-06 14:26:46.988: W/System.err(14700):    at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
11-06 14:26:46.990: W/System.err(14700):    at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
11-06 14:26:46.990: W/System.err(14700):    at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179)
11-06 14:26:46.991: W/System.err(14700):    at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:246)
11-06 14:26:46.991: W/System.err(14700):    at com.serviceapp.WSURlCon.executeAndroid(WSURlCon.java:33)
11-06 14:26:46.992: W/System.err(14700):    at com.serviceapp.HttpPage$1$1.run(HttpPage.java:74)
11-06 14:26:46.992: W/System.err(14700):    at java.lang.Thread.run(Thread.java:848)
2个回答

15
根据 Android 开发者文档,TLS 1.2 可在 API 级别 20+(Android 4.4 Wearable)的设备上使用和启用:

http://developer.android.com/reference/javax/net/ssl/SSLEngine.html

我猜您的测试设备都没有使用该 API 级别,因此只有 5.0 设备可以连接。

根据我的个人经验,一些 4.4 设备支持 TLS 1.2,但未启用。您可以尝试通过在使用的 SSLSocket 上调用 setEnabledProtocols(new String[]{"TLSv1.2"}) 来启用它。

通过实现代理模式来编写自己的 SSLSocketFactory 是一种优雅的解决方案:

public class MySSLSocketFactory extends SSLSocketFactory {

    SSLSocketFactory sslSocketFactory;

    public MySSLSocketFactory(SSLSocketFactory sslSocketFactory) {
        super();
        this.sslSocketFactory = sslSocketFactory;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);
        socket.setEnabledProtocols(new String[] { "TLSv1.2" });
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port);
        socket.setEnabledProtocols(new String[] { "TLSv1.2" });
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException,
            UnknownHostException {
        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port, localHost, localPort);
        socket.setEnabledProtocols(new String[] { "TLSv1.2" });
        return socket;
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port);
        socket.setEnabledProtocols(new String[] { "TLSv1.2" });
        return socket;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
            throws IOException {
        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(address, port, localAddress, localPort);
        socket.setEnabledProtocols(new String[] { "TLSv1.2" });
        return socket;
    }

您可以这样使用它:

    ...
    urlConnection = (HttpsURLConnection) url.openConnection();
    urlConnection.setSSLSocketFactory(new MySSLSocketFactory(urlConnection.getSSLSocketFactory()));
    ...

嗨,Robert,感谢回答。我刚用您在这里定义的“MySSLSocketFactory”类进行了一项测试,测试使用运行Android 4.4.2版本的Android设备,并收到异常消息“java.lang.IllegalArgumentException:协议TLS1.2不受支持”。我将在我的问题编辑下添加完整的堆栈跟踪....另外作为附加测试,设备默认的Web浏览器(Chrome和Android Web浏览器均)可以无任何问题地访问托管URL的Web服务。 - JibW
同样的异常也出现在安卓5.1.1设备上。 - JibW
看起来我错过了一个“v”:正确应该是“TLSv1.2”。 - Robert
是的,就是这样。在Android 4.4.2和5.1.1版本的设备上运行良好...谢谢并非常有帮助... - JibW
@bdevloper 你找到答案了吗? - Anurag
不过在我的情况下,问题被服务器端解决了。从现在起我使用Retrofit。 - Bhavin Patel

0

这是 Robert 的答案的 Kotlin 版本,它在我的情况下解决了问题:

import java.net.InetAddress
import java.net.Socket
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory

class MySSLSocketFactory(socket_factory :SSLSocketFactory):
      SSLSocketFactory()
{
    val sslSocketFactory :SSLSocketFactory

    init
    {
        this.sslSocketFactory = socket_factory
    }

    override fun getDefaultCipherSuites(): Array<String>
    {
        return this.sslSocketFactory.getDefaultCipherSuites()
    }

    override fun createSocket(socket: Socket?, host: String?, port: Int, autoclose: Boolean): Socket
    {
        val socket: SSLSocket = this.sslSocketFactory.createSocket(socket, host, port, autoclose) as SSLSocket
        socket.setEnabledProtocols(arrayOf("TLSv1.2"))
        return socket
    }

    override fun createSocket(host: String?, port: Int): Socket
    {
        val socket: SSLSocket = this.sslSocketFactory.createSocket(host, port) as SSLSocket
        socket.setEnabledProtocols(arrayOf("TLSv1.2"))
        return socket
    }

    override fun createSocket(host: String?, port: Int, local_host: InetAddress?, local_port: Int): Socket {
        val socket = sslSocketFactory.createSocket(host, port, local_host, local_port) as SSLSocket
        socket.enabledProtocols = arrayOf("TLSv1.2")
        return socket
    }

    override fun createSocket(host: InetAddress?, port: Int): Socket
    {
        val socket = sslSocketFactory.createSocket(host, port) as SSLSocket
        socket.enabledProtocols = arrayOf("TLSv1.2")
        return socket
    }

    override fun createSocket(address: InetAddress?, port: Int, local_address: InetAddress?, local_port: Int): Socket
    {
        val socket = sslSocketFactory.createSocket(address, port, local_address, local_port) as SSLSocket
        socket.enabledProtocols = arrayOf("TLSv1.2")
        return socket
    }

    override fun getSupportedCipherSuites(): Array<String>
    {
        return this.sslSocketFactory.getSupportedCipherSuites()
    }
}

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