可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 it 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.");
回答1:
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."); } }
回答2:
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()
回答3:
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.
回答4:
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.