如何在Android中禁用HttpsUrlConnection的SSLv3?

19
我们在Android中编写了客户端应用程序,使用HttpsUrlConnection API连接https服务器。由于Poodle漏洞,我们需要在调用任何请求时从启用的协议列表中禁用SSLv3。
我们遵循了Oracle捕获的指南并在调用URL连接之前添加了以下行。
java.lang.System.setProperty("https.protocols", "TLSv1");

这个解决方案适用于普通的Java程序。
当尝试连接只支持SSLv3协议的服务器时,我们遇到了SSLHandShakeException问题。 但是需要注意的是:同样的修复方法在Android上不起作用。我是否漏掉了什么或者应该尝试另一种方法来解决Android的问题?请给出建议。

你尝试过创建自己的SSLContext并将其SocketFactory传递给HttpsUrlConnection吗? - Selvin
你能分享一个仅支持 SSLv3 连接的服务 URL 吗? - Hardik Muliya
嗨Selvin,我还没有尝试过SSLContext。我会尝试并更新你。如果有任何快速指针/代码片段,那就太好了。 - user1375399
8个回答

47

我通过使用Wireshark分析数据包找到了解决方案。我发现,在建立一个安全连接时,Android会从TLSv1回退到SSLv3。这是在Android版本小于4.4中的一个错误,可以通过从已启用协议列表中删除SSLv3协议来解决。我创建了一个名为NoSSLv3SocketFactory.java的自定义socketFactory类。请使用它来创建一个socketfactory。

/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;


public class NoSSLv3SocketFactory extends SSLSocketFactory{
    private final SSLSocketFactory delegate;

public NoSSLv3SocketFactory() {
    this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}

public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
    this.delegate = delegate;
}

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

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

private Socket makeSocketSafe(Socket socket) {
    if (socket instanceof SSLSocket) {
        socket = new NoSSLv3SSLSocket((SSLSocket) socket);
    }
    return socket;
}

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

@Override
public Socket createSocket(String host, int port) throws IOException {
    return makeSocketSafe(delegate.createSocket(host, port));
}

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

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

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

private class NoSSLv3SSLSocket extends DelegateSSLSocket {

    private NoSSLv3SSLSocket(SSLSocket delegate) {
        super(delegate);

    }

    @Override
    public void setEnabledProtocols(String[] protocols) {
        if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {

            List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
            if (enabledProtocols.size() > 1) {
                enabledProtocols.remove("SSLv3");
                System.out.println("Removed SSLv3 from enabled protocols");
            } else {
                System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
            }
            protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
        }

        super.setEnabledProtocols(protocols);
    }
}

public class DelegateSSLSocket extends SSLSocket {

    protected final SSLSocket delegate;

    DelegateSSLSocket(SSLSocket delegate) {
        this.delegate = delegate;
    }

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

    @Override
    public String[] getEnabledCipherSuites() {
        return delegate.getEnabledCipherSuites();
    }

    @Override
    public void setEnabledCipherSuites(String[] suites) {
        delegate.setEnabledCipherSuites(suites);
    }

    @Override
    public String[] getSupportedProtocols() {
        return delegate.getSupportedProtocols();
    }

    @Override
    public String[] getEnabledProtocols() {
        return delegate.getEnabledProtocols();
    }

    @Override
    public void setEnabledProtocols(String[] protocols) {
        delegate.setEnabledProtocols(protocols);
    }

    @Override
    public SSLSession getSession() {
        return delegate.getSession();
    }

    @Override
    public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
        delegate.addHandshakeCompletedListener(listener);
    }

    @Override
    public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
        delegate.removeHandshakeCompletedListener(listener);
    }

    @Override
    public void startHandshake() throws IOException {
        delegate.startHandshake();
    }

    @Override
    public void setUseClientMode(boolean mode) {
        delegate.setUseClientMode(mode);
    }

    @Override
    public boolean getUseClientMode() {
        return delegate.getUseClientMode();
    }

    @Override
    public void setNeedClientAuth(boolean need) {
        delegate.setNeedClientAuth(need);
    }

    @Override
    public void setWantClientAuth(boolean want) {
        delegate.setWantClientAuth(want);
    }

    @Override
    public boolean getNeedClientAuth() {
        return delegate.getNeedClientAuth();
    }

    @Override
    public boolean getWantClientAuth() {
        return delegate.getWantClientAuth();
    }

    @Override
    public void setEnableSessionCreation(boolean flag) {
        delegate.setEnableSessionCreation(flag);
    }

    @Override
    public boolean getEnableSessionCreation() {
        return delegate.getEnableSessionCreation();
    }

    @Override
    public void bind(SocketAddress localAddr) throws IOException {
        delegate.bind(localAddr);
    }

    @Override
    public synchronized void close() throws IOException {
        delegate.close();
    }

    @Override
    public void connect(SocketAddress remoteAddr) throws IOException {
        delegate.connect(remoteAddr);
    }

    @Override
    public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
        delegate.connect(remoteAddr, timeout);
    }

    @Override
    public SocketChannel getChannel() {
        return delegate.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return delegate.getInetAddress();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return delegate.getInputStream();
    }

    @Override
    public boolean getKeepAlive() throws SocketException {
        return delegate.getKeepAlive();
    }

    @Override
    public InetAddress getLocalAddress() {
        return delegate.getLocalAddress();
    }

    @Override
    public int getLocalPort() {
        return delegate.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return delegate.getLocalSocketAddress();
    }

    @Override
    public boolean getOOBInline() throws SocketException {
        return delegate.getOOBInline();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        return delegate.getOutputStream();
    }

    @Override
    public int getPort() {
        return delegate.getPort();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return delegate.getReceiveBufferSize();
    }

    @Override
    public SocketAddress getRemoteSocketAddress() {
        return delegate.getRemoteSocketAddress();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return delegate.getReuseAddress();
    }

    @Override
    public synchronized int getSendBufferSize() throws SocketException {
        return delegate.getSendBufferSize();
    }

    @Override
    public int getSoLinger() throws SocketException {
        return delegate.getSoLinger();
    }

    @Override
    public synchronized int getSoTimeout() throws SocketException {
        return delegate.getSoTimeout();
    }

    @Override
    public boolean getTcpNoDelay() throws SocketException {
        return delegate.getTcpNoDelay();
    }

    @Override
    public int getTrafficClass() throws SocketException {
        return delegate.getTrafficClass();
    }

    @Override
    public boolean isBound() {
        return delegate.isBound();
    }

    @Override
    public boolean isClosed() {
        return delegate.isClosed();
    }

    @Override
    public boolean isConnected() {
        return delegate.isConnected();
    }

    @Override
    public boolean isInputShutdown() {
        return delegate.isInputShutdown();
    }

    @Override
    public boolean isOutputShutdown() {
        return delegate.isOutputShutdown();
    }

    @Override
    public void sendUrgentData(int value) throws IOException {
        delegate.sendUrgentData(value);
    }

    @Override
    public void setKeepAlive(boolean keepAlive) throws SocketException {
        delegate.setKeepAlive(keepAlive);
    }

    @Override
    public void setOOBInline(boolean oobinline) throws SocketException {
        delegate.setOOBInline(oobinline);
    }

    @Override
    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
        delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(int size) throws SocketException {
        delegate.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(boolean reuse) throws SocketException {
        delegate.setReuseAddress(reuse);
    }

    @Override
    public synchronized void setSendBufferSize(int size) throws SocketException {
        delegate.setSendBufferSize(size);
    }

    @Override
    public void setSoLinger(boolean on, int timeout) throws SocketException {
        delegate.setSoLinger(on, timeout);
    }

    @Override
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        delegate.setSoTimeout(timeout);
    }

    @Override
    public void setTcpNoDelay(boolean on) throws SocketException {
        delegate.setTcpNoDelay(on);
    }

    @Override
    public void setTrafficClass(int value) throws SocketException {
        delegate.setTrafficClass(value);
    }

    @Override
    public void shutdownInput() throws IOException {
        delegate.shutdownInput();
    }

    @Override
    public void shutdownOutput() throws IOException {
        delegate.shutdownOutput();
    }

    @Override
    public String toString() {
        return delegate.toString();
    }

    @Override
    public boolean equals(Object o) {
        return delegate.equals(o);
    }
}
}

