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

Posted

技术标签:

【中文标题】使用HttpsURLConnection时如何覆盖Android发送给服务器的密码列表?【英文标题】:How to override the cipherlist sent to the server by Android when using HttpsURLConnection? 【发布时间】:2013-04-24 08:26:56 【问题描述】:

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

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

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

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

我找不到更改返回的SSLSocket 对象的SSLParameters 的方法,该对象由我传递给HttpsURLConnectionSSLSocketFactory 创建。

怎么办?

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

【问题讨论】:

【参考方案1】:

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

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()]);


当你想使用它时。

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

谢谢。

【讨论】:

小精度:这是 javax.net.ssl.SSLSocketFactory 而不是补丁 SSLSocketFactory。 这里的 SSLPinningTrustManager 是什么? “SSLPinningTrustManager”是用户定义的类,因为 Android Studio 无法解决,如果是,那么可以替换什么? @Amritesh 也许SSLPinningTrustManager 指的是this implementation【参考方案2】:

我将@ThinkChris 的 answer1 中的技术捆绑到一个死的简单方法调用中。在使用 Android 的 HttpsURLConnection 时,您可以使用 NetCipher 库来获取现代 TLS 配置。 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);

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

【讨论】:

【参考方案3】:

这段代码为意想不到的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 造成的。请参阅 this 以获得出色的分析。

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

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

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

【讨论】:

以上是关于使用HttpsURLConnection时如何覆盖Android发送给服务器的密码列表?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 HttpsURLConnection 使用证书身份验证?

如何在 Android 上使用 HttpsURLConnection 和 HttpResponseCache 强制缓存?

Android:当密码错误时,带有 Authenticator 的 HttpsUrlConnection 将永远迭代(在 401 响应中)

Android HttpsURLConnection,如何判断响应是不是被缓存?

Java HttpsURLConnection SSLHandshakeException

HttpsURLConnection 身份验证器错误