Java网络编程二:Java Secure(SSL/TLS) Socket实现

Java网络编程系列:

Java网络编程一:Java Socket简例

Java网络编程二:Java Secure(SSL/TLS) Socket实现

Java网络编程三:Java NIO-非阻塞通信

 

通信端无需向对方证明自己的身份,则称该端处于“客户模式”,否则称其处于“服务器模式”,无论是客户端还是服务器端,都可处于“客户模式”或者“服务器模式”,但是对于通信双方来说,只能有一方处于“服务模式”,而另一方则必须处于“客户模式”

一,证书部分

首先使用java自带的keytool工具生成所需的证书:

1,创建服务端keystore

keytool -genkey -keystore c:\sean_app\server.jks -storepass 1234sp -keyalg RSA -keypass 1234kp

2,创建客户端keystore

keytool -genkey -keystore c:\sean_app\client.jks -storepass 1234sp -keyalg RSA -keypass 1234kp

3,导出服务端证书

keytool -export -keystore c:\sean_app\server.jks -storepass 1234sp -file c:\sean_app\server.cer

4,导出客户端证书

keytool -export -keystore c:\sean_app\client.jks -storepass 1234sp -file c:\sean_app\client.cer

5,将服务端证书导入到客户端trustkeystroe

keytool -import -keystore c:\sean_app\clientTrust.jks -storepass 1234sp -file c:\sean_app\server.cer

6,将客户端证书导入到服务端trustkeystroe

keytool -import -keystore c:\sean_app\serverTrust.jks -storepass 1234sp -file c:\sean_app\client.cer

 

二,代码部分

使用JDK1.5.0_22和JDK1.6.0_45测试单向认证和双向认证均通过,唯一需要引入的包为log4j-1.2.14.jar,用来记录日志

代码结构如下:

Java网络编程二:Java Secure(SSL/TLS) Socket实现_第1张图片

客户端认证:

package com.test.client.auth;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import com.test.server.config.Configuration;

public class Auth {
	private static SSLContext sslContext;
	
	public static SSLContext getSSLContext() throws Exception{
		Properties p = Configuration.getConfig();
		String protocol = p.getProperty("protocol");
		String clientTrustCerFile = p.getProperty("clientTrustCer");
		String clientTrustCerPwd = p.getProperty("clientTrustCerPwd");
		
		//Trust Key Store
		KeyStore keyStore = KeyStore.getInstance("JKS");
		keyStore.load(new FileInputStream(clientTrustCerFile), 
				clientTrustCerPwd.toCharArray());  
			
		
		TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); 
		trustManagerFactory.init(keyStore); 
		TrustManager[] tms = trustManagerFactory.getTrustManagers();
		
		KeyManager[] kms = null;
		if(Configuration.getConfig().getProperty("authority").equals("2")){	
			String clientCerFile = p.getProperty("clientCer");
			String clientCerPwd = p.getProperty("clientCerPwd");
			String clientKeyPwd = p.getProperty("clientKeyPwd");
			
			//Key Store
			keyStore = KeyStore.getInstance("JKS");  
			keyStore.load(new FileInputStream(clientCerFile), 
					clientCerPwd.toCharArray());  
			
			KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); 
			keyManagerFactory.init(keyStore, clientKeyPwd.toCharArray()); 
			kms = keyManagerFactory.getKeyManagers();
		}
		sslContext = SSLContext.getInstance(protocol);
		sslContext.init(kms, tms, null);  
		
		return sslContext;
	}
}

客户端主程序:

package com.test.client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Properties;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import org.apache.log4j.Logger;

import com.test.client.auth.Auth;
import com.test.server.config.Configuration;
import com.test.tools.MyHandshakeCompletedListener;
import com.test.tools.SocketIO;

public class Client {
	static Logger logger = Logger.getLogger(Client.class);
	private SSLContext sslContext;
	private int port = 10000;
	private String host = "127.0.0.1";
	private SSLSocket socket;
	private Properties p;
	
