简体   繁体   English

在Android OS中,使用SSLSocket进行TLS连接很慢

[英]TLS connection using SSLSocket is slow in Android OS

I'm developing an Android app which uses SSLSocket to connect to a server. 我正在开发一个使用SSLSocket连接到服务器的Android应用。 This is the code I'm using: 这是我正在使用的代码:

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

The problem is that it takes about 5 seconds or event more to do the TLS handshake in the line below: 问题在于,在下面的行中进行TLS握手大约需要5秒钟或更多时间:

SSLSession session = socket.getSession();

I have made a similar iPhone app, the handshake takes just 1 second there, so I think the problem is not in the server I'm connecting to, it's maybe in the code above. 我制作了一个类似的iPhone应用程序,握手在那里只花了1秒钟,所以我认为问题不在我连接的服务器上,可能是在上面的代码中。 The connection itself is fast enough, just the TLS handshake is slow. 连接本身足够快,只是TLS握手很慢。

Does anybody know if it's normal in Android, or if it is not, how to make it faster? 有人知道它在Android中是否正常吗?如何使其更快?

Thank you. 谢谢。

EDITED on 21.01.11: 修改于21.01.11:

I have found out, that the handshake is fast when I connect to another server, for example paypal.com:443 . 我发现,当我连接到另一台服务器(例如paypal.com:443)时,握手很快。

But I had been connecting to another server before - a .NET service written by me. 但是我之前一直连接到另一台服务器-我编写的.NET服务。 As I had said before, I did not think the problem was in that server because if I connect to it with my iPhone App the handshake is fast. 正如我之前所说,我不认为问题出在该服务器上,因为如果使用我的iPhone App连接该服务器,则握手速度很快。 Now I don't know why it is fast on iPhone and slow on Android. 现在我不知道为什么它在iPhone上速度快而在Android上速度慢。 After the connection is established, the only thing I do in the .NET server is: 建立连接后,我在.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.");

There was a bug on earlier versions of the Android SDK. 早期版本的Android SDK中存在错误。 Apparently, it's doing an unnecessary DNS reverse lookup. 显然,它正在执行不必要的DNS反向查找。 You need to prevent this from happening. 您需要防止这种情况的发生。 Here's a workaround that worked for me. 这是一种对我有用的解决方法。 It used to take 15 seconds, now it takes 0-1 seconds. 过去需要15秒,现在需要0-1秒。 Hope it helps. 希望能帮助到你。

Here's the link to the Google issue . 这是Google 问题的链接。

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

You are using a new SecureRandom per connection, instead of using a single static pre-initialized SecureRandom . 您正在为每个连接使用新的SecureRandom ,而不是使用单个静态的预初始化SecureRandom Everytime you create a new SecureRandom(), you need to gather entropy for seeding (a slow process). 每次创建新的SecureRandom()时,都需要收集用于播种的熵(一个缓慢的过程)。

SecureRandom does not self-seed until it is first used, which is why the delay does not occur until the call to getSession() 在首次使用SecureRandom之前,它不会自种,这就是为什么直到调用getSession()才发生延迟的原因

I have done something similar to this and it is slower than an unsecured connection. 我做了类似的事情,它比不安全的连接要慢。 Granted my case was https vs http and it is a little different the SSL/TLS factor will add slowness to the deal. 当然,我的案例是https vs http,但是SSL / TLS因素会稍微增加交易的速度。

I have two identical apps that comunicate with the same protocol to the same server, one in android and one in iPhone, both using https. 我有两个相同的应用程序,它们使用相同的协议与同一台服务器通信,一个在android中,一个在iPhone中,都使用https。 When I tested them both in http I would see more or less the same response time, in https iOS was slightly faster in my case, but not terribly. 当我在http中测试它们两者时,我会看到大致相同的响应时间,在我看来,https iOS的响应速度稍快,但并不十分糟糕。

The problem is most likely in the way the device validates server certificates. 问题很可能是设备验证服务器证书的方式。 Validation can involve contacting third-party for CRLs and OCSP responses. 验证可能涉及与第三方联系以获取CRL和OCSP响应。 If this happens, it takes time. 如果发生这种情况,则需要时间。 iPhone probably just doesn't do this (at least by default) which is a security hole BTW. iPhone可能只是不这样做(至少默认情况下),这是一个安全漏洞。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM