(java 基础知识) Java 安全套接字--javax.net.ssl

2023-11-14

有关SSL的原理和介绍在网上已经有不少,对于Java下使用keytool生成证书,配置SSL通信的教程也非常多。但如果我们不能够亲自动手做一个SSL Sever和SSL Client,可能就永远也不能深入地理解Java环境下,SSL的通信是如何实现的。对SSL中的各种概念的认识也可能会仅限于可以使用的程度。本文通过构造一个简单的SSL Server和SSL Client来讲解Java环境下SSL的通信原理。

首先我们先回顾一下常规的Java Socket编程。在Java下写一个Socket服务器和客户端的例子还是比较简单的。以下是服务端的代码:

Java代码 复制代码  收藏代码
  1. package org.bluedash.tryssl;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.IOException;   
  5. import java.io.InputStreamReader;   
  6. import java.io.PrintWriter;   
  7. import java.net.ServerSocket;   
  8. import java.net.Socket;   
  9.   
  10. public class Server extends Thread {   
  11.     private Socket socket;   
  12.   
  13.     public Server(Socket socket) {   
  14.         this.socket = socket;   
  15.     }   
  16.   
  17.     public void run() {   
  18.         try {   
  19.             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));   
  20.             PrintWriter writer = new PrintWriter(socket.getOutputStream());   
  21.   
  22.             String data = reader.readLine();   
  23.             writer.println(data);   
  24.             writer.close();   
  25.             socket.close();   
  26.         } catch (IOException e) {   
  27.   
  28.         }   
  29.     }   
  30.        
  31.     public static void main(String[] args) throws Exception {   
  32.         while (true) {   
  33.             new Server((new ServerSocket(8080)).accept()).start();   
  34.         }   
  35.     }   
  36. }  
package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server extends Thread {
	private Socket socket;

	public Server(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			PrintWriter writer = new PrintWriter(socket.getOutputStream());

			String data = reader.readLine();
			writer.println(data);
			writer.close();
			socket.close();
		} catch (IOException e) {

		}
	}
	
	public static void main(String[] args) throws Exception {
		while (true) {
			new Server((new ServerSocket(8080)).accept()).start();
		}
	}
}


服务端很简单:侦听8080端口,并把客户端发来的字符串返回去。下面是客户端的代码:

Java代码 复制代码  收藏代码
  1. package org.bluedash.tryssl;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.InputStreamReader;   
  5. import java.io.PrintWriter;   
  6. import java.net.Socket;   
  7.   
  8. public class Client {   
  9.   
  10.     public static void main(String[] args) throws Exception {   
  11.   
  12.         Socket s = new Socket("localhost"8080);   
  13.   
  14.         PrintWriter writer = new PrintWriter(s.getOutputStream());   
  15.         BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));   
  16.         writer.println("hello");   
  17.         writer.flush();   
  18.         System.out.println(reader.readLine());   
  19.         s.close();   
  20.     }   
  21.   
  22. }  
package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {

	public static void main(String[] args) throws Exception {

		Socket s = new Socket("localhost", 8080);

		PrintWriter writer = new PrintWriter(s.getOutputStream());
		BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
		writer.println("hello");
		writer.flush();
		System.out.println(reader.readLine());
		s.close();
	}

}


客户端也非常简单:向服务端发起请求,发送一个"hello"字串,然后获得服务端的返回。把服务端运行起来后,执行客户端,我们将得到"hello"的返回。

就是这样一套简单的网络通信的代码,我们来把它改造成使用SSL通信。在SSL通信协议中,我们都知道首先服务端必须有一个数字证书,当客户端连接到服务端时,会得到这个证书,然后客户端会判断这个证书是否是可信的,如果是,则交换信道加密密钥,进行通信。如果不信任这个证书,则连接失败。

因此,我们首先要为服务端生成一个数字证书。Java环境下,数字证书是用keytool生成的,这些证书被存储在store的概念中,就是证书仓库。我们来调用keytool命令为服务端生成数字证书和保存它使用的证书仓库:

Bash代码 复制代码  收藏代码
  1. keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123  
keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123


这样,我们就将服务端证书bluedash-ssl-demo-server保存在了server_ksy这个store文件当中。有关keytool的用法在本文中就不再多赘述。执行上面的命令得到如下结果:

