在OKHttp中启用TLSv1.2的Android

22

我正在使用OKHttp进行项目开发。我想为我的服务调用启用TLSv1.2。有人能告诉我如何启用它吗?


请告诉我我不能在stackoverflow上提问。 - Zeeshan
这个解决方案修复了我的问题:https://dev59.com/PI7ea4cB1Zd3GeqPGdaD#45853669 - Bajrang Hudda
5个回答

13

请查看OkHttp的HTTPS文档

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) 
  .tlsVersions(TlsVersion.TLS_1_2)
  .cipherSuites( 
     CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
     CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
     CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
  .build();
OkHttpClient client = ... 
client.setConnectionSpecs(Collections.singletonList(spec));

4
根据文档说明,这只在安卓5.0或更高版本上才能运行。 - Shlomi
3
它甚至不能在Android 5.0上运行。我遇到了SSL握手异常。 - IgorGanapolsky
@ShanXeeshi 能否解释一下为什么它不起作用?很可能是你的 Web 服务器没有配置 TLS 1.2。 - Jesse Wilson
已在以下 Android 设备上测试通过:4.4.2、6.0.1、7.0。 - Kirill Vashilo
和 @IgorGanapolsky 一样,我也遇到了 javax.net.ssl.SSLHandshakeException:HANDSHAKE_FAILURE_ON_CLIENT_HELLO 的问题。 - Oleksandr Nos
显示剩余3条评论

10
据我所知,OKHttp不包括自己的SSL/TLS库,因此它只使用Android提供的标准SSLSocket。支持和启用哪些TLS版本取决于所使用的Android版本。在一些手机上,虽然支持TLS 1.2,但默认情况下未被启用(据我记得这会影响安装有Android 4.1/4.2/4.4的手机)。在这种情况下,您可以通过实现一个自定义的包装器SSLSocketFactory,在内部使用默认的SSLSocketFactory并对每个创建的Socket调用“setEnabledProtocols(new String[]{"TLS1.2"})”来启用它。在已安装Google服务的设备上,将旧的Android 4.x设备启用TLS 1.2的首选方法是使用ProviderInstaller

1
感谢您的建议,我将它与 https://dev59.com/_l4b5IYBdhLWcg3wYAb6#38962842 相结合。如果 ProviderInstaller 失败(或引发异常),我会尝试使用自定义的 SSLSoketFactory 来设置启用的协议... - convexHull

6

检查我的代码!完美运行!

private void checkTls() {
    if (android.os.Build.VERSION.SDK_INT < 21) {
        try {
            ProviderInstaller.installIfNeededAsync(this, new ProviderInstaller.ProviderInstallListener() {
                @Override
                public void onProviderInstalled() {
                }

                @Override
                public void onProviderInstallFailed(int i, Intent intent) {
                }
            });
        } catch (Exception e) {
            finish();
            e.printStackTrace();
        }
    }
}

2
在调用 Web 服务之前,请使用此代码。只需要在您的应用程序中调用一次即可。 - wei wang
5
这个解决方案不适用于所有人。ProviderInstaller类来自于Google Play服务,而Google Play服务并非所有Android设备都可用。尤其是在中国,因为Google Play服务被禁用了。 - EJK

1
结果发现我的解决方案与Ken的非常相似(只是用Java实现)。我在这里找到了它here,虽然必须进行一些小修改才能使它正常工作。希望这对其他人来说可以“开箱即用”。
public class TLSSocketFactoryCompat extends SSLSocketFactory {

private SSLSocketFactory internalSSLSocketFactory;

public TLSSocketFactoryCompat() throws KeyManagementException, NoSuchAlgorithmException {
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, null, null);
    internalSSLSocketFactory = context.getSocketFactory();
}

public TLSSocketFactoryCompat(TrustManager[] tm) throws KeyManagementException, NoSuchAlgorithmException {
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, tm, new java.security.SecureRandom());
    internalSSLSocketFactory = context.getSocketFactory();
}

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

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

@Override
public Socket createSocket() throws IOException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSOnSocket(Socket socket) {
    if(socket != null && (socket instanceof SSLSocket)) {
        //Create list of supported protocols
        ArrayList<String> supportedProtocols = new ArrayList<>();
        for (String protocol : ((SSLSocket)socket).getEnabledProtocols()) {

            //Log.d("TLSSocketFactory", "Supported protocol:" + protocol);
            //Only add TLS protocols (don't want ot support older SSL versions)
            if (protocol.toUpperCase().contains("TLS")) {
                supportedProtocols.add(protocol);
            }
        }
        //Force add TLSv1.1 and 1.2 if not already added
        if (!supportedProtocols.contains("TLSv1.1")) {
            supportedProtocols.add("TLSv1.1");
        }
        if (!supportedProtocols.contains("TLSv1.2")) {
            supportedProtocols.add("TLSv1.2");
        }

        String[] protocolArray = supportedProtocols.toArray(new String[supportedProtocols.size()]);
        /*for (int i = 0; i < protocolArray.length; i++) {
            Log.d("TLSSocketFactory", "protocolArray[" + i + "]" + protocolArray[i]);
        }*/

        //enable protocols in our list
        ((SSLSocket)socket).setEnabledProtocols(protocolArray);
    }
    return socket;
  }
}

