我正在尝试使用Java SSLSockets将安全性应用于简单的聊天应用程序。
我创建了一个自签名CA,然后用它签署了两个证书(所有使用的RSA密钥),一个用于服务器,一个用于客户端。之后,我将证书导入服务器的密钥库,并导入客户端的另一个密钥库。
CA :
openssl genrsa -out ca.key 1024 -rsa
openssl req -new -key ca.key -out ca.csr
openssl x509 -req -days 365 -in ca.csr -out ca.crt -signkey ca.key
SERVER CERTIFICATE:
openssl genrsa -out server.key 1024 -rsa
openssl req -new -key server.key -out server.csr
openssl ca -in server.csr -cert ca.crt -keyfile ca.key -out server.crt
CLIENT CERTIFICATE :
openssl genrsa -out client.key 1024 -rsa
openssl req -new -key client.key -out client.csr
openssl ca -in client.csr -cert ca.crt -keyfile ca.key -out client.crt
KEYSTORES:
keytool -import -keystore serverkeystore -file ca.crt -alias theCARoot
keytool -import -keystore serverkeystore -file server.crt -alias servercrt
keytool -import -keystore serverkeystore -file client.crt -alias clientcrt
keytool -import -keystore clientkeystore -file ca.crt -alias theCARoot
keytool -import -keystore clientkeystore -file server.crt -alias servercrt
keytool -import -keystore clientkeystore -file client.crt -alias clientcrt
我想使用特定的密码,但显然没有任何支持的密码可以工作。
我的客户代码:
import java.net.*;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import javax.net.ssl.*;
public class ChatClient implements Runnable
{
private SSLSocket socket = null;
private Thread thread = null;
private DataInputStream console = null;
private DataOutputStream streamOut = null;
private ChatClientThread client = null;
final String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_256_CBC_SHA256"};
final char[] passphrase = "123456".toCharArray();
public ChatClient(String serverName, int serverPort)
{
System.out.println("Establishing connection to server...");
try
{
SSLSocketFactory factory = null;
SSLContext ctx = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks= KeyStore.getInstance("JKS");
ks.load(new FileInputStream("clientkeystore"), passphrase);
kmf.init(ks, passphrase);
KeyStore serverKey = KeyStore.getInstance("JKS");
serverKey.load(new FileInputStream("serverkeystore"),passphrase);
TrustManagerFactory trustManager = TrustManagerFactory.getInstance("SunX509");
trustManager.init(serverKey);
ctx.init(kmf.getKeyManagers(), trustManager.getTrustManagers(), null);
factory = ctx.getSocketFactory();
socket = (SSLSocket)factory.createSocket(serverName, serverPort);
socket.setEnabledCipherSuites(enabledCipherSuites);
start();
}
catch(UnknownHostException uhe)
{
// Host unkwnown
System.out.println("Error establishing connection - host unknown: " + uhe.getMessage());
}
catch(IOException ioexception)
{
// Other error establishing connection
System.out.println("Error establishing connection - unexpected exception: " + ioexception.getMessage());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
public void run()
{
while (thread != null)
{
try
{
// Sends message from console to server
streamOut.writeUTF(console.readLine());
streamOut.flush();
}
catch(IOException ioexception)
{
System.out.println("Error sending string to server: " + ioexception.getMessage());
stop();
}
}
}
public void handle(String msg)
{
// Receives message from server
if (msg.equals(".quit"))
{
// Leaving, quit command
System.out.println("Exiting...Please press RETURN to exit ...");
stop();
}
else
// else, writes message received from server to console
System.out.println(msg);
}
// Inits new client thread
public void start() throws IOException
{
console = new DataInputStream(System.in);
streamOut = new DataOutputStream(socket.getOutputStream());
if (thread == null)
{
client = new ChatClientThread(this, socket);
thread = new Thread(this);
thread.start();
}
}
// Stops client thread
public void stop()
{
if (thread != null)
{
thread.stop();
thread = null;
}
try
{
if (console != null) console.close();
if (streamOut != null) streamOut.close();
if (socket != null) socket.close();
}
catch(IOException ioe)
{
System.out.println("Error closing thread..."); }
client.close();
client.stop();
}
public static void main(String args[])
{
ChatClient client = null;
if (args.length != 2)
// Displays correct usage syntax on stdout
System.out.println("Usage: java ChatClient host port");
else
// Calls new client
client = new ChatClient(args[0], Integer.parseInt(args[1]));
}
}
class ChatClientThread extends Thread
{
private SSLSocket socket = null;
private ChatClient client = null;
private DataInputStream streamIn = null;
public ChatClientThread(ChatClient _client, SSLSocket _socket)
{
client = _client;
socket = _socket;
open();
start();
}
public void open()
{
try
{
streamIn = new DataInputStream(socket.getInputStream());
}
catch(IOException ioe)
{
System.out.println("Error getting input stream: " + ioe);
client.stop();
}
}
public void close()
{
try
{
if (streamIn != null) streamIn.close();
}
catch(IOException ioe)
{
System.out.println("Error closing input stream: " + ioe);
}
}
public void run()
{
while (true)
{ try
{
client.handle(streamIn.readUTF());
}
catch(IOException ioe)
{
System.out.println("Listening error: " + ioe.getMessage());
client.stop();
}
}
}
}
对于服务器:
import java.net.*;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Arrays;
import javax.net.ServerSocketFactory;
import javax.net.ssl.*;
public class ChatServer implements Runnable
{
private ChatServerThread clients[] = new ChatServerThread[20];
private SSLServerSocket server_socket = null;
private Thread thread = null;
private int clientCount = 0;
final String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_256_CBC_SHA256"};
final char[] passphrase = "123456".toCharArray();
public ChatServer(int port)
{
try
{
// Binds to port and starts server
System.out.println("Binding to port " + port);
SSLContext ctx = SSLContext.getInstance("TLS");;
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("serverkeystore"), passphrase);
kmf.init(ks, passphrase);
KeyStore serverKey = KeyStore.getInstance("JKS");
serverKey.load(new FileInputStream("clientkeystore"),passphrase);
TrustManagerFactory trustManager = TrustManagerFactory.getInstance("SunX509");
trustManager.init(serverKey);
ctx.init(kmf.getKeyManagers(), trustManager.getTrustManagers(), null);
SSLServerSocketFactory ssf = ctx.getServerSocketFactory();
server_socket = (SSLServerSocket) ssf.createServerSocket(port);
server_socket.setEnabledCipherSuites(enabledCipherSuites);
server_socket.setNeedClientAuth(true);
System.out.println("Server started: " + server_socket);
start();
}
catch(IOException ioexception)
{
// Error binding to port
System.out.println("Binding error (port=" + port + "): " + ioexception.getMessage());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
public void run()
{
while (thread != null)
{
try
{
// Adds new thread for new client
System.out.println("Waiting for a client ...");
addThread((SSLSocket)server_socket.accept());
}
catch(IOException ioexception)
{
System.out.println("Accept error: " + ioexception); stop();
}
}
}
public void start()
{
if (thread == null)
{
// Starts new thread for client
thread = new Thread(this);
thread.start();
}
}
public void stop()
{
if (thread != null)
{
// Stops running thread for client
thread.stop();
thread = null;
}
}
private int findClient(int ID)
{
// Returns client from id
for (int i = 0; i < clientCount; i++)
if (clients[i].getID() == ID)
return i;
return -1;
}
public synchronized void handle(int ID, String input)
{
if (input.equals(".quit"))
{
int leaving_id = findClient(ID);
// Client exits
clients[leaving_id].send(".quit");
// Notify remaing users
for (int i = 0; i < clientCount; i++)
if (i!=leaving_id)
clients[i].send("Client " +ID + " exits..");
remove(ID);
}
else
// Brodcast message for every other client online
for (int i = 0; i < clientCount; i++)
clients[i].send(ID + ": " + input);
}
public synchronized void remove(int ID)
{
int pos = findClient(ID);
if (pos >= 0)
{
// Removes thread for exiting client
ChatServerThread toTerminate = clients[pos];
System.out.println("Removing client thread " + ID + " at " + pos);
if (pos < clientCount-1)
for (int i = pos+1; i < clientCount; i++)
clients[i-1] = clients[i];
clientCount--;
try
{
toTerminate.close();
}
catch(IOException ioe)
{
System.out.println("Error closing thread: " + ioe);
}
toTerminate.stop();
}
}
private void addThread(SSLSocket socket)
{
if (clientCount < clients.length)
{
// Adds thread for new accepted client
System.out.println("Client accepted: " + socket);
clients[clientCount] = new ChatServerThread(this, socket);
try
{
clients[clientCount].open();
clients[clientCount].start();
clientCount++;
}
catch(IOException ioe)
{
System.out.println("Error opening thread: " + ioe);
}
}
else
System.out.println("Client refused: maximum " + clients.length + " reached.");
}
public static void main(String args[])
{
ChatServer server = null;
if (args.length != 1)
// Displays correct usage for server
System.out.println("Usage: java ChatServer port");
else
// Calls new server
server = new ChatServer(Integer.parseInt(args[0]));
}
}
class ChatServerThread extends Thread
{
private ChatServer server = null;
private SSLSocket socket = null;
private int ID = -1;
private DataInputStream streamIn = null;
private DataOutputStream streamOut = null;
public ChatServerThread(ChatServer _server, SSLSocket _socket)
{
super();
server = _server;
socket = _socket;
ID = socket.getPort();
}
// Sends message to client
public void send(String msg)
{
try
{
streamOut.writeUTF(msg);
streamOut.flush();
}
catch(IOException ioexception)
{
System.out.println(ID + " ERROR sending message: " + ioexception.getMessage());
server.remove(ID);
stop();
}
}
// Gets id for client
public int getID()
{
return ID;
}
// Runs thread
public void run()
{
System.out.println("Server Thread " + ID + " running.");
while (true)
{
try
{
server.handle(ID, streamIn.readUTF());
}
catch(IOException ioe)
{
System.out.println(ID + " ERROR reading: " + ioe.getMessage());
server.remove(ID);
stop();
}
}
}
// Opens thread
public void open() throws IOException
{
streamIn = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
streamOut = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
}
// Closes thread
public void close() throws IOException
{
if (socket != null) socket.close();
if (streamIn != null) streamIn.close();
if (streamOut != null) streamOut.close();
}
}
对不起,如果我的英语有点生疏。
我的操作系统是OS X El Capitan,Java版本是1.8。
Here is the server's stack trace:
/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/bin/java -Djavax.net.debug=all -Didea.launcher.port=7536 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA 15.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/tools.jar:/Users/joaogoncalves/Dropbox/STI/out/production/STI Assignment 3:/Applications/IntelliJ IDEA 15.app/Contents/lib/idea_rt.jar" com.intellij.rt.execution.application.AppMain ChatServer 5000
Binding to port 5000
adding as trusted cert:
Subject: CN=Joao Goncalves, OU=DEQ, O=UC, ST=Coimbra, C=PT
Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT
Algorithm: RSA; Serial number: 0xc94895f3863a5c36
Valid from Mon May 23 23:43:42 WEST 2016 until Tue May 23 23:43:42 WEST 2017
adding as trusted cert:
Subject: CN=www.uc.pt, OU=DEM, O=UC, ST=Coimbra, C=PT
Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT
Algorithm: RSA; Serial number: 0xc94895f3863a5c35
Valid from Mon May 23 23:42:54 WEST 2016 until Tue May 23 23:42:54 WEST 2017
adding as trusted cert:
Subject: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT
Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT
Algorithm: RSA; Serial number: 0xdb931da4e1abec22
Valid from Mon May 23 23:42:03 WEST 2016 until Tue May 23 23:42:03 WEST 2017
trigger seeding of SecureRandom
done seeding SecureRandom
Server started: [SSL: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=5000]]
Waiting for a client ...
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
Client accepted: 74ce57fc[SSL_NULL_WITH_NULL_NULL: Socket[addr=/127.0.0.1,port=57519,localport=5000]]
Waiting for a client ...
Server Thread 57519 running.
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
No available cipher suite for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
No available cipher suite for TLSv1.1
[Raw read]: length = 5
0000: 16 03 03 00 52 ....R
[Raw read]: length = 82
0000: 01 00 00 4E 03 03 57 44 7B 3B B8 1E 77 88 AF 4E ...N..WD.;..w..N
0010: C7 CA 73 CE AC 38 62 5D 18 BD 9A 16 7E 25 86 25 ..s..8b].....%.%
0020: 36 1C EF F5 B6 FF 00 00 02 00 3D 01 00 00 23 00 6.........=...#.
0030: 0D 00 1A 00 18 06 03 06 01 05 03 05 01 04 03 04 ................
0040: 01 03 03 03 01 02 03 02 01 02 02 01 01 FF 01 00 ................
0050: 01 00 ..
Thread-1, READ: TLSv1.2 Handshake, length = 82
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1464105787 bytes = { 184, 30, 119, 136, 175, 78, 199, 202, 115, 206, 172, 56, 98, 93, 24, 189, 154, 22, 126, 37, 134, 37, 54, 28, 239, 245, 182, 255 }
Session ID: {}
Cipher Suites: [TLS_RSA_WITH_AES_256_CBC_SHA256]
Compression Methods: { 0 }
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension renegotiation_info, renegotiated_connection: <empty>
***
[read] MD5 and SHA1 hashes: len = 82
0000: 01 00 00 4E 03 03 57 44 7B 3B B8 1E 77 88 AF 4E ...N..WD.;..w..N
0010: C7 CA 73 CE AC 38 62 5D 18 BD 9A 16 7E 25 86 25 ..s..8b].....%.%
0020: 36 1C EF F5 B6 FF 00 00 02 00 3D 01 00 00 23 00 6.........=...#.
0030: 0D 00 1A 00 18 06 03 06 01 05 03 05 01 04 03 04 ................
0040: 01 03 03 03 01 02 03 02 01 02 02 01 01 FF 01 00 ................
0050: 01 00 ..
%% Initialized: [Session-1, SSL_NULL_WITH_NULL_NULL]
%% Invalidated: [Session-1, SSL_NULL_WITH_NULL_NULL]
Thread-1, SEND TLSv1.2 ALERT: fatal, description = handshake_failure
Thread-1, WRITE: TLSv1.2 Alert, length = 2
[Raw write]: length = 7
0000: 15 03 03 00 02 02 28 ......(
Thread-1, called closeSocket()
Thread-1, handling exception: javax.net.ssl.SSLHandshakeException: no cipher suites in common
57519 ERROR reading: no cipher suites in common
Removing client thread 57519 at 0
Thread-1, called close()
Thread-1, called closeInternal(true)
Thread-1, called close()
Thread-1, called closeInternal(true)
Thread-1, called close()
Thread-1, called closeInternal(true)
Process finished with exit code 130
答案 0 :(得分:4)
每个经过身份验证的一方的密钥库,始终是服务器,此处客户端也因为您指定了NeedClientAuth
,必须拥有私钥和证书,而不仅仅是证书( S)。有两种方法可以做到这一点:
将OpenSSL生成的私钥和相关证书转换为PKCS#12,然后将PKCS#12转换为JKS,或者只使用Java中的PKCS#12(JCE可以处理它,以及最新版本的Java8)即使您指定了JKS! - 请参阅密钥库兼容模式下的http://www.oracle.com/technetwork/java/javase/8u60-relnotes-2620227.html。见:
How to import an existing x509 certificate and private key in Java keystore to use in SSL?
How can i create keystore from an existing certificate (abc.crt) and abc.key files?
Importing the private-key/public-certificate pair in the Java KeyStore
convert certificate from pem into jks(披露:我的)
How to create keystore from cer files(披露:我的)
在Java中生成EE私钥和CSR,然后使用OpenSSL(使用CA密钥和证书)颁发EE证书,并将证书重新导入Java密钥库:
keytool -keystore server.jks -genkeypair -keyalg RSA
# before j7 best to add -keysize 2048 see below
keytool -keystore server.jks -certreq >server.csr
openssl ca -in server.csr ... -out server.crt
# or submit the CSR to a real CA and get its response
# then either install the chain all at once:
cat server.crt ca.crt >temp
keytool -keystore server.jks -importcert -file temp
# and confirm (need temp so stdin available for confirm;
# if using a public CA, can add -trustcacerts and use pipe instead)
# or install the certs separately, top down:
keytool -keystore server.jks -importcert -file ca.crt -alias ca
# and confirm, THEN
keytool -keystore server.jks -importcert -file server.crt
# (last) response must be 'Certificate reply was installed'
# NOT merely 'Certificate was added' which means you messed up
# and similarly for client
后一种方法(ca.crt和privatekey + server.crt的单独条目)具有以下优点:同一文件可以用作密钥库和信任库,您不需要将serverkey用作clienttrust,反之亦然。如果这些是真正的系统,这将是一个安全上的好处。
最后一点:您应该开始使用RSA 2048位密钥。自2014年以来,NIST和CABforum等机构已禁止1024位,尽管Java仍然接受它们,但大多数浏览器和许多其他工具已经警告过它们,并且可能很快就会拒绝它们。出于类似的原因,您应该至少使用SHA256签署证书 - 这可以在ca
使用的配置文件中设置,或者您可以使用命令行标记-md sha256
。
答案 1 :(得分:1)
我从未见过KeyManagerFactory以你的方式声明,我通常会这样看:
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
我意识到我错过了第二个链接:
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug
您需要完成SSL调试。但是,您需要明确启用某些密码(如果我没记错),并建议使用该路径。