【问题标题】:TLS connection using SSLSocket is slow in Android OS在 Android 操作系统中使用 SSLSocket 的 TLS 连接速度很慢
【发布时间】:2011-06-11 20:13:25
【问题描述】:

我正在开发一个使用 SSLSocket 连接到服务器的 Android 应用程序。这是我正在使用的代码:

// Connect
if (socket == null || socket.isClosed() || !socket.isConnected()) {
    if (socket != null && !socket.isClosed())
        socket.close();
    Log.i(getClass().toString(), "Connecting...");
    if (sslContext == null) {
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom()); 
    }
    SSLSocketFactory socketFactory = sslContext.getSocketFactory();
    socket = (SSLSocket)socketFactory.createSocket(host, port);
    socket.setSoTimeout(20000);
    socket.setUseClientMode(true);
    connected = true;
    Log.i(getClass().toString(), "Connected.");
}

// Secure
if (connected) {
    Log.i(getClass().toString(), "Securing...");
    SSLSession session = socket.getSession();
    secured = session.isValid();
    if (secured) {
        Log.i(getClass().toString(), "Secured.");
    }
    else
        Log.i(getClass().toString(), "Securing failed.");
}

问题是在下面一行中进行 TLS 握手大约需要 5 秒或更多时间:

SSLSession session = socket.getSession();

我做了一个类似的 iPhone 应用程序,握手只需要 1 秒,所以我认为问题不在于我正在连接的服务器,它可能在上面的代码中。连接本身已经足够快了,只是 TLS 握手很慢。

有谁知道在Android中是否正常,如果不正常,如何使其更快?

谢谢。

21.01.11 编辑:

我发现,当我连接到另一台服务器时,握手速度很快,例如 paypal.com:443

但我之前一直连接到另一台服务器 - 我编写的 .NET 服务。正如我之前所说,我不认为问题出在那个服务器上,因为如果我用我的 iPhone 应用程序连接到它,握手会很快。现在我不知道为什么它在 iPhone 上快而在 Android 上慢。建立连接后,我在.NET服务器中唯一要做的就是:

Console.WriteLine("New client connected.");
this.sslStream = new SslStream(tcpClient.GetStream(), true);
this.sslStream.ReadTimeout = 15000;
this.sslStream.WriteTimeout = 15000;

Console.WriteLine("Beginning TLS handshake...");
this.sslStream.AuthenticateAsServer(connection.ServerCertificate, false, SslProtocols.Tls, false);
Console.WriteLine("TLS handshake completed.");

