如何在 Android 应用程序中启用 TLS 1.2 支持(在 Android 4.1 JB 上运行)

Posted

技术标签:

【中文标题】如何在 Android 应用程序中启用 TLS 1.2 支持(在 Android 4.1 JB 上运行)【英文标题】:How to enable TLS 1.2 support in an Android application (running on Android 4.1 JB) 【发布时间】:2015-05-10 16:57:54 【问题描述】:

根据 android 中针对 SSLSocketSSLContext 的文档,API 级别 16+ 支持 TLS v1.1 和 v1.2 协议,但默认情况下不启用。 http://developer.android.com/reference/javax/net/ssl/SSLSocket.html http://developer.android.com/reference/javax/net/ssl/SSLContext.html

如何在运行 Android 4.1 或更高版本(但低于 5.0)的设备上启用它?

我尝试创建一个自定义 SSLSocketFactory,它在创建 Socket 时启用所有受支持的协议,然后将我的自定义实现用作:

HttpsURLConnection.setDefaultSSLSocketFactory(new MySSLSocketFactory());

public class MySSLSocketFactory extends SSLSocketFactory 
        
        private SSLContext sc;
        private SSLSocketFactory ssf;  
        
        public MySSLSocketFactory() 
            try 
                sc = SSLContext.getInstance("TLS");
                sc.init(null, null, null);
                ssf = sc.getSocketFactory();

             catch (NoSuchAlgorithmException e) 
                e.printStackTrace();
             catch (KeyManagementException e) 
                e.printStackTrace();
              
        
        
        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose)
                throws IOException 
            SSLSocket ss = (SSLSocket) ssf.createSocket(s, host, port, autoClose);
            ss.setEnabledProtocols(ss.getSupportedProtocols());
            ss.setEnabledCipherSuites(ss.getSupportedCipherSuites());
            return ss;
        

        @Override
        public String[] getDefaultCipherSuites() 
            return ssf.getDefaultCipherSuites();
        

        @Override
        public String[] getSupportedCipherSuites() 
            return ssf.getSupportedCipherSuites();
        

        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException 
            SSLSocket ss = (SSLSocket) ssf.createSocket(host, port);
            ss.setEnabledProtocols(ss.getSupportedProtocols());
            ss.setEnabledCipherSuites(ss.getSupportedCipherSuites());
            return ss;
        

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException 
            SSLSocket ss = (SSLSocket) ssf.createSocket(host, port);
            ss.setEnabledProtocols(ss.getSupportedProtocols());
            ss.setEnabledCipherSuites(ss.getSupportedCipherSuites());
            return ss;
        

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
                throws IOException, UnknownHostException 
            SSLSocket ss = (SSLSocket) ssf.createSocket(host, port, localHost, localPort);
            ss.setEnabledProtocols(ss.getSupportedProtocols());
            ss.setEnabledCipherSuites(ss.getSupportedCipherSuites());
            return ss;
        

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
                int localPort) throws IOException 
            SSLSocket ss = (SSLSocket) ssf.createSocket(address, port, localAddress, localPort);
            ss.setEnabledProtocols(ss.getSupportedProtocols());
            ss.setEnabledCipherSuites(ss.getSupportedCipherSuites());
            return ss;
        
    

但在尝试与启用了仅 TLS 1.2 的服务器建立连接时,它仍然会出现异常。

这是我得到的例外:

03-09 09:21:38.427: W/System.err(2496): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException:SSL 握手中止: ssl=0xb7fa0620: SSL 库失败,通常是协议错误

03-09 09:21:38.427: W/System.err(2496): 错误:14077410:SSL 例程:SSL23_GET_SERVER_HELLO:sslv3 警报握手失败 (外部/openssl/ssl/s23_clnt.c:741 0xa90e6990:0x00000000)

【问题讨论】:

在我的测试中,我发现虽然 TLSv1.2 可用并且可以在 API 16-18 上启用,但在提供客户端证书等特定用途方面仍然存在问题,即使在包含 Play Services 之后也是如此。还没有解决方案。 更令人困惑的是 SSLEngine 声明对 TLS1.1/1.2 的支持仅在 API20+ 中。 developer.android.com/reference/javax/net/ssl/SSLEngine.html 【参考方案1】:

你应该使用

 SSLContext.getInstance("TLSv1.2"); 

具体协议版本。

第二个异常发生是因为默认的 socketFactory 使用了备用 SSLv3 协议来处理故障。

您可以在此处使用主要答案中的 NoSSLFactory 来抑制 How to disable SSLv3 in android for HttpsUrlConnection?

您还应该使用所有证书(如果需要,客户端和受信任的证书)初始化 SSLContext

但如果不使用,所有这些都是无用的

ProviderInstaller.installIfNeeded(getContext())

这里有更多关于正确使用场景的信息https://developer.android.com/training/articles/security-gms-provider.html

希望对你有帮助。

【讨论】:

据此 - grepcode.com/file/repo1.maven.org/maven2/org.ektorp/… - 仅接受 "TLS""SSL""SSLV2" 这似乎无法解决 4.1.2 客户端的问题 也许这很愚蠢,但我尝试将 4.0.1.2 版本中的 com.google.android lib 添加到我的 gradle 中,但我无法导入 com.google.android.gms.security.* 所以从哪个包你导入ProviderInstaller?【参考方案2】:

启用 TLSv1.1 和 TLSv1.2 的两种方法:

    使用此指南: http://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/ 使用这个类 https://github.com/erickok/transdroid/blob/master/app/src/main/java/org/transdroid/daemon/util/TlsSniSocketFactory.javaschemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(), port));

【讨论】:

我使用了您的第二个建议,但我遇到了问题。在 TlsSniSocketFactory 类的 createSocket() 方法中,返回的 SSL Session 无效。我不知道为什么,但这发生在 API 17 和 API 24 设备中?请查看我的详细查询:***.com/questions/40684898/… 非常感谢!这为我节省了很多时间! 使用第一个选项,使用 httpclient,但 v1.0 变旧,实际上想要 v1.2。私有 HttpClient getTheHttpClient(HttpParams httpParameters) SchemeRegistry registry = new SchemeRegistry();尝试 registry.register(new Scheme("https", new TLSSocketFactory(), 443)); 捕捉 (KeyManagementException e) e.printStackTrace(); catch (NoSuchAlgorithmException e) e.printStackTrace(); HttpClient 客户端 = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParameters, registry),httpParameters);返回客户; 【参考方案3】:

@Inherently Curious - 感谢您发布此信息。您快到了 - 您必须向 SSLContext.init() 方法添加另外两个参数。

TrustManager[] trustManagers = new TrustManager[]  new TrustManagerManipulator() ;
sc.init(null, trustManagers, new SecureRandom());

它将开始工作。再次非常感谢您发布此信息。我用你的代码解决了这个/我的问题。

【讨论】:

我一定是错过了,但是 TrustManagerManipulator 类是从哪里来的呢?【参考方案4】:

我按照文章http://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/ 中提供的指示解决了这个问题,几乎没有改变。

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
SSLSocketFactory noSSLv3Factory = null;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) 
    noSSLv3Factory = new TLSSocketFactory(sslContext.getSocketFactory());
 else 
    noSSLv3Factory = sslContext.getSocketFactory();

connection.setSSLSocketFactory(noSSLv3Factory);

这是自定义TLSSocketFactory的代码:

