Java 使用 AndroidHttpClient 的 SSL/TLS 协议和密码套件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18523784/
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
SSL/TLS protocols and cipher suites with the AndroidHttpClient
提问by Jimmy Dee
EDIT: Apologies if my original post was poorly worded. It led to some confusion, represented by comments to the original post. So let me try again:
编辑:如果我原来的帖子措辞不当,我深表歉意。它导致了一些混乱,以对原始帖子的评论为代表。那么让我再试一次:
I started with a question. I wanted to solve a problem on Android, but didn't know how. I spent a lot of time looking around the net for solutions, but found not one single discussion of the matter anywhere. Nevertheless, a number of discussions, including StackOverflow threads, led me to a technique that looked promising. I solved the problem. But the solution was a little involved. So I decided to post the question here, thinking a) there must be a better solution, and hopefully someone will know and post the answere here; or b) maybe this is a good solution, and since I found no discussion of the matter anywhere else on the net, maybe my solution to the problem would be useful to others trying to do the same thing. Either way, the result would be a new contribution to StackOverflow: a question that is not answered elsewhere, with, eventually, the correct answer one way or the other. In fact, StackOverflow even invited me to answer my own question by way of sharing my knowledge when I originally posted it. That was, in fact, part of my motivation. Even the facts of this matter are not collected anywhere that I have found.
我从一个问题开始。我想在 Android 上解决一个问题,但不知道如何解决。我花了很多时间在网上寻找解决方案,但在任何地方都没有找到有关此事的讨论。尽管如此,许多讨论,包括 StackOverflow 线程,让我找到了一种看起来很有前途的技术。我解决了这个问题。但解决方案有点复杂。所以我决定在这里发布问题,认为 a) 必须有更好的解决方案,希望有人知道并在这里发布答案;或者 b) 也许这是一个很好的解决方案,而且由于我在网络上的其他任何地方都没有发现有关此事的讨论,因此我对问题的解决方案可能对其尝试做同样事情的其他人有用。无论哪种方式,结果都将是对 StackOverflow 的新贡献:一个在其他地方没有回答的问题,最终,以一种或另一种方式得到正确答案。事实上,StackOverflow 甚至在我最初发布时邀请我通过分享我的知识来回答我自己的问题。事实上,那是我动机的一部分。甚至在我发现的任何地方都没有收集到这件事的事实。
So:
所以:
Q. When using the AndroidHttpClient to make REST requests via HTTPS, how can I specify which SSL protocols and ciphers to use?
问:使用 AndroidHttpClient 通过 HTTPS 发出 REST 请求时,如何指定要使用的 SSL 协议和密码?
This is important. The point is well taken that there is much that can be done on the server, but there are limits. The same server has to serve browsers, including old ones, as well as other clients. That means the server has to support a broad array of protocols and ciphers. Even within Android, if you have to support a lot of different versions, you're going to have to support a number of different protocols and ciphers.
这个很重要。在服务器上可以做的事情很多,但也有限制。同一台服务器必须为浏览器(包括旧浏览器)以及其他客户端提供服务。这意味着服务器必须支持广泛的协议和密码。即使在 Android 中,如果您必须支持许多不同的版本,您将不得不支持许多不同的协议和密码。
More importantly, by default, OpenSSL honors the client's cipher preference, notthe server's, during the SSL handshake. See this post, for example, which says that you can override that behavior in the client by setting SSL_OP_CIPHER_SERVER_PREFERENCE. It's not entirely clear if this option can even be set on an SSLSocket in Java. Even if it can, you can set the cipher list yourself or tell your client to honor the server's list. Otherwise, you're getting the Android default, whatever it may be for the version you're running on (not the version you link against).
更重要的是,默认情况下,OpenSSL在 SSL 握手期间尊重客户端的密码首选项,而不是服务器的密码首选项。例如,请参阅这篇文章,其中说您可以通过设置 SSL_OP_CIPHER_SERVER_PREFERENCE 在客户端中覆盖该行为。是否甚至可以在 Java 中的 SSLSocket 上设置此选项尚不完全清楚。即使可以,您也可以自己设置密码列表或告诉您的客户端遵守服务器的列表。否则,您将获得 Android 默认值,无论您运行的版本可能是什么(而不是您链接的版本)。
If you take the defaults, the preference list sent by the client to the server by a Jellybean 4.2+ client can be seen here, starting around line 504. The default list of protocols starts around line 620. Though Jellybean 4.2+ includes support for OpenSSL 1.0.1, particularly TLSv1.1 and TLSv1.2, these protocols are not enabled by default. If you do not do something like what I've done, you cannot take advantage of TLSv1.2 support, despite the fact that support for TLSv1.2 is advertised on recent versions of Android. And the details vary quite a bit as you go back through previous Android versions. At least, you may wish to take a close look at the defaults on all supported versions and see what your client is actually doing. You might be surprised.
如果采用默认值,则可以在此处看到 Jellybean 4.2+ 客户端发送到服务器的首选项列表,从第 504 行开始。默认协议列表从第 620 行开始。虽然 Jellybean 4.2+ 包括对 OpenSSL 的支持1.0.1,尤其是 TLSv1.1 和 TLSv1.2,这些协议默认不启用。如果你不做我做过的事情,你就不能利用 TLSv1.2 支持,尽管在最新版本的 Android 上宣传了对 TLSv1.2 的支持。当您回顾以前的 Android 版本时,细节会有很大差异。至少,您可能希望仔细查看所有支持版本的默认设置,看看您的客户端实际在做什么。你可能会感到惊讶。
There is lots more you can say about support for various protocols and ciphers. The point is, there can at times be a need to change these settings in a client.
关于对各种协议和密码的支持,您还有很多话要说。关键是,有时可能需要更改客户端中的这些设置。
A. Use a custom SSLSocketFactory
A. 使用自定义 SSLSocketFactory
This worked well for me, and in the end, it was not very much code. But it was a little thorny for a couple of reasons:
这对我来说效果很好,最后,它不是很多代码。但由于以下几个原因,它有点棘手:
- There are two different SSLSocketFactory classes. The client needs an org.apache.http.conn.ssl.SSLSocketFactory, but OpenSSL returns a javax.net.ssl.SSLSocketFactory. This is definitely confusing. I used delegation to make the one call the other, without much problem.
- Watch out for the difference between the OpenSSLContextImpl and the SSLContextImpl. One just wraps the other, but they are not interchangeable. When I used the SSLContextImpl.engineGetSocketFactory method—I forget what exactly happened, but something quietly failed. Be sure to use an OpenSSLContextImpl to get your socket factory, not an SSLContextImpl. You might also be able to use javax.net.ssl.SSLSocketFactory.getDefault(), but I'm not sure.
- You cannot easily subclass AndroidHttpClient, because its constructor is private. This is unfortunate, since it provides some other nice goodies, like making sure you shut it down properly instead of leaking resources. The DefaultHttpClient works just fine. I borrowed the newInstance method from AndroidHttpClient (around line 105).
- 有两个不同的 SSLSocketFactory 类。客户端需要一个 org.apache.http.conn.ssl.SSLSocketFactory,但 OpenSSL 返回一个 javax.net.ssl.SSLSocketFactory。这绝对是令人困惑的。我使用委托让一个调用另一个,没有太大问题。
- 注意 OpenSSLContextImpl 和 SSLContextImpl 之间的区别。一个只是包裹另一个,但它们不能互换。当我使用 SSLContextImpl.engineGetSocketFactory 方法时——我忘记了到底发生了什么,但有些事情悄悄地失败了。请务必使用 OpenSSLContextImpl 来获取您的套接字工厂,而不是 SSLContextImpl。您也许还可以使用 javax.net.ssl.SSLSocketFactory.getDefault(),但我不确定。
- 您不能轻松地将 AndroidHttpClient 子类化,因为它的构造函数是私有的。这是不幸的,因为它提供了其他一些不错的好处,例如确保正确关闭它而不是泄漏资源。DefaultHttpClient 工作得很好。我从 AndroidHttpClient 借用了 newInstance 方法(大约在第 105 行)。
The key points:
关键点:
public class SecureSocketFactory extends SSLSocketFactory {
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
// order should not matter here; the server should select the highest
// one from this list that it supports
s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });
// order matters here; specify in preference order
s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });
Then:
然后:
// when creating client
HttpParams params;
SchemeRegistry schemeRegistry = new SchemeRegistry();
// use custom socket factory for https
SSLSocketFactory sf = new SecureSocketFactory();
schemeRegistry.register(new Scheme("https", sf, 443));
// use the default for http
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient client = new DefaultHttpClient(manager, params);
Below Android 3.0 (Honeycomb/SDK 11), the supported cipher choices become more limited, and there's less motivation to override the defaults. On FROYO/SDK 8, my SecureSocketFactory blows up for some reason, and the jury is out on 10. But it seems to work for 11 upward just fine.
在 Android 3.0 (Honeycomb/SDK 11) 以下,支持的密码选择变得更加有限,并且没有动力去覆盖默认值。在 FROYO/SDK 8 上,我的 SecureSocketFactory 由于某种原因爆炸了,陪审团在 10 上被淘汰。但它似乎在 11 向上工作就好了。
The full solution is in a public github repo.
完整的解决方案位于公共 github repo 中。
Another solution might be to use an HttpsUrlConnection, which makes it easy to use a custom socket factory, but I imagine you'll probably lose even more of the convenience of the high-level HTTP client. I don't have any experience with HttpsUrlConnection.
另一种解决方案可能是使用 HttpsUrlConnection,这样可以轻松使用自定义套接字工厂,但我想您可能会失去高级 HTTP 客户端的更多便利。我对 HttpsUrlConnection 没有任何经验。
采纳答案by jww
When using the AndroidHttpClient to make REST requests via HTTPS, how can I specify which SSL protocols and ciphers to use?
使用 AndroidHttpClient 通过 HTTPS 发出 REST 请求时,如何指定要使用的 SSL 协议和密码?
I don't believe you can do it with AndroidHttpClient
. Everything I've done to harden the channel (like cipher lists, certificate pinning, and public key pinning) required a custom class somewhere, whether it was SSLSocketFactory
or X509TrustManager
. That's Java and that's Android. See How to override the cipherlist sent to the server by Android when using HttpsURLConnection?.
我不相信你可以用AndroidHttpClient
. 我所做的一切硬化通道(如密码列表,证书钉扎,和公钥钉扎)所需要的自定义类的地方,无论是SSLSocketFactory
还是X509TrustManager
。那是Java,那是Android。请参阅如何在使用 HttpsURLConnection 时覆盖 Android 发送到服务器的密码列表?.