连接时请像这样使用此类:

SSLContext sslcontext = SSLContext.getInstance("TLSv1");

            sslcontext.init(null,
                    null,
                    null);
            SSLSocketFactory NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());

            HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
            l_connection = (HttpsURLConnection) l_url.openConnection();
            l_connection.connect();

更新:

现在,正确的解决方案是使用Google Play服务安装较新的安全提供程序。

    ProviderInstaller.installIfNeeded(getApplicationContext());

这实际上为您的应用程序提供了访问更新版本的OpenSSL和Java Security Provider的权限,并包括对SSLEngine中TLSv1.2的支持。安装新提供程序后,您可以按通常方式创建支持SSLv3、TLSv1、TLSv1.1和TLSv1.2的SSLEngine:

    SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
    sslContext.init(null, null, null);
    SSLEngine engine = sslContext.createSSLEngine();

或者您可以使用engine.setEnabledProtocols限制启用的协议。

不要忘记添加以下依赖项(最新版本在此处找到):

compile 'com.google.android.gms:play-services-auth:11.8.0'

了解更多信息,请查看此链接


谢谢!您的解决方案非常完美,可以在低于20版本的手机上使用okhttp 2.3.0启用TLS v1.2。 - prijupaul
1
你绝对救了我的一天!我遇到了这个问题,并尝试了很多不同的解决方案,但都没有起作用(比如指定TLSv1.1等)。你的解决方案——从支持的协议中移除SSLv3,效果很好,并且不会在任何其他设备上破坏。有趣的是,它之前在所有设备上都能正常工作,只有三星Galaxy S3 Mini不能。 - byemute
2
参考以下问题中的错误:https://code.google.com/p/android/issues/detail?id=78187 - bcoughlan
1
我只希望几天前就能找到你的答案!谢谢! - Leo supports Monica Cellio
1
我刚刚使用了 ProviderInstaller.installIfNeeded(getApplicationContext()); 这个方法,问题得到了解决。谢谢。 - tsig
显示剩余5条评论

15

受 Bhavit S. Sengar 的答案的启发,它将该技术捆绑到了一个非常简单的方法调用中。您可以使用NetCipher库来获取现代 TLS 配置,当使用 Android 的 HttpsURLConnection 时。NetCipher 配置 HttpsURLConnection 实例以使用最佳支持的 TLS 版本,删除 SSLv3 支持,并为该 TLS 版本配置最佳密码套件。首先,将其添加到您的build.gradle中:

compile 'info.guardianproject.netcipher:netcipher:1.2'

或者您可以下载 netcipher-1.2.jar 并直接将其包含在您的应用程序中。然后,不是调用:

HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();

调用此函数:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);

如果我想使用OKHttp怎么办? - IgorGanapolsky
我尝试了这个,我得到了净密码sslsocketfactory,但是在调试器中检查时仍然显示SSLv3协议。这是在Android 5.1上的情况。我尝试过的所有方法,包括我的自定义sslsocketfactory类,在其中禁用SSLv3或仅启用TLS...都导致了这种情况;SSLv3协议仍然显示。 - wkhatch
自从第一个版本以来,Android已经支持TLS v1.0,因此禁用了SSLv2和SSLv3支持。 android-20是第一个支持TLS v1.1和v1.2的Android版本。 https://developer.android.com/reference/javax/net/ssl/SSLEngine.html - Hans-Christoph Steiner
它支持Apache HttpClient吗? - Pallavi
是的,您可以在Android中使用ch.boye HttpClient或Apache HttpClient。 - Hans-Christoph Steiner
显示剩余5条评论

