关于Java:使用HttpsURLConnection时,如何覆盖Android发送到服务器的密码列表?

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

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

我知道我可以在HttpsURLConnection对象上使用setSSLSocketFactory将其设置为使用SSLSocketFactory。 当我想更改SSLSocketFactory返回的SSLSocket使用的trustmanager等时,这很有用。

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

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

我找不到改变我传给HttpsURLConnectionSSLSocketFactory创建的返回SSLSocket对象的SSLParameters的方法。

该怎么办?

我猜想这不仅与Android有关,而且与Java有关。 也许有一种OO方法可以做到这一点(例如扩展SSLSocketFactory并将其提供给HttpsURLConnection?)


这段代码有点原始。请谨慎使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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()]);
}
}

当您想使用它时。

1
2
3
4
5
6
7
8
            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();

谢谢。


我将@ThinkChris的answer1中的技术捆绑为无效的简单方法调用。使用Android的HttpsURLConnection时,可以使用NetCipher库获取现代的TLS配置。 NetCipher将HttpsURLConnection实例配置为使用受支持的最佳TLS版本,删除SSLv3支持,并为该TLS版本配置最佳的密码套件。首先,将其添加到您的build.gradle中:

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

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

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

叫这个:

1
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);

如果要专门自定义该密码列表,可以在此处检查代码。但是大多数人不必考虑密码列表,而是默认情况下应使用常见的最佳做法。


这段代码为意外的javax.net.ssl.SSLHandshakeException创造了奇迹。

升级到jdk1.8.0_92和Oracle JCE无限强度策略文件无济于事,并且尝试将特定的SSLParameters应用于HttpsURLConnection失败了。

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

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

该网站在2016年4月15日之前运行正常,然后开始失败。我相信该故障是由于DROWN漏洞导致网站不再支持SSLv2HelloSSLv3所致。对此进行了很好的分析。

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

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

我希望这可以帮助其他人。