开发者

TLS connection using SSLSocket is slow in Android OS

I'm developing an Android app which uses SSLSocket to connect to a server. 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:

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. The connection itself is fast enough, just the TLS handshake is slow.

Does anybody know if it's normal in Android, or if it is not, how to make it faster?

Thank you.

EDITED on 21.01.11:

I have found out, that the handshake is fast when I connect to another server, for example paypal.com:443.

But I had been connecting to another server before - a .NET service written by me. 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. Now I don't know why i开发者_开发技巧t is fast on iPhone and slow on Android. After the connection is established, the only thing I do in the .NET server is:

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. Apparently, it's doing an unnecessary DNS reverse lookup. 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. Hope it helps.

Here's the link to the 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.");
    }
}


You are using a new SecureRandom per connection, instead of using a single static pre-initialized SecureRandom. Everytime you create a new SecureRandom(), you need to gather entropy for seeding (a slow process).

SecureRandom does not self-seed until it is first used, which is why the delay does not occur until the call to 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.

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. 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.


The problem is most likely in the way the device validates server certificates. Validation can involve contacting third-party for CRLs and OCSP responses. If this happens, it takes time. iPhone probably just doesn't do this (at least by default) which is a security hole BTW.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