public static class TLSSocketFactory extends SSLSocketFactory 

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory(SSLSocketFactory delegate) throws KeyManagementException, NoSuchAlgorithmException 
        internalSSLSocketFactory = delegate;
    

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

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

    @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));
    

    /*
     * Utility methods
     */

    private static Socket enableTLSOnSocket(Socket socket) 
        if (socket != null && (socket instanceof SSLSocket)
                && isTLSServerEnabled((SSLSocket) socket))  // skip the fix if server doesn't provide there TLS version
            ((SSLSocket) socket).setEnabledProtocols(new String[]TLS_v1_1, TLS_v1_2);
        
        return socket;
    

    private static boolean isTLSServerEnabled(SSLSocket sslSocket) 
        System.out.println("__prova__ :: " + sslSocket.getSupportedProtocols().toString());
        for (String protocol : sslSocket.getSupportedProtocols()) 
            if (protocol.equals(TLS_v1_1) || protocol.equals(TLS_v1_2)) 
                return true;
            
        
        return false;
    

编辑:感谢 ademar111190 的 kotlin 实现 (link)

class TLSSocketFactory constructor(
        private val internalSSLSocketFactory: SSLSocketFactory
) : SSLSocketFactory() 

    private val protocols = arrayOf("TLSv1.2", "TLSv1.1")

    override fun getDefaultCipherSuites(): Array<String> = internalSSLSocketFactory.defaultCipherSuites

    override fun getSupportedCipherSuites(): Array<String> = internalSSLSocketFactory.supportedCipherSuites

    override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean) =
            enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))

    override fun createSocket(host: String, port: Int) =
            enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))

    override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int) =
            enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))

    override fun createSocket(host: InetAddress, port: Int) =
            enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))

    override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int) =
            enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))

    private fun enableTLSOnSocket(socket: Socket?) = socket?.apply 
        if (this is SSLSocket && isTLSServerEnabled(this)) 
            enabledProtocols = protocols
        
    

    private fun isTLSServerEnabled(sslSocket: SSLSocket) = sslSocket.supportedProtocols.any  it in protocols 


【讨论】:

嗨,它对我不起作用,知道为什么吗?如果您能提供帮助,那就太好了 我已经使用你的答案来建立 MQTT SSL 连接,它工作得很好!谢谢你。 #UP @StoicaMircea 这实际上将使用无 ssl 进行连接。这是不安全的。你可以在这里使用我的解决方法:***.com/questions/47410689/… @AmirZiarati ,我后来发现你是对的。我也从 TLS_v1_1, TLS_v1_2 切换到 TLS_v1_2, TLS_v1_1 ,首先尝试使用 TLS 1.2 好点 ;) 谢谢。我会根据您的建议编辑我的答案。【参考方案5】:

我对上述答案有一些补充 它实际上是来自 okhttp 的 Jesse Wilson 提到的 hack,正方形 here。根据这个 hack,我不得不将我的 SSLSocketFactory 变量重命名为

private SSLSocketFactory delegate;

这是我的 TLSSocketFactory

public class TLSSocketFactory extends SSLSocketFactory 

private SSLSocketFactory delegate;

public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException 
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, null, null);
    delegate = context.getSocketFactory();


@Override
public String[] getDefaultCipherSuites() 
    return delegate.getDefaultCipherSuites();


@Override
public String[] getSupportedCipherSuites() 
    return delegate.getSupportedCipherSuites();


@Override
public Socket createSocket() throws IOException 
    return enableTLSOnSocket(delegate.createSocket());


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


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


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


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


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


private Socket enableTLSOnSocket(Socket socket) 
    if(socket != null && (socket instanceof SSLSocket)) 
        ((SSLSocket)socket).setEnabledProtocols(new String[] "TLSv1.1", "TLSv1.2");
    
    return socket;


这就是我在 okhttp 和改造中使用它的方式

 OkHttpClient client=new OkHttpClient();
    try 
        client = new OkHttpClient.Builder()
                .sslSocketFactory(new TLSSocketFactory())
                .build();
     catch (KeyManagementException e) 
        e.printStackTrace();
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
    

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

【讨论】:

这是我得到的最有帮助的答案。谢谢,兄弟。【参考方案6】:

