使用AndroidHttpClient的SSL / TLS协议和密码套件

时间:2013-08-30 02:03:33

标签: java android ssl https openssl

编辑:如果我原来的帖子措辞不好,请道歉。它引起了一些混乱,以对原始帖子的评论为代表。那么让我再试一次:

我开始提问。我想在Android上解决问题,但不知道如何解决。我花了很多时间在网上寻找解决方案,但在任何地方都找不到关于此问题的单一讨论。尽管如此,包括StackOverflow线程在内的一些讨论让我看到了一种前景看好的技术。我解决了这个问题。但解决方案有点牵扯。所以我决定在这里发布这个问题,思考a)必须有一个更好的解决方案,并希望有人知道并在这里发布答案;或者b)也许这是一个很好的解决方案,因为我发现网上其他任何地方都没有讨论过这个问题,也许我对这个问题的解决方案对于其他试图做同样事情的人来说也是有用的。无论哪种方式,结果都将是对StackOverflow的新贡献:一个在其他地方无法回答的问题,最终会以某种方式回答正确答案。实际上,StackOverflow甚至邀请我在我最初发布它时通过分享我的知识来回答我自己的问题。事实上,那是我动机的一部分。即使是这件事的事实也没有收集到我找到的任何地方。

所以:

问。使用AndroidHttpClient通过HTTPS发出REST请求时,如何指定要使用的SSL协议和密码?

这很重要。很明显,服务器上可以做很多事情,但是有限制。同一台服务器必须提供浏览器,包括旧浏览器以及其他客户端。这意味着服务器必须支持广泛的协议和密码。即使在Android中,如果你必须支持许多不同的版本,你将不得不支持许多不同的协议和密码。

更重要的是,默认情况下,OpenSSL在SSL握手期间尊重客户端的密码首选项而不是服务器。例如,请参阅此post,其中说明您可以通过设置SSL_OP_CIPHER_SERVER_PREFERENCE来覆盖客户端中的该行为。如果甚至可以在Java中的SSLSocket上设置此选项,则不完全清楚。即使可以,您也可以自己设置密码列表或告诉客户尊重服务器列表。否则,您将获得Android默认设置,无论您运行的版本是什么(不是您链接的版本)。

如果采用默认设置,Jellybean 4.2+客户端从客户端发送到服务器的首选项列表可以看到here,从第504行开始。默认的协议列表从第620行开始。 Jellybean 4.2+包括对OpenSSL 1.0.1的支持,特别是TLSv1.1和TLSv1.2,默认情况下不启用这些协议。如果你没有像我所做的那样做,你就无法利用TLSv1.2的支持,尽管在最新版本的Android上宣传了对TLSv1.2的支持。回顾以前的Android版本,细节会有很大差异。至少,您可能希望仔细查看所有受支持版本的默认值,并查看客户端实际执行的操作。你可能会感到惊讶。

关于对各种协议和密码的支持,你可以说更多。关键是,有时可能需要在客户端中更改这些设置。

:一种。使用自定义SSLSocketFactory

这对我来说效果很好,最后代码不是很多。但由于以下几个原因,这有点棘手:

  • 有两种不同的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行)。

关键点:

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" });

然后:

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

在Android 3.0(Honeycomb / SDK 11)下,支持的密码选择变得更加有限,并且覆盖默认值的动机也减少了。在FROYO / SDK 8上,我的SecureSocketFactory由于某种原因而爆炸,并且陪审团已经出现在10.但它似乎适用于11向上就好了。

完整的解决方案是在公共github repo

另一个解决方案可能是使用HttpsUrlConnection,这使得使用自定义套接字工厂变得容易,但我想你可能会失去更多高级HTTP客户端的便利性。我对HttpsUrlConnection没有任何经验。

1 个答案:

答案 0 :(得分:1)

  

使用AndroidHttpClient通过HTTPS发出REST请求时,如何指定要使用的SSL协议和密码?

我认为你不能用AndroidHttpClient做到这一点。我为加强频道所做的一切(如密码列表,证书固定和公钥固定)都需要在某个地方使用自定义类,无论是SSLSocketFactory还是X509TrustManager。那是Java,那是Android。请参阅How to override the cipherlist sent to the server by Android when using HttpsURLConnection?