【问题讨论】:

    标签: java android sockets ssl


    【解决方案1】:

    我做过类似的事情,它比不安全的连接要慢。当然,我的情况是 https 与 http,但 SSL/TLS 因素会增加交易速度,但略有不同。

    我有两个相同的应用程序,它们使用相同的协议与同一服务器通信,一个在 android 中,一个在 iPhone 中,都使用 https。当我在 http 中测试它们时,我会看到或多或少相同的响应时间,在我的情况下,在 https 中,iOS 稍微快一些,但不是非常快。

    【讨论】:

    • 是的,我知道 TLS 通常比纯连接慢,但足够快。然而,TLS 握手非常慢,需要 10 秒,而不是我之前所说的 5 秒。
    【解决方案2】:

    您正在为每个连接使用一个新的SecureRandom,而不是使用单个静态预初始化SecureRandom。每次创建新的 SecureRandom() 时,都需要收集熵用于播种(一个缓慢的过程)。

    SecureRandom 在第一次使用之前不会自播,这就是为什么直到调用 getSession() 才会出现延迟

    【讨论】:

    • 感谢您的回复 Jumbogram,您所说的我已经尝试过了,现在我只使用了一个在构造函数中初始化的 SecureRandom 实例。但是无论我连接和断开连接的频率如何,执行 socket.getSession(); 总是需要大约 10 秒;
    • 如果是我,我会尝试捕获数据包并查看减速的位置。每个数据包之间是 1 秒吗?数据包 (n) 和 (n+1) 之间是否有很长的延迟,但其他一切都快吗?像这样的事情。
    • 好主意,我会试试的。我发现,当我连接到另一台服务器时,握手速度很快,例如 paypal.com:443。我现在已经编辑了我的帖子,我想我必须在 .NET 服务器中更改一些内容但我不知道是什么,也许问题出在服务器的证书中,不知道..
    • 感谢实际上将我们的请求时间缩短了一半,但我想在随机时间后重新生成安全随机数也不错!
    【解决方案3】:

    问题很可能出在设备验证服务器证书的方式上。验证可能涉及联系第三方以获取 CRL 和 OCSP 响应。如果发生这种情况,需要时间。 iPhone 可能只是不这样做(至少默认情况下),这是一个安全漏洞 BTW。

    【讨论】:

      【解决方案4】:

      早期版本的 Android SDK 存在错误。显然,它正在进行不必要的 DNS 反向查找。您需要防止这种情况发生。这是一个对我有用的解决方法。过去需要 15 秒,现在需要 0-1 秒。希望对您有所帮助。

      这是 Google issue 的链接。

      boolean connected = false;
      if (socket == null || socket.isClosed() || !socket.isConnected()) {
          if (socket != null && !socket.isClosed()) {
              socket.close();
          }
      
          Log.i(getClass().toString(), "Connecting...");
          messages.getText().append("Connecting...");
          final KeyStore keyStore = KeyStore.getInstance("BKS");
          keyStore.load(getResources().openRawResource(R.raw.serverkey), null);
      
          final KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
          keyManager.init(keyStore, null);
          //keyManager.init(null, null);
      
          final TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
          trustFactory.init(keyStore);
      
          sslContext = SSLContext.getInstance("TLS");
          sslContext.init(keyManager.getKeyManagers(), trustFactory.getTrustManagers(), rnd);
          final SSLSocketFactory delegate = sslContext.getSocketFactory();
          SocketFactory factory = new SSLSocketFactory() {
              @Override
              public Socket createSocket(String host, int port)
                              throws IOException, UnknownHostException {
      
                  InetAddress addr = InetAddress.getByName(host);
                  injectHostname(addr, host);
                  return delegate.createSocket(addr, port);
              }
              @Override
              public Socket createSocket(InetAddress host, int port)
                              throws IOException {
      
                  return delegate.createSocket(host, port);
              }
              @Override
              public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
                              throws IOException, UnknownHostException {
      
                  return delegate.createSocket(host, port, localHost, localPort);
              }
              @Override
              public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
                              throws IOException {
      
                  return delegate.createSocket(address, port, localAddress, localPort);
              }
              private void injectHostname(InetAddress address, String host) {
                  try {
                      Field field = InetAddress.class.getDeclaredField("hostName");
                      field.setAccessible(true);
                      field.set(address, host);
                  } catch (Exception ignored) {
                  }
              }
              @Override
              public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
      
                  injectHostname(s.getInetAddress(), host);
                  return delegate.createSocket(s, host, port, autoClose);
              }
              @Override
              public String[] getDefaultCipherSuites() {
                  return delegate.getDefaultCipherSuites();
              }
              @Override
              public String[] getSupportedCipherSuites() {
                  return delegate.getSupportedCipherSuites();
              }
          };
          socket = (SSLSocket)factory.createSocket("192.168.197.133", 9999);
          socket.setSoTimeout(20000);
          socket.setUseClientMode(true);
          connected = true;
          Log.i(getClass().toString(), "Connected.");
          messages.getText().append("Connected.");
      }
      
      // Secure
      if (connected) {
          Log.i(getClass().toString(), "Securing...");
          messages.getText().append("Securing...");
          SSLSession session = socket.getSession();
          boolean secured = session.isValid();
          if (secured) {
              Log.i(getClass().toString(), "Secured.");
              messages.getText().append("Secured.");
          }
      }
      

      【讨论】:

      • 我一直在到处寻找一种方法来做到这一点(甚至与 android 无关,只需要在 java 项目中禁用反向 DNS 查找)。我不得不为最新的 JDK 修改它——他们将hostName 放在InetAddressHolder 包装类内部更深一层(也无法访问,需要更多反射来遍历它)。仍然 - 谢谢。
      • 谢谢悠悠。这个问题困扰了我很久。
      • 在 Android 6 中使我的后端请求更快,不幸的是,该错误似乎仍然存在于 Android SDK 中。
      猜你喜欢
      • 1970-01-01
      • 2013-11-10
      • 2018-03-09
      • 1970-01-01
      • 1970-01-01
      • 2018-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多