使用方法:

    OkHttpClient httpClient = new OkHttpClient();
    //Add Custom SSL Socket Factory which adds TLS 1.1 and 1.2 support for Android 4.1-4.4
    try {
        httpClient.setSslSocketFactory(new TLSSocketFactoryCompat());
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

我对这个非常有希望,但我看到 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 启用TLS 1.2 javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL握手终止:ssl=0x5ff7f518:SSL库中的错误,通常是协议错误 error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure (external/openssl/ssl/s3_pkt.c:1256 0x5fe90e28:0x00000003) at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:568) - Someone Somewhere
在放弃之前,它会尝试在几秒钟内至少8次调用enableTLSOnSocket()。 - Someone Somewhere
某个地方的某人,我不确定你遇到的错误是否与证书问题有关,但这让我想起我更新了这个类以确保仅添加支持的协议列表。我认为之前可能只启用了TLS 1.1和1.2。希望这可以帮助到你。 - aaronmarino
1
嗨,艾伦,我今天发现我遇到的问题是由于设备固件问题引起的。如果底层固件有问题,应用程序就无法奏效。 - Someone Somewhere
我该如何在HttpClient中实现呢?我的意思是如何设置setSslSocketFactory? - Horrorgoogle
为什么我会收到 java.lang.IllegalStateException: Unable to extract the trust manager on Platform, sslSocketFactory is class 的错误信息? - francis

0

这基本上与上面的答案相同,但我觉得代码示例对于任何其他降落在此处并且不熟悉导航Java SSL领域的人都会有用。

最终对我有用的是基于此处报告的问题:https://github.com/mattleibow/square-bindings/issues/1
来自此要点 https://gist.github.com/mattleibow/c8abfa323db094b820cc

请注意,这些代码示例是使用C# / Xamarin编写的,但可以很容易地转换为Java。

internal class CompleteSSLSocketFactory : SSLSocketFactory
{
    private readonly SSLSocketFactory innerFactory;

    public CompleteSSLSocketFactory (SSLSocketFactory innerFactory)
    {
        this.innerFactory = innerFactory;
    }

    public override string[] GetDefaultCipherSuites ()
    {
        return innerFactory.GetDefaultCipherSuites ();
    }

    public override string[] GetSupportedCipherSuites ()
    {
        return innerFactory.GetSupportedCipherSuites ();
    }

    public override Socket CreateSocket ()
    {
        return MakeSocketSafe (innerFactory.CreateSocket ());
    }

    public override Socket CreateSocket (Socket s, string host, int port, bool autoClose)
    {
        return MakeSocketSafe (innerFactory.CreateSocket (s, host, port, autoClose));
    }

    public override Socket CreateSocket (string host, int port)
    {
        return MakeSocketSafe (innerFactory.CreateSocket (host, port));
    }

    public override Socket CreateSocket (string host, int port, InetAddress localHost, int localPort)
    {
        return MakeSocketSafe (innerFactory.CreateSocket (host, port, localHost, localPort));
    }

    public override Socket CreateSocket (InetAddress host, int port)
    {
        return MakeSocketSafe (innerFactory.CreateSocket (host, port));
    }

    public override Socket CreateSocket (InetAddress address, int port, InetAddress localAddress, int localPort)
    {
        return MakeSocketSafe (innerFactory.CreateSocket (address, port, localAddress, localPort));
    }

    private Socket MakeSocketSafe (Socket socket)
    {
        var sslSocket = socket as SSLSocket;
        if (sslSocket != null) {
            // enable all supported protocols for this socket
            sslSocket.SetEnabledProtocols (sslSocket.GetSupportedProtocols ());
            sslSocket.SetEnabledCipherSuites (sslSocket.GetSupportedCipherSuites ());
        }
        return socket;
    }
}

然后像这样调用它:

// this.client is an OkHttpClient
if (Android.OS.Build.VERSION.SdkInt < BuildVersionCodes.Lollipop) {
    this.client.SetSslSocketFactory(new CompleteSSLSocketFactory(HttpsURLConnection.DefaultSSLSocketFactory));
}

这对我有用,在API 19上测试过。


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