正如 OP 所说,TLS v1.1 和 v1.2 协议在 API 级别 16+ 中支持,但默认不启用,我们只需要启用它即可。

此处的示例使用HttpsUrlConnection,而不是HttpUrlConnection。关注https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/,我们可以创建工厂

class MyFactory extends SSLSocketFactory 

    private javax.net.ssl.SSLSocketFactory internalSSLSocketFactory;

    public MyFactory() throws KeyManagementException, NoSuchAlgorithmException 
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        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)) 
            ((SSLSocket)socket).setEnabledProtocols(new String[] "TLSv1.1", "TLSv1.2");
        
        return socket;
    

无论您使用哪个网络库,请确保调用 ((SSLSocket)socket).setEnabledProtocols(new String[] "TLSv1.1", "TLSv1.2");,以便 Socket 启用 TLS 协议。

现在,您可以在 HttpsUrlConnection 中使用它

class MyHttpRequestTask extends AsyncTask<String,Integer,String> 

    @Override
    protected String doInBackground(String... params) 
        String my_url = params[0];
        try 
            URL url = new URL(my_url);
            HttpsURLConnection httpURLConnection = (HttpsURLConnection) url.openConnection();
            httpURLConnection.setSSLSocketFactory(new MyFactory());
            // setting the  Request Method Type
            httpURLConnection.setRequestMethod("GET");
            // adding the headers for request
            httpURLConnection.setRequestProperty("Content-Type", "application/json");


            String result = readStream(httpURLConnection.getInputStream());
            Log.e("My Networking", "We have data" + result.toString());


        catch (Exception e)
            e.printStackTrace();
            Log.e("My Networking", "Oh no, error occurred " + e.toString());
        

        return null;
    

    private static String readStream(InputStream is) throws IOException 
        final BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("US-ASCII")));
        StringBuilder total = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) 
            total.append(line);
        
        if (reader != null) 
            reader.close();
        
        return total.toString();
    

例如

new MyHttpRequestTask().execute(myUrl);

另外,记得将 build.gradle 中的 minSdkVersion 加到 16

minSdkVersion 16

【讨论】:

【参考方案7】:

在android中添加play-services-safetynetbuild.gradle:

implementation 'com.google.android.gms:play-services-safetynet:+'

并将此代码添加到您的MainApplication.java:

@Override
  public void onCreate() 
    super.onCreate();
    upgradeSecurityProvider();
    SoLoader.init(this, /* native exopackage */ false);
  

  private void upgradeSecurityProvider() 
    ProviderInstaller.installIfNeededAsync(this, new ProviderInstallListener() 
      @Override
      public void onProviderInstalled() 

      

      @Override
      public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) 
//        GooglePlayServicesUtil.showErrorNotification(errorCode, MainApplication.this);
        GoogleApiAvailability.getInstance().showErrorNotification(MainApplication.this, errorCode);
      
    );
  

【讨论】:

这解决了我的问题...错误:14077410:SSL 例程:SSL23_GET_SERVER_HELLO:sslv3 警报握手失败 另外,请记住按照文档developer.android.com/training/articles/security-gms-provider中所述处理以下异常GooglePlayServicesRepairableException和GooglePlayServicesNotAvailableException 可以确认这适用于 react-native android 4.x implementation....【参考方案8】:

这对我有用:Enable TLS1.1 and TLS1.2 in Android

【讨论】:

以上是关于如何在 Android 应用程序中启用 TLS 1.2 支持(在 Android 4.1 JB 上运行)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring-boot 上启用 TLS 1.2?

在 Android OS 4.4.2/MobileFirst 混合应用程序上启用 TLS 1.2

如何在 Java 7 中启用 TLS 1.2

在高级设置中启用 TLS 1.0、TLS 1.1 和 TLS 1.2,

如何在 IBM java 1.6 中启用 TLS 1.2?

win2008 Server R2 中IIS启用TLS 1.2