Bash代码 复制代码  收藏代码
  1. Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days   
  2.         for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  3. [Storing ./server_ks]  
Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
        for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./server_ks]


然后,改造我们的服务端代码,让服务端使用这个证书,并提供SSL通信:

Java代码 复制代码  收藏代码
  1. package org.bluedash.tryssl;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.FileInputStream;   
  5. import java.io.IOException;   
  6. import java.io.InputStreamReader;   
  7. import java.io.PrintWriter;   
  8. import java.net.ServerSocket;   
  9. import java.net.Socket;   
  10. import java.security.KeyStore;   
  11.   
  12. import javax.net.ServerSocketFactory;   
  13. import javax.net.ssl.KeyManagerFactory;   
  14. import javax.net.ssl.SSLContext;   
  15. import javax.net.ssl.SSLServerSocket;   
  16.   
  17. public class SSLServer extends Thread {   
  18.     private Socket socket;   
  19.   
  20.     public SSLServer(Socket socket) {   
  21.         this.socket = socket;   
  22.     }   
  23.   
  24.     public void run() {   
  25.         try {   
  26.             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));   
  27.             PrintWriter writer = new PrintWriter(socket.getOutputStream());   
  28.   
  29.             String data = reader.readLine();   
  30.             writer.println(data);   
  31.             writer.close();   
  32.             socket.close();   
  33.         } catch (IOException e) {   
  34.   
  35.         }   
  36.     }   
  37.   
  38.     private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks";   
  39.     private static String SERVER_KEY_STORE_PASSWORD = "123123";   
  40.   
  41.     public static void main(String[] args) throws Exception {   
  42.         System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);   
  43.         SSLContext context = SSLContext.getInstance("TLS");   
  44.            
  45.         KeyStore ks = KeyStore.getInstance("jceks");   
  46.         ks.load(new FileInputStream(SERVER_KEY_STORE), null);   
  47.         KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");   
  48.         kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());   
  49.            
  50.         context.init(kf.getKeyManagers(), nullnull);   
  51.   
  52.         ServerSocketFactory factory = context.getServerSocketFactory();   
  53.         ServerSocket _socket = factory.createServerSocket(8443);   
  54.         ((SSLServerSocket) _socket).setNeedClientAuth(false);   
  55.   
  56.         while (true) {   
  57.             new SSLServer(_socket.accept()).start();   
  58.         }   
  59.     }   
  60. }  
package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;

import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;

public class SSLServer extends Thread {
	private Socket socket;

	public SSLServer(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			PrintWriter writer = new PrintWriter(socket.getOutputStream());

			String data = reader.readLine();
			writer.println(data);
			writer.close();
			socket.close();
		} catch (IOException e) {

		}
	}

	private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks";
	private static String SERVER_KEY_STORE_PASSWORD = "123123";

	public static void main(String[] args) throws Exception {
		System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);
		SSLContext context = SSLContext.getInstance("TLS");
		
		KeyStore ks = KeyStore.getInstance("jceks");
		ks.load(new FileInputStream(SERVER_KEY_STORE), null);
		KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
		kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
		
		context.init(kf.getKeyManagers(), null, null);

		ServerSocketFactory factory = context.getServerSocketFactory();
		ServerSocket _socket = factory.createServerSocket(8443);
		((SSLServerSocket) _socket).setNeedClientAuth(false);

		while (true) {
			new SSLServer(_socket.accept()).start();
		}
	}
}


可以看到,服务端的Socket准备设置工作大大增加了,增加的代码的作用主要是将证书导入并进行使用。此外,所使用的Socket变成了SSLServerSocket,另外端口改到了8443(这个不是强制的,仅仅是为了遵守习惯)。另外,最重要的一点,服务端证书里面的CN一定和服务端的域名统一,我们的证书服务的域名是localhost,那么我们的客户端在连接服务端时一定也要用localhost来连接,否则根据SSL协议标准,域名与证书的CN不匹配,说明这个证书是不安全的,通信将无法正常运行。

