TLS-соединение с использованием SSLSocket медленное в ОС Android - PullRequest
10 голосов
/ 19 января 2011

Я разрабатываю приложение для Android, которое использует SSLSocket для подключения к серверу. Это код, который я использую:

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

Ответы [ 4 ]

6 голосов
/ 15 декабря 2011

В предыдущих версиях Android SDK произошла ошибка. Видимо, он делает ненужный обратный поиск DNS. Вы должны предотвратить это. Вот обходной путь, который работал для меня. Раньше это занимало 15 секунд, теперь это занимает 0-1 секунд. Надеюсь, это поможет.

Вот ссылка на вопрос 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.");
    }
}
2 голосов
/ 20 января 2011

Вы используете новый SecureRandom для каждого соединения, вместо того, чтобы использовать один статический предварительно инициализированный SecureRandom. Каждый раз, когда вы создаете новый SecureRandom (), вам нужно собирать энтропию для заполнения (медленный процесс).

SecureRandom не выполняет самозагрузку до его первого использования, поэтому задержка не возникает до вызова getSession()

0 голосов
/ 16 апреля 2011

Скорее всего, проблема в том, как устройство проверяет сертификаты сервера. Валидация может включать контакт со сторонними организациями для получения списков отзыва сертификатов и ответов OCSP. Если это произойдет, это займет время. iPhone, вероятно, просто не делает этого (по крайней мере, по умолчанию), что является дырой в безопасности.

0 голосов
/ 19 января 2011

Я сделал что-то похожее на это, и это медленнее, чем незащищенное соединение. Конечно, мой случай был «https против http», и это немного отличается от того, что фактор SSL / TLS добавит медлительности сделке.

У меня есть два идентичных приложения, которые взаимодействуют с одним и тем же протоколом на одном и том же сервере, одно на Android и одно на iPhone, оба используют https. Когда я тестировал их оба в http, я видел более или менее одинаковое время отклика, в https iOS была немного быстрее в моем случае, но не ужасно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...