使用SSLSocket的TLS连接在Android OS中很慢

时间:2011-01-19 15:22:33

标签: java android sockets ssl

我正在开发一款使用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 App连接它,握手很快。现在我不知道为什么它在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.");

4 个答案:

答案 0 :(得分:6)

早期版本的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.");
    }
}

答案 1 :(得分:2)

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

SecureRandom在首次使用之前不会自行播种,这就是为什么在调用getSession()之前不会发生延迟

答案 2 :(得分:0)

我做了类似的事情,它比不安全的连接慢。虽然我的情况是https vs http,但SSL / TLS因素会增加交易的速度。

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

答案 3 :(得分:-1)

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