	public Client(){
		try {
			p = Configuration.getConfig();
			
			sslContext = Auth.getSSLContext();
			SSLSocketFactory factory = (SSLSocketFactory) sslContext.getSocketFactory();  
			socket = (SSLSocket)factory.createSocket(); 
			String[] pwdsuits = socket.getSupportedCipherSuites();
			//socket可以使用所有支持的加密套件
			socket.setEnabledCipherSuites(pwdsuits);
			//默认就是true
			socket.setUseClientMode(true);
			
			SocketAddress address = new InetSocketAddress(host, port);
			socket.connect(address, 0);
			
			MyHandshakeCompletedListener listener = new MyHandshakeCompletedListener();
			socket.addHandshakeCompletedListener(listener);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("socket establish failed!");
		}
	}
	
	public void request(){
		try{
			String encoding = p.getProperty("socketStreamEncoding");
			
			DataOutputStream output = SocketIO.getDataOutput(socket);
			String user = "name";
			byte[] bytes = user.getBytes(encoding);
			int length = bytes.length;
			int pwd = 123;
			
			
			output.write(length);
			output.write(bytes);
			output.write(pwd);
			
			DataInputStream input = SocketIO.getDataInput(socket);
			length = input.readShort();
			bytes = new byte[length];
			input.read(bytes);
			
			logger.info("request result:"+new String(bytes,encoding));
		}catch(Exception e){
			e.printStackTrace();
			logger.error("request error");
		}finally{
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args){
		Client client = new Client();
		client.request();
	}
}

服务端认证:

package com.test.server.auth;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import com.test.server.config.Configuration;

public class Auth {
	private static SSLContext sslContext;
	
	public static SSLContext getSSLContext() throws Exception{
		Properties p = Configuration.getConfig();
		String protocol = p.getProperty("protocol");
		String serverCer = p.getProperty("serverCer");
		String serverCerPwd = p.getProperty("serverCerPwd");
		String serverKeyPwd = p.getProperty("serverKeyPwd");
		
		//Key Stroe
		KeyStore keyStore = KeyStore.getInstance("JKS");  
		keyStore.load(new FileInputStream(serverCer), 
				serverCerPwd.toCharArray());  
		
		KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); 
		keyManagerFactory.init(keyStore, serverKeyPwd.toCharArray()); 
		KeyManager[] kms = keyManagerFactory.getKeyManagers();
		
		TrustManager[] tms = null;
		if(Configuration.getConfig().getProperty("authority").equals("2")){
			String serverTrustCer = p.getProperty("serverTrustCer");
			String serverTrustCerPwd = p.getProperty("serverTrustCerPwd");
			
			//Trust Key Store
			keyStore = KeyStore.getInstance("JKS");  
			keyStore.load(new FileInputStream(serverTrustCer), 
					serverTrustCerPwd.toCharArray());  
			
			TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); 
			trustManagerFactory.init(keyStore); 
			tms = trustManagerFactory.getTrustManagers();
		}
		sslContext = SSLContext.getInstance(protocol);
		sslContext.init(kms, tms, null);  
		
		return sslContext;
	}
}

服务端主程序:

package com.test.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

import org.apache.log4j.Logger;

import com.test.server.auth.Auth;
import com.test.server.business.Job;
import com.test.server.config.Configuration;

public class Server {
	static Logger logger = Logger.getLogger(Server.class);
	private SSLContext sslContext;
	private SSLServerSocketFactory sslServerSocketFactory;
	private SSLServerSocket sslServerSocket;
	private final Executor executor;
	
