java 使用 SSLSocket 的 TLS 连接在 Android 操作系统中很慢
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4737019/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
TLS connection using SSLSocket is slow in Android OS
提问by Arthur
I'm developing an Android app which uses SSLSocket to connect to a server. This is the code I'm using:
我正在开发一个使用 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.");
}
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. The connection itself is fast enough, just the TLS handshake is slow.
我制作了一个类似的 iPhone 应用程序,握手只需要 1 秒钟,所以我认为问题不在我连接的服务器上,可能在上面的代码中。连接本身足够快,只是 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:
11 年 1 月 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. 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:
但我之前一直连接到另一台服务器 - 由我编写的 .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.");
回答by Yuyo
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.
早期版本的 Android SDK 存在一个错误。显然,它正在执行不必要的 DNS 反向查找。您需要防止这种情况发生。这是一个对我有用的解决方法。过去需要 15 秒,现在需要 0-1 秒。希望能帮助到你。
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.");
}
}
回答by Jumbogram
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
每个连接,而不是使用单个静态预初始化SecureRandom
. 每次创建新的 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()
回答by blindstuff
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 与 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. 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.
我有两个相同的应用程序,它们使用相同的协议与同一台服务器通信,一个在 android 中,一个在 iPhone 中,都使用 https。当我在 http 中测试它们时,我会看到或多或少相同的响应时间,在我的情况下,在 https iOS 中稍微快一点,但不是很糟糕。
回答by Eugene Mayevski 'Callback
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.
问题很可能在于设备验证服务器证书的方式。验证可能涉及联系第三方以获得 CRL 和 OCSP 响应。如果发生这种情况,则需要时间。iPhone 可能只是不这样做(至少在默认情况下)这是一个安全漏洞 BTW。