有了服务端,我们原来的客户端就不能使用了,必须要走SSL协议。由于服务端的证书是我们自己生成的,没有任何受信任机构的签名,所以客户端是无法验证服务端证书的有效性的,通信必然会失败。所以我们需要为客户端创建一个保存所有信任证书的仓库,然后把服务端证书导进这个仓库。这样,当客户端连接服务端时,会发现服务端的证书在自己的信任列表中,就可以正常通信了。

因此现在我们要做的是生成一个客户端的证书仓库,因为keytool不能仅生成一个空白仓库,所以和服务端一样,我们还是生成一个证书加一个仓库(客户端证书加仓库):

Bash代码 复制代码  收藏代码
  1. keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456  
keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456


结果如下:

Bash代码 复制代码  收藏代码
  1. Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days   
  2.         for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  3. [Storing ./client_ks]  
Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
        for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./client_ks]


接下来,我们要把服务端的证书导出来,并导入到客户端的仓库。第一步是导出服务端的证书:

Bash代码 复制代码  收藏代码
  1. keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer  
keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer


执行结果如下:

Bash代码 复制代码  收藏代码
  1. Enter keystore password:  server   
  2. Certificate stored in file <server_key.cer>  
Enter keystore password:  server
Certificate stored in file <server_key.cer>


然后是把导出的证书导入到客户端证书仓库:

Bash代码 复制代码  收藏代码
  1. keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks  
keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks


结果如下:

Bash代码 复制代码  收藏代码
  1. Enter keystore password:  client   
  2. Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  3. Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  4. Serial number: 4c57c7de   
  5. Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010  
  6. Certificate fingerprints:   
  7.          MD5:  FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C   
  8.          SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4   
  9.          Signature algorithm name: SHA1withRSA   
  10.          Version: 3  
  11. Trust this certificate? [no]:  yes   
  12. Certificate was added to keystore  
Enter keystore password:  client
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c7de
Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010
Certificate fingerprints:
         MD5:  FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C
         SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4
         Signature algorithm name: SHA1withRSA
         Version: 3
Trust this certificate? [no]:  yes
Certificate was added to keystore


好,准备工作做完了,我们来撰写客户端的代码:

Java代码 复制代码  收藏代码
  1. package org.bluedash.tryssl;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.InputStreamReader;   
  5. import java.io.PrintWriter;   
  6. import java.net.Socket;   
  7.   
  8. import javax.net.SocketFactory;   
  9. import javax.net.ssl.SSLSocketFactory;   
  10.   
  11. public class SSLClient {   
  12.   
  13.     private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";   
  14.   
  15.     public static void main(String[] args) throws Exception {   
  16.         // Set the key store to use for validating the server cert.   
  17.         System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);   
  18.            
  19.         System.setProperty("javax.net.debug""ssl,handshake");   
  20.   
  21.         SSLClient client = new SSLClient();   
  22.         Socket s = client.clientWithoutCert();   
  23.   
  24.         PrintWriter writer = new PrintWriter(s.getOutputStream());   
  25.         BufferedReader reader = new BufferedReader(new InputStreamReader(s   
  26.                 .getInputStream()));   
  27.         writer.println("hello");   
  28.         writer.flush();   
  29.         System.out.println(reader.readLine());   
  30.         s.close();   
  31.     }   
  32.   
  33.     private Socket clientWithoutCert() throws Exception {   
  34.         SocketFactory sf = SSLSocketFactory.getDefault();   
  35.         Socket s = sf.createSocket("localhost"8443);   
  36.         return s;   
  37.     }   
  38. }  
package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

public class SSLClient {

	private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";

	public static void main(String[] args) throws Exception {
		// Set the key store to use for validating the server cert.
		System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
		
		System.setProperty("javax.net.debug", "ssl,handshake");

		SSLClient client = new SSLClient();
		Socket s = client.clientWithoutCert();

		PrintWriter writer = new PrintWriter(s.getOutputStream());
		BufferedReader reader = new BufferedReader(new InputStreamReader(s
				.getInputStream()));
		writer.println("hello");
		writer.flush();
		System.out.println(reader.readLine());
		s.close();
	}

	private Socket clientWithoutCert() throws Exception {
		SocketFactory sf = SSLSocketFactory.getDefault();
		Socket s = sf.createSocket("localhost", 8443);
		return s;
	}
}