	public Server() throws Exception{
		Properties p = Configuration.getConfig();
		Integer serverListenPort = Integer.valueOf(p.getProperty("serverListenPort"));
		Integer serverThreadPoolSize = Integer.valueOf(p.getProperty("serverThreadPoolSize"));
		Integer serverRequestQueueSize = Integer.valueOf(p.getProperty("serverRequestQueueSize"));
		Integer authority = Integer.valueOf(p.getProperty("authority"));
		
		executor = Executors.newFixedThreadPool(serverThreadPoolSize);
		
		sslContext = Auth.getSSLContext();
		sslServerSocketFactory = sslContext.getServerSocketFactory();  
		//只是创建一个TCP连接,SSL handshake还没开始
		//客户端或服务端第一次试图获取socket输入流或输出流时,
		//SSL handshake才会开始
		sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(); 
		String[] pwdsuits = sslServerSocket.getSupportedCipherSuites();
		sslServerSocket.setEnabledCipherSuites(pwdsuits);
		//默认是client mode,必须在握手开始之前调用
		sslServerSocket.setUseClientMode(false);
		if(authority.intValue() == 2){
			//只有设置为server mode,该配置才生效
			//如果客户端不提供其证书,通信将会结束
			sslServerSocket.setNeedClientAuth(true);
		}else{
			//只有设置为server mode,该配置才生效
			//即使客户端不提供其证书,通信也将继续
			sslServerSocket.setWantClientAuth(true);
		}

		sslServerSocket.setReuseAddress(true);
		sslServerSocket.setReceiveBufferSize(128*1024);
		sslServerSocket.setPerformancePreferences(3, 2, 1);
		sslServerSocket.bind(new InetSocketAddress(serverListenPort),serverRequestQueueSize);
			
		logger.info("Server start up!");
		logger.info("server port is:"+serverListenPort);
	}

