java 使用HttpsURLConnection时如何覆盖Android发送到服务器的密码列表?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16299531/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 22:33:21  来源:igfitidea点击:

How to override the cipherlist sent to the server by Android when using HttpsURLConnection?

javaandroidssljssehttpsurlconnection

提问by AndroidSec

During TLS negotiation, clients send a list of supported ciphers to the server, the server picks one, and encryption starts. I want to change this cipherlist sent to the server by Android, when I'm using HttpsURLConnectionfor communication.

在 TLS 协商期间,客户端将支持的密码列表发送到服务器,服务器选择一个,然后开始加密。当我HttpsURLConnection用于通信时,我想更改由 Android 发送到服务器的这个密码列表。

I know that I can use setSSLSocketFactoryon the HttpsURLConnectionobject to set it up to use a SSLSocketFactory. This is useful when I want to change the trustmanager etc used by the SSLSocketreturned by the SSLSocketFactory.

我知道我可以setSSLSocketFactoryHttpsURLConnection对象上使用以将其设置为使用SSLSocketFactory. 当我想改变由使用的TrustManager等,这是有用SSLSocket的返回SSLSocketFactory

I know that in general this ciphersuite list can be edited using an SSLParametersobject and passing it to SSlsocketor SSLEngineobjects using the methods they provide.

我知道通常可以使用SSLParameters对象编辑此密码套件列表,并使用它们提供的方法将其传递给SSlsocketSSLEngine对象。

BUT the SSLSocketFactorydoes not seem to expose such methods!

但是SSLSocketFactory似乎没有公开这样的方法!

I cannot find a way to change the SSLParametersof the returned SSLSocketobjects created by the SSLSocketFactoryI pass to HttpsURLConnection.

我找不到一种方法来更改由I 传递给SSLParameters的返回SSLSocket对象的。SSLSocketFactoryHttpsURLConnection

What to do?

该怎么办?

I guess this is also relevant to Java in general, not only Android. Maybe there's an OO way to do it (e.g. extend SSLSocketFactoryand provide that to HttpsURLConnection?)

我想这通常也与 Java 相关,而不仅仅是 Android。也许有一种面向对象的方式来做到这一点(例如扩展SSLSocketFactory并提供给HttpsURLConnection?)

回答by ThinkChris

This piece of code is a bit raw. please use with care.

这段代码有点原始。请小心使用。

public class PreferredCipherSuiteSSLSocketFactory extends SSLSocketFactory {


private static final String PREFERRED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA";

private final SSLSocketFactory delegate;

public PreferredCipherSuiteSSLSocketFactory(SSLSocketFactory delegate) {

    this.delegate = delegate;
}

@Override
public String[] getDefaultCipherSuites() {

    return setupPreferredDefaultCipherSuites(this.delegate);
}

@Override
public String[] getSupportedCipherSuites() {

    return setupPreferredSupportedCipherSuites(this.delegate);
}

@Override
public Socket createSocket(String arg0, int arg1) throws IOException,
        UnknownHostException {

    Socket socket = this.delegate.createSocket(arg0, arg1);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3)
        throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
        throws IOException, UnknownHostException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2,
        int arg3) throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

private static String[] setupPreferredDefaultCipherSuites(SSLSocketFactory sslSocketFactory) {

    String[] defaultCipherSuites = sslSocketFactory.getDefaultCipherSuites();

    ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(defaultCipherSuites));
    suitesList.remove(PREFERRED_CIPHER_SUITE);
    suitesList.add(0, PREFERRED_CIPHER_SUITE);

    return suitesList.toArray(new String[suitesList.size()]);
}

private static String[] setupPreferredSupportedCipherSuites(SSLSocketFactory sslSocketFactory) {

    String[] supportedCipherSuites = sslSocketFactory.getSupportedCipherSuites();

    ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(supportedCipherSuites));
    suitesList.remove(PREFERRED_CIPHER_SUITE);
    suitesList.add(0, PREFERRED_CIPHER_SUITE);

    return suitesList.toArray(new String[suitesList.size()]);
}
}

When you want to use it.

当你想使用它时。

            HttpsURLConnection connection = (HttpsURLConnection) (new URL(url))
                .openConnection();
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManager tm[] = {new SSLPinningTrustManager()};
        context.init(null, tm, null);
        SSLSocketFactory preferredCipherSuiteSSLSocketFactory = new PreferredCipherSuiteSSLSocketFactory(context.getSocketFactory());
        connection.setSSLSocketFactory(preferredCipherSuiteSSLSocketFactory);
                    connection.connect();

Thanks you.

谢谢。

回答by Hans-Christoph Steiner

I bundled the technique in @ThinkChris's answer1into a dead simple method call. You can use the NetCipherlibrary to get a modern TLS config when using Android's HttpsURLConnection. NetCipher configures the HttpsURLConnectioninstance to use the best supported TLS version, removes SSLv3 support, and configures the best suite of ciphers for that TLS version. First, add it to your build.gradle:

我将@ThinkChris 的回答1 中的技术捆绑到一个简单的方法调用中。您可以使用NetCipher库获得现代TLS配置使用Android的时候HttpsURLConnection。NetCipher 将HttpsURLConnection实例配置为使用受支持的最佳 TLS 版本,移除 SSLv3 支持,并为该 TLS 版本配置最佳密码套件。首先,将它添加到你的build.gradle

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

Or you can download the netcipher-1.2.jarand include it directly in your app. Then instead of calling:

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

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

Call this:

调用这个:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);

If you want to specifically customize that cipher list, you can check the code there. But most people should not have to think about the cipher list, instead it should use the common best practices by default.

如果你想专门定制那个密码列表,你可以在那里检查代码。但是大多数人不应该考虑密码列表,而是应该默认使用常见的最佳实践。

回答by Peter Costello

This code worked wonders for an unexpected javax.net.ssl.SSLHandshakeException.

此代码为意外的javax.net.ssl.SSLHandshakeException.

Upgrading to jdk1.8.0_92and Oracle JCE unlimited strength policy files did not help, and I was unsuccessful trying to apply specific SSLParametersto the HttpsUrlConnection.

升级到jdk1.8.0_92Oracle JCE 无限强度策略文件并没有帮助,我尝试将特定SSLParametersHttpsUrlConnection.

In particular, attempting to use HttpsUrlConnectionto read https://www.adrbnymellon.comresults in the following error:

特别是,尝试使用HttpsUrlConnection读取会https://www.adrbnymellon.com导致以下错误:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

This website worked OK prior to about 4/15/2016, and then started failing. I believe the failure is caused by the website discontinuing support for SSLv2Helloand SSLv3due to the DROWN vulnerability. See thisfor a great analysis.

该网站在 2016 年 4 月 15 日之前运行正常,然后开始出现故障。我相信失败是由于该网站停止支持造成的SSLv2Hello,并SSLv3由于淹没漏洞。请参阅this以获得很好的分析。

Access to the website started working by modifying the code with changes to just 2 constants:

通过修改代码,仅更改 2 个常量,即可开始访问网站:

private static final String PREFERRED_CIPHER_SUITE = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
SSLContext context = SSLContext.getInstance("TLSv1.2");

I hope this helps someone else.

我希望这对其他人有帮助。