可以看到,除了把一些类变成SSL通信类以外,客户端也多出了使用信任证书仓库的代码。以上,我们便完成了SSL单向握手通信。即:客户端验证服务端的证书,服务端不认证客户端的证书。

以上便是Java环境下SSL单向握手的全过程。因为我们在客户端设置了日志输出级别为DEBUG:

Java代码 复制代码  收藏代码
  1. System.setProperty("javax.net.debug""ssl,handshake");  
System.setProperty("javax.net.debug", "ssl,handshake");


因此我们可以看到SSL通信的全过程,这些日志可以帮助我们更具体地了解通过SSL协议建立网络连接时的全过程。

结合日志,我们来看一下SSL双向认证的全过程:



第一步: 客户端发送ClientHello消息,发起SSL连接请求,告诉服务器自己支持的SSL选项(加密方式等)。

Bash代码 复制代码  收藏代码
  1. *** ClientHello, TLSv1  
*** ClientHello, TLSv1


第二步: 服务器响应请求,回复ServerHello消息,和客户端确认SSL加密方式:

Bash代码 复制代码  收藏代码
  1. *** ServerHello, TLSv1  
*** ServerHello, TLSv1


第三步: 服务端向客户端发布自己的公钥。

第四步: 客户端与服务端的协通沟通完毕,服务端发送ServerHelloDone消息:

Bash代码 复制代码  收藏代码
  1. *** ServerHelloDone  
*** ServerHelloDone


第五步: 客户端使用服务端给予的公钥,创建会话用密钥(SSL证书认证完成后,为了提高性能,所有的信息交互就可能会使用对称加密算法),并通过ClientKeyExchange消息发给服务器:

Bash代码 复制代码  收藏代码
  1. *** ClientKeyExchange, RSA PreMasterSecret, TLSv1  
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1


第六步: 客户端通知服务器改变加密算法,通过ChangeCipherSpec消息发给服务端:

Bash代码 复制代码  收藏代码
  1. main, WRITE: TLSv1 Change Cipher Spec, length = 1  
main, WRITE: TLSv1 Change Cipher Spec, length = 1


第七步: 客户端发送Finished消息,告知服务器请检查加密算法的变更请求:

Bash代码 复制代码  收藏代码
  1. *** Finished  
*** Finished


第八步:服务端确认算法变更,返回ChangeCipherSpec消息

Bash代码 复制代码  收藏代码
  1. main, READ: TLSv1 Change Cipher Spec, length = 1  
main, READ: TLSv1 Change Cipher Spec, length = 1


第九步:服务端发送Finished消息,加密算法生效:

Bash代码 复制代码  收藏代码
  1. *** Finished  
*** Finished


那么如何让服务端也认证客户端的身份,即双向握手呢?其实很简单,在服务端代码中,把这一行:

Java代码 复制代码  收藏代码
  1. ((SSLServerSocket) _socket).setNeedClientAuth(false);  
((SSLServerSocket) _socket).setNeedClientAuth(false);


改成:

Java代码 复制代码  收藏代码
  1. ((SSLServerSocket) _socket).setNeedClientAuth(true);  
((SSLServerSocket) _socket).setNeedClientAuth(true);


就可以了。但是,同样的道理,现在服务端并没有信任客户端的证书,因为客户端的证书也是自己生成的。所以,对于服务端,需要做同样的工作:把客户端的证书导出来,并导入到服务端的证书仓库:

Bash代码 复制代码  收藏代码
  1. keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer   
  2. Enter keystore password:  client   
  3. Certificate stored in file <client_key.cer>  
keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
Enter keystore password:  client
Certificate stored in file <client_key.cer>


Bash代码 复制代码  收藏代码
  1. keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks   
  2. Enter keystore password:  server   
  3. Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  4. Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn   
  5. Serial number: 4c57c80b   
  6. Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010  
  7. Certificate fingerprints:   
  8.          MD5:  DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79  
  9.          SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2   
  10.          Signature algorithm name: SHA1withRSA   
  11.          Version: 3  
  12. Trust this certificate? [no]:  yes   
  13. Certificate was added to keystore  
keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks
Enter keystore password:  server
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c80b
Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010
Certificate fingerprints:
         MD5:  DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79
         SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2
         Signature algorithm name: SHA1withRSA
         Version: 3
