keytool即JDK中自带的证书生成工具,常见的还有openssl工具。
1.生成一个自签名的CA证书,为了给Client和Server的证书签名。
命令:keytool -genkeypair (-keyalg RSA) -alias TEST_ROOT -keystore test_root.jks
解释:生成一对密钥,存储在test_root.jks中,条目别名为TEST_ROOT。输入该命令后会提示输入个人信息。当命令完成后,会在test_root.jks中生成一个自签名的证书。当然,私钥也保存在该文件中。这里密钥库的密码和密钥密码都设置成123456
如果不加keyalg 的,默认是DSA算法生成
2.为server生成一对密钥(也就是一个自签名的证书)
命令:keytool -genkeypair -alias TEST_SERVER -keystore test_server.jks
解释:生成一对密钥,存储在test_server.jks中,条目别名TEST_SERVER。
3.生成一个签名请求文件(即请求CA给server自签名的证书签名)
命令:keytool -certreq -file test_server.csr -alias TEST_SERVER -keystore test_server.jks
解释:为存储在test_server.jks中的别名TEST_SERVER生成一个证书请求文件test_server.csr。
4.ca为server的证书签名
命令:keytool -gencert -infile test_server.csr -outfile test_server.cer -alias TEST_ROOT -keystore TEST_ROOT.jks
解释:利用TEST_ROOT.jks中条目别名为TEST_ROOT的私钥为test_server.csr证书请求对应的证书签名,并将签名后的证书保存在test_server.cer中。
5.导出ca证书
命令:keytool -exportcert -alias TEST_ROOT -file test_root.cer -keystore test_root.jks
解释:将test_root.jks中条目别名为TEST_ROOT的证书导出到test_root.cer中。
6.将ca证书导入到test_server.jks中。
命令:keytool -importcert -alias TEST_ROOT -file test_root.cer -keystore TEST_SERVER.jks
解释:将test_root.cer证书文件导入TEST_SERVER.jks中,条目别名为TEST_ROOT。这里需要注意,在TEST_SERVER.jks中之前应不存在条目别名为TEST_ROOT,这样才会以信任证书的方式导入。
7.更新server证书
命令:keytool -importcert -alias TEST_SERVER -file test_server.cer -keystore TEST_SERVER.jks
解释:将test_server.cer导入到TEST_SERVER.jks中。因为在生成server密钥对的时候,在test_server.jks中已经存在自签的证书,条目别名为TEST_SERVER。而该命令保存的别名还是TEST_SERVER。意思上就是更新证书。将之前自签名的证书替换为ca签名过的证书。
8.查看test_server.jks中server的证书和ca证书。
命令:keytool -list -v -keystore test_server.jks
从条目test_server中可以看出,该证书被ca签名后,ca的证书也保存在该条目中,形成证书链。
对于Client的证书生成参考2-7步。在该过程中生成的文件如下:
Java中主要通过JSSE(Java Secure Socket Extension)来完成安全套接字的编写。其中主要涉及了一些类,例如Keystore、KeyManagerFactory、TrustManagerFactory、SSLContext等。它们的关系如下图(图的来源于文章结尾的链接中)
编程的步骤如下:
1.利用keystore类完成客户端和服务器的证书库和信任库的加载
2.利用加载的证书库和信任库,完成KeyManagerFactory和TrustManagerFactory的初始化。
3.生成SSLContext对象,并用keyManagerFactory和TrustManagerFactory生成的keyManager和TrustManager完成初始化。
4.利用SSLContext对象,生成对应的SSLSocket和SSLServerSocket。
需要的文件
1.test_root.jks(存储了ca证书,因为client和server的证书都是被ca签名的,所以ca证书被信任,则client和server的证书都会被信任)。
2.test_client.jks(存储了client的证书)。
3.test_server.jks(存储了server的证书)。
客户端代码
public class Client {
public static void main(String[] args) throws Exception {
String clientKeyStoreFile = "d:\\keystore\\test_client.jks";
String clientKeyStorePwd = "123456";
String clientKeyPwd = "123456";
String clientTrustKeyStoreFile = "d:\\keystore\\test_root.jks";
String clientTrustKeyStorePwd = "123456";
//生成client的keystore对象
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray());
//生成信任证书的keystore对象
KeyStore clientTrustKeyStore = KeyStore.getInstance("JKS");
clientTrustKeyStore.load(new FileInputStream(clientTrustKeyStoreFile), clientTrustKeyStorePwd.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, clientKeyPwd.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(clientTrustKeyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
Socket socket = socketFactory.createSocket("localhost", Server.SERVER_PORT);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
send("hello", out);
send("exit", out);
receive(in);
socket.close();
}
public static void send(String s, PrintWriter out) throws IOException {
System.out.println("Sending: " + s);
out.println(s);
}
public static void receive(BufferedReader in) throws IOException {
String s;
while ((s = in.readLine()) != null) {
System.out.println("Reveived: " + s);
}
}
}
服务器代码
public class Server implements Runnable,HandshakeCompletedListener {
public static final int SERVER_PORT = 11123;
private final Socket s;
private String peerCerName;
public Server(Socket s) {
this.s = s;
}
public static void main(String[] args) throws Exception {
String serverKeyStoreFile = "d:\\keystore\\test_server.jks";
String serverKeyStorePwd = "123456";
String ServerKeyPwd = "123456";
String serverTrustKeyStoreFile = "d:\\keystore\\test_root.jks";
String serverTrustKeyStorePwd = "123456";
/*
* 加载server.keystore
*
*/
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
serverKeyStore.load(new FileInputStream(serverKeyStoreFile), serverKeyStorePwd.toCharArray());
/*
* 加载servertrust.keystore
*
*/
KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");
serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(serverKeyStore, ServerKeyPwd.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(serverTrustKeyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(SERVER_PORT);
//设置双向验证
sslServerSocket.setNeedClientAuth(true);
while (true) {
SSLSocket s = (SSLSocket)sslServerSocket.accept();
Server cs = new Server(s);
s.addHandshakeCompletedListener(cs);
new Thread(cs).start();
}
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter writer = new PrintWriter(s.getOutputStream(), true);
writer.println("Welcome~, enter exit to leave.");
String message;
while ((message = reader.readLine()) != null && !message.trim().equalsIgnoreCase("exit")) {
writer.println("Echo: " + message);
}
writer.println("Bye~, " + peerCerName);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
try {
X509Certificate cert = (X509Certificate) event.getPeerCertificates()[0];
peerCerName = cert.getSubjectX500Principal().getName();
} catch (SSLPeerUnverifiedException ex) {
ex.printStackTrace();
}
}
}
运行结果:
说明SSL握手成功建立。
参考的文章:https://www.ibm.com/developerworks/cn/java/j-lo-socketkeytool/