为什么 SSL 服务器套接字连接会在 Java 中阻塞,而非 SSL 服务器套接字不会?
Why does an SSL Server Socket connection block in Java whereas a non SSL Server Socket does not?
考虑一个线程上的非 SSL 套接字服务器和客户端的以下代码:
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerClient {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(0); // open a random free port.
Socket c = new Socket(ss.getInetAddress(), ss.getLocalPort());
Socket s = ss.accept();
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = c.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(s.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
c.close();
ss.close();
}
}
这会产生以下输出:
writing to stream
reading from stream
Hello World!
此进程打开了一个服务器套接字 - 与客户端套接字连接。将数据传递到套接字,然后关闭。传递数据没有问题。
考虑使用 SSL 套接字证明观点的版本:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Logger;
public class SSLServerClient {
private static Logger log = Logger.getLogger("InfoLogging");
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
log.info("Server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
log.info("new client");
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
这给出了以下输出:
Nov 21, 2018 10:23:51 PM com.gamble.ssl.SSLServerClient main INFO: Server started
Nov 21, 2018 10:23:52 PM com.gamble.ssl.SSLServerClient main INFO: new client
writing to stream
即它在服务器套接字启动时阻塞。
我的问题是:为什么 SSL 服务器套接字连接会在 Java 中阻塞,而非 SSL 服务器套接字则不会?
"flip to plaintext?" 是什么意思?
这是您正在做的事情:
- 创建 SSL 服务器套接字
- 创建连接到 SSL 服务器套接字的普通套接字
- 从客户端发送一些数据
- 正在服务器端读取一些数据
现在,SSL 套接字需要加密数据传输。但是您未发送的数据未加密,因此它不是有效的 SSL 并且会引发异常 - 正如它所说,"Unrecognized SSL message" 因为您没有发送有效的 SSL 消息,而 "plaintext connection?" 是一个提示关于可能出错的地方。
您必须在连接的两端都使用 SSL,否则它们之间无法正常通信。
问题的简单答案是:
Because an SSL Handshake is asynchronous and the non SSL socket doesn't have to do that step.
好的 - 原始 SSL 代码有两个问题:
没有信任库:
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
因为握手是同步的 - 您必须在不同的线程上启动客户端。即:
(new Thread() {
public void run() {
// do stuff
}
}).start();
因此代码如下所示:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
public class SSLServerClient {
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
System.out.println("--server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
System.out.println("--new client");
final byte[] bytes = "--Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("--Gotten output stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
(new Thread() {
public void run() {
System.out.println("--reading from stream");
int len = 0;
try {
len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("--writing to stream");
out.write(bytes.length);
System.out.println("--writing to stream - length");
out.write(bytes);
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
输出看起来像
--server started
--new client
--Gotten output stream
--writing to stream
--reading from stream
--writing to stream - length
--Hello World!
Process finished with exit code 0
完成
(注意我在输出中添加了前导 --
以帮助读取 SSL 调试输出。
考虑一个线程上的非 SSL 套接字服务器和客户端的以下代码:
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerClient {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(0); // open a random free port.
Socket c = new Socket(ss.getInetAddress(), ss.getLocalPort());
Socket s = ss.accept();
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = c.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(s.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
c.close();
ss.close();
}
}
这会产生以下输出:
writing to stream
reading from stream
Hello World!
此进程打开了一个服务器套接字 - 与客户端套接字连接。将数据传递到套接字,然后关闭。传递数据没有问题。
考虑使用 SSL 套接字证明观点的版本:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Logger;
public class SSLServerClient {
private static Logger log = Logger.getLogger("InfoLogging");
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
log.info("Server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
log.info("new client");
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
这给出了以下输出:
Nov 21, 2018 10:23:51 PM com.gamble.ssl.SSLServerClient main INFO: Server started
Nov 21, 2018 10:23:52 PM com.gamble.ssl.SSLServerClient main INFO: new client
writing to stream
即它在服务器套接字启动时阻塞。
我的问题是:为什么 SSL 服务器套接字连接会在 Java 中阻塞,而非 SSL 服务器套接字则不会?
"flip to plaintext?" 是什么意思?
这是您正在做的事情:
- 创建 SSL 服务器套接字
- 创建连接到 SSL 服务器套接字的普通套接字
- 从客户端发送一些数据
- 正在服务器端读取一些数据
现在,SSL 套接字需要加密数据传输。但是您未发送的数据未加密,因此它不是有效的 SSL 并且会引发异常 - 正如它所说,"Unrecognized SSL message" 因为您没有发送有效的 SSL 消息,而 "plaintext connection?" 是一个提示关于可能出错的地方。
您必须在连接的两端都使用 SSL,否则它们之间无法正常通信。
问题的简单答案是:
Because an SSL Handshake is asynchronous and the non SSL socket doesn't have to do that step.
好的 - 原始 SSL 代码有两个问题:
没有信任库:
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
因为握手是同步的 - 您必须在不同的线程上启动客户端。即:
(new Thread() { public void run() { // do stuff } }).start();
因此代码如下所示:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
public class SSLServerClient {
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
System.out.println("--server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
System.out.println("--new client");
final byte[] bytes = "--Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("--Gotten output stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
(new Thread() {
public void run() {
System.out.println("--reading from stream");
int len = 0;
try {
len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("--writing to stream");
out.write(bytes.length);
System.out.println("--writing to stream - length");
out.write(bytes);
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
输出看起来像
--server started
--new client
--Gotten output stream
--writing to stream
--reading from stream
--writing to stream - length
--Hello World!
Process finished with exit code 0
完成
(注意我在输出中添加了前导 --
以帮助读取 SSL 调试输出。