Trust this certificate? [no]:  yes
Certificate was added to keystore


完成了证书的导入,还要在客户端需要加入一段代码,用于在连接时,客户端向服务端出示自己的证书:

Java代码 复制代码  收藏代码
  1. package org.bluedash.tryssl;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.FileInputStream;   
  5. import java.io.InputStreamReader;   
  6. import java.io.PrintWriter;   
  7. import java.net.Socket;   
  8. import java.security.KeyStore;   
  9. import javax.net.SocketFactory;   
  10. import javax.net.ssl.KeyManagerFactory;   
  11. import javax.net.ssl.SSLContext;   
  12. import javax.net.ssl.SSLSocketFactory;   
  13.   
  14. public class SSLClient {   
  15.     private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";   
  16.     private static String CLIENT_KEY_STORE_PASSWORD = "456456";   
  17.        
  18.     public static void main(String[] args) throws Exception {   
  19.         // Set the key store to use for validating the server cert.   
  20.         System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);   
  21.         System.setProperty("javax.net.debug""ssl,handshake");   
  22.         SSLClient client = new SSLClient();   
  23.         Socket s = client.clientWithCert();   
  24.            
  25.         PrintWriter writer = new PrintWriter(s.getOutputStream());   
  26.         BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));   
  27.         writer.println("hello");   
  28.         writer.flush();   
  29.         System.out.println(reader.readLine());   
  30.         s.close();   
  31.     }   
  32.   
  33.     private Socket clientWithoutCert() throws Exception {   
  34.         SocketFactory sf = SSLSocketFactory.getDefault();   
  35.         Socket s = sf.createSocket("localhost"8443);   
  36.         return s;   
  37.     }   
  38.   
  39.     private Socket clientWithCert() throws Exception {   
  40.         SSLContext context = SSLContext.getInstance("TLS");   
  41.         KeyStore ks = KeyStore.getInstance("jceks");   
  42.            
  43.         ks.load(new FileInputStream(CLIENT_KEY_STORE), null);   
  44.         KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");   
  45.         kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());   
  46.         context.init(kf.getKeyManagers(), nullnull);   
  47.            
  48.         SocketFactory factory = context.getSocketFactory();   
  49.         Socket s = factory.createSocket("localhost"8443);   
  50.         return s;   
  51.     }   
  52. }  
package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

public class SSLClient {
	private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
	private static String CLIENT_KEY_STORE_PASSWORD = "456456";
	
	public static void main(String[] args) throws Exception {
		// Set the key store to use for validating the server cert.
		System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
		System.setProperty("javax.net.debug", "ssl,handshake");
		SSLClient client = new SSLClient();
		Socket s = client.clientWithCert();
		
		PrintWriter writer = new PrintWriter(s.getOutputStream());
		BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
		writer.println("hello");
		writer.flush();
		System.out.println(reader.readLine());
		s.close();
	}

	private Socket clientWithoutCert() throws Exception {
		SocketFactory sf = SSLSocketFactory.getDefault();
		Socket s = sf.createSocket("localhost", 8443);
		return s;
	}

	private Socket clientWithCert() throws Exception {
		SSLContext context = SSLContext.getInstance("TLS");
		KeyStore ks = KeyStore.getInstance("jceks");
		
		ks.load(new FileInputStream(CLIENT_KEY_STORE), null);
		KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
		kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
		context.init(kf.getKeyManagers(), null, null);
		
		SocketFactory factory = context.getSocketFactory();
		Socket s = factory.createSocket("localhost", 8443);
		return s;
	}
}


通过比对单向认证的日志输出,我们可以发现双向认证时,多出了服务端认证客户端证书的步骤:

Bash代码 复制代码  收藏代码
  1. *** CertificateRequest   
  2. Cert Types: RSA, DSS   
  3. Cert Authorities:   
  4. <CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>   
  5. <CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>   
  6. *** ServerHelloDone  
*** CertificateRequest
Cert Types: RSA, DSS
Cert Authorities:
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
*** ServerHelloDone