	private void service(){		
		while(true){
			SSLSocket socket = null;
			try{
				logger.debug("Wait for client request!");
				socket = (SSLSocket)sslServerSocket.accept();
				logger.debug("Get client request!");
				
				Runnable job = new Job(socket);
				executor.execute(job);	
			}catch(Exception e){
				logger.error("server run exception");
				try {
					socket.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Server server;
		try{
			 server = new Server();
			 server.service();
		}catch(Exception e){
			e.printStackTrace();
			logger.error("server socket establish error!");
		}
	}
}

服务端业务执行线程:

package com.test.server.business;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.test.server.config.Configuration;
import com.test.tools.SocketIO;

public class Job implements Runnable {
	static Logger logger = Logger.getLogger(Job.class);
	private Socket socket;
	
	public Job(Socket socket){
		this.socket = socket;
	}

	public void run() {
		Properties p = Configuration.getConfig();
		String encoding = p.getProperty("socketStreamEncoding");
		
		DataInputStream input = null;
		DataOutputStream output = null;
		try{
			input = SocketIO.getDataInput(socket);
		
			int length = input.read();
			byte[] bytes = new byte[length];
			input.read(bytes);
			String user = new String(bytes,encoding).trim();
			int pwd = input.read();
			
			String result = null;
			if(null != user && !user.equals("") && user.equals("name") && pwd == 123){
				result = "login success";
			}else{
				result = "login failed";
			}
			logger.info("request user:"+user);
			logger.info("request pwd:"+pwd);
			
			output = SocketIO.getDataOutput(socket);
			
			bytes = result.getBytes(encoding);
			length = (short)bytes.length;
			output.writeShort(length);
			output.write(bytes);
			
			logger.info("response info:"+result);
		}catch(Exception e){
			e.printStackTrace();
			logger.error("business thread run exception");
		}finally{
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
				logger.error("server socket close error");
			}
		}
	}
}

配置文件类:

package com.test.server.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;

public class Configuration {
	private static Properties config;
	
	static Logger logger = Logger.getLogger(Configuration.class);
	
	public static Properties getConfig(){
		try{
			if(null == config){
				File configFile = new File("./conf/conf.properties");
				if(configFile.exists() && configFile.isFile()
						&& configFile.canRead()){
					InputStream input = new FileInputStream(configFile);
					config = new Properties();
					config.load(input);
				}
			}
		}catch(Exception e){
			//default set
			config = new Properties();
			config.setProperty("protocol", "TLSV1");
			config.setProperty("serverCer", "./certificate/server.jks");
			config.setProperty("serverCerPwd", "1234sp");
			config.setProperty("serverKeyPwd", "1234kp");
			config.setProperty("serverTrustCer", "./certificate/serverTrust.jks");
			config.setProperty("serverTrustCerPwd", "1234sp");
			config.setProperty("clientCer", "./certificate/client.jks");
			config.setProperty("clientCerPwd", "1234sp");
			config.setProperty("clientKeyPwd", "1234kp");
			config.setProperty("clientTrustCer", "./certificate/clientTrust.jks");
			config.setProperty("clientTrustCerPwd", "1234sp");
			config.setProperty("serverListenPort", "10000");
			config.setProperty("serverThreadPoolSize", "5");
			config.setProperty("serverRequestQueueSize", "10");
			config.setProperty("socketStreamEncoding", "UTF-8");
		}
		return config;
	}
}

接口工具类:

package com.test.tools;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class SocketIO{
	public static DataInputStream getDataInput(Socket socket) throws IOException{
		DataInputStream input = new DataInputStream(socket.getInputStream());
		return input;
	}
	
	public static DataOutputStream getDataOutput(Socket socket) throws IOException{
		DataOutputStream out = new DataOutputStream(socket.getOutputStream());
		return out;
	}
}

握手工具类:

package com.test.tools;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;

public class MyHandshakeCompletedListener implements HandshakeCompletedListener {

	public void handshakeCompleted(HandshakeCompletedEvent arg0) {
		System.out.println("Handshake finished successfully");
	}
}

配置文件:

#1:单向认证,只有服务器端需证明其身份  
#2:双向认证,服务器端和客户端都需证明其身份
authority=2
#通信协议
protocol=TLSV1
#服务端证书信息
serverCer=./certificate/server.jks
#keystore的storepass
serverCerPwd=1234sp
#keystore的keypass
serverKeyPwd=1234kp
#服务端证书信息
serverTrustCer=./certificate/serverTrust.jks
serverTrustCerPwd=1234sp
#客户端证书信息
clientCer=./certificate/client.jks
clientCerPwd=1234sp
clientKeyPwd=1234kp
clientTrustCer=./certificate/clientTrust.jks
clientTrustCerPwd=1234sp
#服务器监听端口,注意root权限
serverListenPort=10000
#服务器线程池线程数(2*核数+1)
serverThreadPoolSize=5
#服务器Socket请求队列长度  
serverRequestQueueSize=10
#字节流编码  
socketStreamEncoding=utf-8

日志文件:

log4j.rootLogger=debug,logOutput,fileLogOutput

log console out put 
log4j.appender.logOutput=org.apache.log4j.ConsoleAppender
log4j.appender.logOutput.layout=org.apache.log4j.PatternLayout
log4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n

#log file out put
log4j.appender.fileLogOutput=org.apache.log4j.RollingFileAppender
log4j.appender.fileLogOutput.File=./log/server.log
log4j.appender.fileLogOutput.MaxFileSize=1000KB
log4j.appender.fileLogOutput.MaxBackupIndex=3
log4j.appender.fileLogOutput.layout=org.apache.log4j.PatternLayout
log4j.appender.fileLogOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n

运行后的结果:

客户端:

Handshake finished successfully
INFO[15-02-03 09:53:49][com.test.client.Client] -> request result:login success

服务端:

INFO[15-02-03 09:53:45][com.test.server.Server] -> Server start up!
INFO[15-02-03 09:53:45][com.test.server.Server] -> server port is:10000
DEBUG[15-02-03 09:53:45][com.test.server.Server] -> Wait for client request!
DEBUG[15-02-03 09:53:48][com.test.server.Server] -> Get client request!
DEBUG[15-02-03 09:53:48][com.test.server.Server] -> Wait for client request!
INFO[15-02-03 09:53:49][com.test.server.business.Job] -> request user:name
INFO[15-02-03 09:53:49][com.test.server.business.Job] -> request pwd:123
INFO[15-02-03 09:53:49][com.test.server.business.Job] -> response info:login success

 

PS:

1,不能随意的使用close()方法关闭socket输入输出流,使用close()方法关闭socket输入输出流会导致socket本身被关闭

2,字符串必须按照指定的编码转换为字节数组,字节数组也必须通过相同的编码转换为字符串,否则将会出现乱码

String[] pwdsuits = socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(pwdsuits);

加密套件(ciphersuits)包括一组加密参数,这些参数指定了加密算法、密钥长度等加密等信息。

[SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, ...

你可能感兴趣的:(java,socket,SslSocket,java网络编程,SSLTLS)