Java Secure(SSL/TLS) Socket实现

来源:互联网 发布:陈奕迅 苦瓜 知乎 编辑:程序博客网 时间:2024/04/29 04:38

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

一,证书部分

首先使用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,用来记录日志

代码结构如下:


客户端认证:

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 StoreKeyStore 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 StorekeyStore = 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);//默认就是truesocket.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 StroeKeyStore 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 StorekeyStore = 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 setconfig = 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的storepassserverCerPwd=1234sp#keystore的keypassserverKeyPwd=1234kp#服务端证书信息serverTrustCer=./certificate/serverTrust.jksserverTrustCerPwd=1234sp#客户端证书信息clientCer=./certificate/client.jksclientCerPwd=1234spclientKeyPwd=1234kpclientTrustCer=./certificate/clientTrust.jksclientTrustCerPwd=1234sp#服务器监听端口,注意root权限serverListenPort=10000#服务器线程池线程数(2*核数+1)serverThreadPoolSize=5#服务器Socket请求队列长度  serverRequestQueueSize=10#字节流编码  socketStreamEncoding=utf-8

日志文件:

log4j.rootLogger=debug,logOutput,fileLogOutputlog console out put log4j.appender.logOutput=org.apache.log4j.ConsoleAppenderlog4j.appender.logOutput.layout=org.apache.log4j.PatternLayoutlog4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n#log file out putlog4j.appender.fileLogOutput=org.apache.log4j.RollingFileAppenderlog4j.appender.fileLogOutput.File=./log/server.loglog4j.appender.fileLogOutput.MaxFileSize=1000KBlog4j.appender.fileLogOutput.MaxBackupIndex=3log4j.appender.fileLogOutput.layout=org.apache.log4j.PatternLayoutlog4j.appender.fileLogOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n

运行后的结果:

客户端:

Handshake finished successfullyINFO[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:10000DEBUG[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:nameINFO[15-02-03 09:53:49][com.test.server.business.Job] -> request pwd:123INFO[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, ...

原创粉丝点击