Bash代码 复制代码  收藏代码
  1. *** CertificateVerify   
  2. main, WRITE: TLSv1 Handshake, length = 134  
  3. main, WRITE: TLSv1 Change Cipher Spec, length = 1  
*** CertificateVerify
main, WRITE: TLSv1 Handshake, length = 134
main, WRITE: TLSv1 Change Cipher Spec, length = 1


在 @*** ServerHelloDone@ 之前,服务端向客户端发起了需要证书的请求 @*** CertificateRequest@ 。

在客户端向服务端发出 @Change Cipher Spec@ 请求之前,多了一步客户端证书认证的过程 @*** CertificateVerify@ 。

客户端与服务端互相认证证书的情景,可参考下图:



参考资料:

fn1. Tomcat双向SSL认证的配置 - http://www.javaeedev.com/blog/article.jspx?articleId=ff808081198fb524011993a9bb7a029a

fn2. Understanding SSL - http://developerspoint.wordpress.com/2008/06/21/understanding-ssl/

fn3. Change Cipher Spec Protocol - http://www.pierobon.org/ssl/ch2/ccs.htm

fn4. SSL & TLS Essentials: Securing the Web - http://www.amazon.com/SSL-TLS-Essentials-Securing-Web/dp/0471383546/ref=sr_1_1?ie=UTF8&s=books&qid=1280891641&sr=8-1
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(java 基础知识) Java 安全套接字--javax.net.ssl 的相关文章

  • (Java 基础知识) Java反射

    本篇文章依旧采用小例子来说明 因为我始终觉的 案例驱动是最好的 要不然只看理论的话 看了也不懂 不过建议大家在看完文章之后 在回过头去看看理论 会有更好的理解 下面开始正文 案例1 通过一个对象获得完整的包名和类名 1 2 3 4 5 6
  • Java线程:volatile关键字

    本文转载至 http lavasoft blog 51cto com 62575 222076 Java线程 volatile关键字 Java 语言包含两种内在的同步机制 同步块 或方法 和 volatile 变量 这两种机制的提出都是为了
  • Java 终止嵌套for 循环总结

    public class NestedLoopDemo public static void main String args 第一步 构建嵌套for 循环 for int i 0 i lt 10 i for int j 0 j lt 10
  • Java 反射 与 主要API

    控制你的大脑 控制你的行为 你会得到更多 收获很多 文章目录 一 反射相关的主要API 二 代码例子演示 三 反射测试类 一 反射相关的主要API API 名称 代表含义 Java lang class 代表一个类 java lang re
  • java并发包:读写锁

    本文转载至 http blog csdn net a910626 article details 51900954 ReadWriteLock是jdk5中提供的读写分离锁 读写分离锁可以有效的帮助减少锁竞争 以提升性能 用锁分离的机制来提升
  • java 清除字符串空格

    JAVA中去掉空格 1 String trim trim 是去掉首尾空格 2 str replace 去掉所有空格 包括首尾 中间 String str hell o String str2 str replaceAll System ou
  • Java线程:线程的同步-同步方法

    本文转载至 http lavasoft blog 51cto com 62575 221914 Java线程 线程的同步 同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段 线程的同步是Java多线程编程的难点 往往开发者搞不清楚什
  • Java线程:线程的交互

    本文转载至 http lavasoft blog 51cto com 62575 99157 线程交互是比较复杂的问题 SCJP要求不很基础 给定一个场景 编写代码来恰当使用等待 通知和通知所有线程 一 线程交互的基础知识 SCJP所要求的
  • (java 功能篇)java 读取Propety配置文件

    java效果代码 源代码 package com rf test import java io IOException import java io InputStream import java util Properties publi
  • java 线程:概念与原理

    本文转载至 http lavasoft blog 51cto com 62575 99150 一 操作系统中线程和进程的概念 现在的操作系统是多任务操作系统 多线程是实现多任务的一种方式 进程是指一个内存中运行的应用程序 每个进程都有自己独
  • Java线程:新特征-障碍器

    本文转载至 http lavasoft blog 51cto com 62575 222738 Java5中 添加了障碍器类 为了适应一种新的设计需求 比如一个大型的任务 常常需要分配好多子任务去执行 只有当所有子任务都执行完成时候 才能执
  • Java 生成随机数全数字方式

    生成9位随机数字 System out println int Math random 9 1 100000000 生成8位随机数字 System out println int Math random 9 1 10000000 生成6位随
  • Java线程:新特征-有返回值的线程

    本文转载至 http lavasoft blog 51cto com 62575 222082 在Java5之前 线程是没有返回值的 常常为了 有 返回值 破费周折 而且代码很不好写 或者干脆绕过这道坎 走别的路了 现在Java终于有可返回
  • Java线程:新特征-信号量

    本文转载至 http lavasoft blog 51cto com 62575 222469 Java线程 新特征 信号量 Java的信号量实际上是一个功能完毕的计数器 对控制一定资源的消费与回收有着很重要的意义 信号量常常用于多线程的代
  • 代码质量保障第2讲:单元测试 - 浅谈单元测试

    代码质量保障第2讲 单元测试 浅谈单元测试 本文是代码质量保障第2讲 浅谈单元测试 单元测试 unit testing 是指对软件中的最小可测试单元进行检查和验证 这是基础 所以围绕着单元测试 我从网上搜集和总结了相关的概念 以助你完善体系
  • (java 基础知识) Java 安全套接字--javax.net.ssl

    有关SSL的原理和介绍在网上已经有不少 对于Java下使用keytool生成证书 配置SSL通信的教程也非常多 但如果我们不能够亲自动手做一个SSL Sever和SSL Client 可能就永远也不能深入地理解Java环境下 SSL的通信是
  • (Java 基础知识) Java线程池

    ExecutorService 建立多线程的步骤 1 定义线程类 class Handler implements Runnable 2 建立ExecutorService线程池 ExecutorService executorServic
  • java并发包:生产者消费者模式

    本文转载至 http blog csdn net a910626 article details 51900974 生产者消费者模式是一个经典的多线程设计模式 它为多线程间的协作提供了良好的解决方案 在生产者消费者模式中 通常有两类线程 即
  • Java线程:线程状态的转换

    本文转载至 http lavasoft blog 51cto com 62575 99153 一 线程状态类型 1 新建状态 New 新创建了一个线程对象 2 就绪状态 Runnable 线程对象创建后 其他线程调用了该对象的start 方
  • (Java基础知识) 几种Java任务调度比较

    前言 任务调度是指基于给定时间点 给定时间间隔或者给定执行次数自动执行任务 本文由浅入深介绍四种任务调度的 Java 实现 Timer ScheduledExecutor 开源工具包 Quartz 开源工具包 JCronTab 此外 为结合

