使用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
对象编辑此密码套件列表,并使用它们提供的方法将其传递给SSlsocket
或SSLEngine
对象。
但是SSLSocketFactory
似乎没有公开这些方法!
我找不到更改返回的SSLSocket
对象的SSLParameters
的方法,该对象由我传递给HttpsURLConnection
的SSLSocketFactory
创建。
怎么办?
我想这通常也与 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 漏洞而停止支持SSLv2Hello
和SSLv3
造成的。请参阅 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,如何判断响应是不是被缓存?