带有AndroidHttpClient的SSL/TLS协议和密码套件

Jim*_*Dee 8 java ssl https android openssl

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

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

所以:

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

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

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

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

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

A.使用自定义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(第105行)借用了newInstance方法.

关键点:

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" });
Run Code Online (Sandbox Code Playgroud)

然后:

// 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);
Run Code Online (Sandbox Code Playgroud)

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

完整的解决方案是在公共github 仓库.

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

jww*_*jww 1

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

我不相信你能做到这一点AndroidHttpClient。我为强化通道所做的一切(如密码列表、证书固定和公钥固定)都需要在某个地方有一个自定义类,无论是SSLSocketFactory还是X509TrustManager. 那是Java,那是Android。请参阅使用 HttpsURLConnection 时如何覆盖 Android 发送到服务器的密码列表?