Android Https 网络服务通信 (SSL / TLS 1.2)

Android Https web service communication (SSL / TLS 1.2)

在我的 Android 应用程序中,我必须与 https 网络服务通信并读取响应。

我已通知服务器配置了 SSLTLS 1.2

我正在使用以下示例代码连接服务(https get请求),但只有运行Android5.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 更旧的 Androids 版本以支持 TLS 1.2 Web 服务通信)?

问题二

我刚刚用谷歌搜索并找到了这个 DOCUMENT

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

如果是这样的话,如果要与此 Web 服务通信(支持 TLS 1.2 Web 服务的 android 的最低版本),我应该支持的最低 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.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.run(HttpPage.java:69)
11-06 12:56:22.947: W/System.err(447):  at java.lang.Thread.run(Thread.java:1020)

编辑

这是在 Android 4.4.2 和 Android 5.1.1 设备上使用 Robert 的 MySSLSocketFactory class 实现时的完整堆栈跟踪。

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.run(HttpPage.java:74)
11-06 14:26:46.992: W/System.err(14700):    at java.lang.Thread.run(Thread.java:848)

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

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 的答案的 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()
    }
}