6
起初我尝试了Bhavit S. Sengar的答案,在大多数情况下都有效。但是有时即使从启用的协议中删除了SSLv3协议,Android 4.4.4设备上仍然存在问题。因此,Hans-Christoph Steiner的NetCipher库在我测试过后是解决这个问题的完美方案。
我们使用jsoup在不同的服务器上进行大量的网络爬虫操作,因此我们无法设置HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);。如果您使用OkHttp,我认为这应该是相同的问题。
我们找到的最佳解决方案是在静态块中将来自NetCipher的info.guardianproject.netcipher.client.TlsOnlySocketFactory设置为DefaultSSLSocketFactory。这样它就会在我们应用程序的整个运行时期内生效:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);

如果您想使用 trustAllCertificates 来查看完整的详细信息,可以在此处进行。


这里不应该是 TLSv1.2 吗? - source.rar
非常感谢你,兄弟!你让我的一天变得美好! - Fayçal
仍然出现SSLHandshakeException。 - Pradeep Bishnoi

4

如果服务器启用了SSLv3,则使用此代码片段将无法进行握手。

        SocketFactory sf = SSLSocketFactory.getDefault();
        SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443);
        socket.setEnabledProtocols(new String[] { "TLSv1"});
        socket.startHandshake();

我仍然收到一个 javax.net.ssl.SSLProtocolException: SSL handshake failed 错误。 - IgorGanapolsky
应该使用TLSv1.2。 - savvisingh

0
 SSLContext sslContext = SSLContext.getInstance("TLSv1");
                sslContext.init(null, null, null);
                SSLSocketFactory socketFactory = sslContext.getSocketFactory();
                            httpURLConnection.setSSLSocketFactory(socketFactory);

HttpsURLConnection使用TLS创建安全失败,Android实现将退回到SSLV3连接。

请参考此链接:http://code.google.com/p/android/issues/detail?id=78431


为什么你用全部为空的参数初始化sslContext? - IgorGanapolsky

0

连接到https服务器,我们需要客户端在握手过程中提供证书。一年前,我使用自签名证书以以下方式解决了类似的问题-

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpsTrustManager implements X509TrustManager {

private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};

@Override
public void checkClientTrusted(
        java.security.cert.X509Certificate[] x509Certificates, String s)
        throws java.security.cert.CertificateException {

}

@Override
public void checkServerTrusted(
        java.security.cert.X509Certificate[] x509Certificates, String s)
        throws java.security.cert.CertificateException {

}

public boolean isClientTrusted(X509Certificate[] chain) {
    return true;
}

public boolean isServerTrusted(X509Certificate[] chain) {
    return true;
}

@Override
public X509Certificate[] getAcceptedIssuers() {
    return _AcceptedIssuers;
}

public static void allowAllSSL() {
    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            return true;
        }

    });

    SSLContext context = null;
    if (trustManagers == null) {
        trustManagers = new TrustManager[]{new HttpsTrustManager()};
    }

    try {
        context = SSLContext.getInstance("TLS");
        context.init(null, trustManagers, new SecureRandom());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }

    HttpsURLConnection.setDefaultSSLSocketFactory(context
            .getSocketFactory());
}

}

在HttpsUrlConnection之前的客户端使用方法

HttpsTrustManager.allowAllSSL();

希望它能够工作 :)

0
其实我们不需要禁用SSLV3或TLSV1.0,在Android<5设备中,我们只需要启用TLSV1.1或TLSv1.2即可。
问题在于,Android<5默认情况下未启用TLSv1.1和TLSv1.2,要使用这些最新的安全协议连接,我们必须在Android<5设备上启用它们。
这个解决方案解决了我的问题:https://dev59.com/PI7ea4cB1Zd3GeqPGdaD#45853669

0

在Android上使用PlayService publisher client libraries时,运行sample时遇到了同样的问题。

通过@bhavit-s-sengar的上面的答案解决了这个问题。还必须将AndroidPublisherHelper.newTrustedTransport()更改为以下内容:

SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
//  NoSSLv3SocketFactory is @bhavit-s-sengar's https://dev59.com/8l8d5IYBdhLWcg3wlzD6#29946540
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());

NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder();
netTransportBuilder.setSslSocketFactory(noSSLv3Factory);
HTTP_TRANSPORT = netTransportBuilder.build();

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