随机推荐

  • 2023-9-10 集合-Nim游戏

    题目链接 集合 Nim游戏 include
  • uniapp体验如何?uniapp开发感受及优缺点分析

    很多开发者在没有入坑uniapp之前 都想知道uniapp的体验到底怎么样 开发爽不爽 优缺点是怎么样的 坑多不多 今天就以我个人开发的项目来谈一谈 首先介绍一下我在公司开发的项目 购靓号APP 也有h5 购靓号小程序 扫描下方二维码可以快
  • 华为OD机试 - 矩形相交的面积 - 逻辑分析(Java 2023 B卷 100分)

    目录 专栏导读 一 题目描述 二 输入描述 三 输出描述 1 输入 2 输出 3 说明 四 解题思路 五 Java算法源码 六 效果展示 1 输入 2 输出 3 说明 华为OD机试 2023B卷题库疯狂收录中 刷题点这里 专栏导读 本专栏收
  • 在HTML页面加载完毕后运行某个js.

    js方法 以下为jQuery方法 需要引用jQuery文件 或者其简写 function
  • CSDN删除、修改分类标签

    点击头像 进入 管理博客 点击个人分类管理 就可以修改分类标签 备注 删除标签后 该标签下的文章还在 不会丢失
  • LeetCode Number Complement

    class Solution public int findComplement int num int temp i 1 j temp num while temp 0 temp 2 i 2 j i 1 num return j temp
  • 部分选主元matlab,列选主元的高斯消去法-matlab

    大家对高斯消去法应该比较了解了 高代和线性代数中做的已经不少了 但是计算机实现的时候还是要注意一些东西 列选主元的高斯消去法 function X lufact my A B Inpiut A 是系数矩阵 B是右端项 Output x是解
  • 三层网络结构(核心层、汇聚层 、接入层)

    三层网络架构设计的网络有三个层次 核心层 网络的高速交换 汇聚层 提供基于策略的连接 接入层 将工作站接入网络 核心层 在核心层应该采用高带宽的交换机 核心层的设备采用双机冗余热备份是非常必要的 也可以使用负载均衡功能来改善网络性能 对于网
  • 软件测试复习(5)----基本路径测试中的细节

    在做基本路径测试的题中又有了新的发现 希望大家可以注意 另外 如果你是第一次看 可以先看一下我之前讲的基本路径方法的步骤 先看一下题目 我们不讲如何做这道题 只是讲一下我通过这道题关于知识点新的理解 题目 答案 知识点 细节 逻辑与 逻辑或
  • 雪崩击穿/齐纳击穿

    雪崩击穿 发生在掺杂浓度较低 自由电子冲撞价离子 温度越高 雪崩击穿需要的击穿电压越高 齐纳击穿 发生在掺杂浓度比较高的时候 自由电子直接跳出共价键 温度越高 齐纳击穿需要的击穿电压越低
  • 适用于小白-vs2019的下载与安装

    学习c语言时 少不了编译器的使用 那么传统的dev c 过于古老的同时 也不再更新 而大学授课老师一般要求安装vc 6 0 学校机房也一般为vc 6 0 但这两种都还是过于古老 现在机构授课过程一般采用vs2019 使用起来更加便捷 清晰
  • 将CelebA数据集所给标签转化为MTCNN中训练集所需标签

    img dir r D datasets CelebA Img img celeba 7z img celeba anno src r D datasets CelebA Anno list bbox celeba txt anno lan
  • Oracle数据库索引管理-基于函数索引ORA-30553

    在ORACALE数据库中 创建函数索引时 若函数是自定义的函数 须在定义函数时加上deterministic关健字 不然会出现 函数不确定 的错误提示 创建函数索引 ORA 30553 函数不确定 原函数如下 create or repla
  • Hibernate HQL绑定变量

    Hibernate HQL查询 插入 更新 update 实例 1 实体查询 有关实体查询技术 其实我们在先前已经有多次涉及 比如下面的例子 String hql from User user List list session Creat
  • 毕业设计 - 个人博客系统的设计与实现【源码+论文】

    文章目录 前言 一 项目设计 1 模块设计 博主功能用例 游客功能用例 2 实现效果 二 部分源码 项目源码 前言 今天学长向大家分享一个 Java web 毕业设计 项目 个人博客系统的设计与实现 一 项目设计 1 模块设计 博主功能用例
  • Linux驱动_多点电容触摸

    一丶Linux下多点电容触摸驱动框架 电容触摸屏IC是FT5426 为IIC协议芯片 因此需要编写IIC驱动 触摸IC会发出中断信号 并在中断服务函数中上报信息 因此需要编写中断框架 触摸屏向Linux内核上报的信息都属于Input子系统
  • 基于Unity3d 引擎的Android游戏优化

    最近项目进入收尾阶段 之前对项目做了很多优化 mesh合并 减少DrawCall和模型骨骼以及物理计算 合并材质球 优化代码等等 在IOS上还好 但是Android上 试过几款手机 从低端到高端 发现性能还是很差 所以又花了几天来研究摸索
  • C#中索引器

    C 中索引器和数组的使用类似 public class Test private readonly string name new string 10 public string this int index get return name
  • 【论文速览】根据人脑fMRI信号重建图像 Image Reconstruction from human brain activity

    文章目录 前言 文章一 研究背景 主要方法 部分实验结果 总结与思考 参考资料 文章二 研究背景 主要方法 部分实验结果 总结与思考 前言 人类的视觉神经系统对于真实世界的视觉刺激有着非凡的感知与理解能力 比如我们能够准确地识别物体距离和三
  • (java 基础知识) Java 安全套接字--javax.net.ssl

    有关SSL的原理和介绍在网上已经有不少 对于Java下使用keytool生成证书 配置SSL通信的教程也非常多 但如果我们不能够亲自动手做一个SSL Sever和SSL Client 可能就永远也不能深入地理解Java环境下 SSL的通信是