开篇必看
我本机环境:
win7、idea2016、jdk1.8、maven、tomcat7
我的每一个实现都包含服务端和客户端,分别为TCP实现和HTTP实现。其中TCP实现仅靠jdk本身jar包就可 实现,HTTP实现需要依赖其他jar包。
文章中斜体字都是我添加的需要注意的额外解释。
首先,添加maven依赖。
commons-httpclient commons-httpclient 3.1
com.google.code.gson gson 2.8.0
TCP实现
参考文章:http://kingj.iteye.com/blog/2103662。
该文章是我的主要学习资料,但是我实现后根据其他文章改动了一些地方,并对实现本身进行了重构(精简),当然也可以完全依照该文章进行TCP的HTTPS实现。
首先打开cmd去生成私钥库,命令行:keytool -genkey -alias password -keypass password -keyalg RSA -keysize 1024 -keystore https.keystore -storepass password
命令执行后,会在命令行所处文件夹内生成私钥库‘https.keystore’。
接着根据私钥生成证书,命令行:keytool -export -keystore https.keystore -alias password -file https.crt -storepass password
同样的,执行后会在所处文件夹内生成证书‘https.crt’。
注意!!命令行中password是你自己的私钥口令。该命令会生成私钥库‘https.keystore’,而私钥密码就是读取私钥库时所需的口令。这个口令由个人进行自定义,需要特别记下来。这里没有生成公钥,因为公钥可通过证书获得
有了证书后就直接上代码了,需要注意的地方我都在代码中进行了注释
import javax.crypto.*; import java.security.*; import java.util.Random; /** 加解密,字符串与字节的转换 */ public class HttpsMockBase { protected static PrivateKey privateKey; protected static PublicKey publicKey; protected static String hash; public static boolean byteEquals(byte a[],byte[] b){ boolean equals = true; if(a == null || b == null){ equals = false; } if(a != null && b != null){ if(a.length != b.length){ equals = false; }else{ for(int i = 0;i.length;i++){ if(a[i] != b[i]){ equals = false; break; } } } } return equals; } public static byte[] decrypt(byte data[]) throws Exception{ // 对数据解密 Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(data); } public static byte[] decrypt(byte data[],SecureRandom seed) throws Exception{ // 对数据解密 Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateKey,seed); return cipher.doFinal(data); } public static byte[] decryptByPublicKey(byte data[],SecureRandom seed) throws Exception{ if(publicKey == null){ publicKey = CertifcateUtils.readPublicKeys(); } // 对数据解密 Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm()); if(seed == null){ cipher.init(Cipher.DECRYPT_MODE, publicKey); }else{ cipher.init(Cipher.DECRYPT_MODE, publicKey,seed); } return cipher.doFinal(data); } public static byte[] decryptByDes(byte data[],SecureRandom seed) throws Exception{ if(publicKey == null){ publicKey = CertifcateUtils.readPublicKeys(); } // 对数据解密 Cipher cipher = Cipher.getInstance("DES"); if(seed == null){ cipher.init(Cipher.DECRYPT_MODE, publicKey); }else{ cipher.init(Cipher.DECRYPT_MODE, publicKey,seed); } return cipher.doFinal(data); } public static byte[] encryptByPublicKey(byte[] data, SecureRandom seed) throws Exception { if(publicKey == null){ publicKey = CertifcateUtils.readPublicKeys(); } // 对数据加密 Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm()); if(seed == null){ cipher.init(Cipher.ENCRYPT_MODE, publicKey); }else{ cipher.init(Cipher.ENCRYPT_MODE, publicKey,seed); } return cipher.doFinal(data); } public static String byte2hex(byte[] b) { String hs = ""; String stmp = ""; for (int n = 0; n < b.length; n++) { stmp = (Integer.toHexString(b[n] & 0XFF)); if (stmp.length() == 1) { hs = hs + "0" + stmp; } else { hs = hs +" " + stmp; } } return hs.toUpperCase(); } public static byte[] cactHash(byte[] bytes) { byte[] _bytes = null; try { MessageDigest md = MessageDigest.getInstance(hash); md.update(bytes); _bytes = md.digest(); } catch (NoSuchAlgorithmException ex) { ex.printStackTrace(); } return _bytes; } static String random() { StringBuilder builder = new StringBuilder(); Random random = new Random(); int seedLength = 10; for(int i = 0;i;i++){ builder.append(digits[random.nextInt(seedLength)]); } return builder.toString(); } static char[] digits = { '0','1','2','3','4', '5','6','7','8','9', 'a','b','c','d','e', 'f','g','h','i','j' }; }
import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; import java.security.Key; import java.security.SecureRandom; /** 客户端 */ public class HttpsMockClient extends HttpsMockBase { static DataInputStream in; static DataOutputStream out; static Key key; //启动客户端,连接服务端 public static void main(String args[]) throws Exception{ int port = 80; Socket socket = new Socket("localhost",port); socket.setReceiveBufferSize(102400); socket.setKeepAlive(true); in = new DataInputStream(socket.getInputStream()); out = new DataOutputStream(socket.getOutputStream()); shakeHands(); System.out.println("------------------------------------------------------------------"); String name = "duck"; writeBytes(name.getBytes()); int len = in.readInt(); byte[] msg = readBytes(len); System.out.println("服务器反馈消息:" + byte2hex(msg)); Thread.sleep(1000*100); } //握手 private static void shakeHands() throws Exception { //第一步 客户端发送自己支持的hash算法 hash = "SHA1"; int length = hash.getBytes().length; out.writeInt(length); SocketUtils.writeBytes(out, hash.getBytes(), length); //第二步 客户端验证服务器端证书是否合法 int skip = in.readInt(); byte[] certificate = SocketUtils.readBytes(in,skip); java.security.cert.Certificate cc = CertifcateUtils.createCertiface(certificate); publicKey = cc.getPublicKey(); cc.verify(publicKey); System.out.println("客户端校验服务器端证书是否合法:" + true); //第三步 生成随机数密码并用公钥加密后进行发送 SecureRandom seed = new SecureRandom(); int seedLength = 2; byte seedBytes[] = seed.generateSeed(seedLength); System.out.println("生成的seed随机数为 : " + byte2hex(seedBytes)); System.out.println("将seed随机数用公钥加密后发送到服务器"); byte[] encrptedSeed = encryptByPublicKey(seedBytes, null); SocketUtils.writeBytes(out,encrptedSeed,encrptedSeed.length); System.out.println("加密后的seed值为 :" + byte2hex(encrptedSeed)); //第四步 客户端生成消息,用随机数密码加密后发送给服务端,并将消息生成的摘要再次发送给服务端 String message = random(); System.out.println("客户端生成消息为:" + message); System.out.println("使用随机数并用公钥对消息加密"); byte[] encrpt = encryptByPublicKey(message.getBytes(),seed); System.out.println("加密后消息位数为 : " + encrpt.length); SocketUtils.writeBytes(out,encrpt,encrpt.length); System.out.println("客户端使用SHA1计算消息摘要"); byte hash[] = cactHash(message.getBytes()); System.out.println("摘要信息为:"+byte2hex(hash)); System.out.println("消息加密完成,摘要计算完成,发送服务器"); SocketUtils.writeBytes(out,hash,hash.length); //第五步 接受服务端发送来的加密消息和摘要 int serverMessageLength = in.readInt(); byte[] serverMessage = SocketUtils.readBytes(in,serverMessageLength); System.out.println("服务器端的消息内容为 :" + byte2hex(serverMessage)); System.out.println("开始用之前生成的随机密码和DES算法解密消息,密码为:"+byte2hex(seedBytes)); byte[] desKey = DesCoder.initSecretKey(new SecureRandom(seedBytes)); key = DesCoder.toKey(desKey); byte[] decrpytedServerMsg = DesCoder.decrypt(serverMessage, key); System.out.println("解密后的消息为:"+byte2hex(decrpytedServerMsg)); int serverHashLength = in.readInt(); byte[] serverHash = SocketUtils.readBytes(in,serverHashLength); System.out.println("开始接受服务器端的摘要消息:"+byte2hex(serverHash)); byte[] serverHashValues = cactHash(decrpytedServerMsg); System.out.println("计算服务器端发送过来的消息的摘要 : " +byte2hex(serverHashValues)); //第六步 判断服务器端发送过来的hash摘要是否和计算出的摘要一致 boolean isHashEquals = byteEquals(serverHashValues,serverHash); if(isHashEquals){ System.out.println("验证完成,握手成功"); }else{ System.out.println("验证失败,握手失败"); } } public static byte[] readBytes(int length) throws Exception{ byte[] undecrpty = SocketUtils.readBytes(in,length); System.out.println("读取未解密消息:"+byte2hex(undecrpty)); return DesCoder.decrypt(undecrpty,key); } public static void writeBytes(byte[] data) throws Exception{ byte[] encrpted = DesCoder.encrypt(data,key); System.out.println("写入加密后消息:"+byte2hex(encrpted)); SocketUtils.writeBytes(out,encrpted,encrpted.length); } }
import javax.net.ServerSocketFactory; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.security.Key; import java.security.SecureRandom; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** 服务端 */ public class HttpsMockServer extends HttpsMockBase { static DataInputStream in; static DataOutputStream out; static String hash; static Key key; static ExecutorService executorService = Executors.newFixedThreadPool(20); //启动服务端 public static void main(String args[]) throws Exception{ int port = 80; ServerSocket ss = ServerSocketFactory.getDefault().createServerSocket(port); ss.setReceiveBufferSize(102400); ss.setReuseAddress(false); while(true){ try { final Socket s = ss.accept(); doHttpsShakeHands(s); executorService.execute(new Runnable() { // @Override public void run() { doSocketTransport(s); } }); }catch (Exception e){ e.printStackTrace(); } } } //这里是握手成功后进行的消息发送,根据需要自定义 private static void doSocketTransport(Socket s){ try{ System.out.println("--------------------------------------------------------"); int length = in.readInt(); byte[] clientMsg = readBytes(length); System.out.println("客户端指令内容为:" + byte2hex(clientMsg)); writeBytes("服务器已经接受请求".getBytes()); } catch (Exception ex) { ex.printStackTrace(); } } public static byte[] readBytes(int length) throws Exception{ byte[] undecrpty = SocketUtils.readBytes(in,length); System.out.println("读取未解密消息:"+byte2hex(undecrpty)); return DesCoder.decrypt(undecrpty,key); } public static void writeBytes(byte[] data) throws Exception{ byte[] encrpted = DesCoder.encrypt(data,key); System.out.println("写入加密后消息:"+byte2hex(encrpted)); SocketUtils.writeBytes(out,encrpted,encrpted.length); } //握手 private static void doHttpsShakeHands(Socket s) throws Exception { in = new DataInputStream(s.getInputStream()); out = new DataOutputStream(s.getOutputStream()); //第一步 获取客户端发送的支持的验证规则,包括hash算法,这里选用SHA1作为hash,后面用来计算消息摘要 int length = in.readInt(); in.skipBytes(4); byte[] clientSupportHash = SocketUtils.readBytes(in,length); String clientHash = new String(clientSupportHash); hash = clientHash; System.out.println("客户端发送了hash算法为:" + clientHash); //第二步 发送服务器证书到客户端,同时服务器读取私钥,公钥加密私钥解密 byte[] certificateBytes = CertifcateUtils.readCertifacates(); privateKey = CertifcateUtils.readPrivateKeys(); System.out.println("发送证书给客户端,字节长度为:"+certificateBytes.length); System.out.println("证书内容为:" + byte2hex(certificateBytes)); SocketUtils.writeBytes(out, certificateBytes, certificateBytes.length); //第三步 获取客户端通过公钥加密后的随机数密码,用私钥解密得到 int secureByteLength = in.readInt(); byte[] secureBytes = SocketUtils.readBytes(in, secureByteLength); System.out.println("读取到的客户端的随机数为:" + byte2hex(secureBytes)); byte secureSeed[] = decrypt(secureBytes); System.out.println("解密后的随机数密码为:" + byte2hex(secureSeed)); //第四步 首先获取客户端加密消息,其次获取客户端消息摘要 int skip = in.readInt(); System.out.println("第四步 获取客户端加密消息,消息长度为 :" + skip); byte[] data = SocketUtils.readBytes(in,skip); System.out.println("客户端发送的加密消息为 : " + byte2hex(data)); System.out.println("用私钥对消息解密,并计算SHA1的hash值"); byte message[] = decrypt(data,new SecureRandom(secureBytes)); byte[] serverHashBytes = cactHash(message); System.out.println("获取客户端计算的SHA1摘要"); int hashSkip = in.readInt(); byte[] clientHashBytes = SocketUtils.readBytes(in,hashSkip); System.out.println("客户端SHA1摘要为 : " + byte2hex(clientHashBytes)); //第五步 比较服务端摘要(服务端摘要通过)和客户端摘要是否一致 boolean isHashEquals = byteEquals(serverHashBytes,clientHashBytes); System.out.println("hash值比较是否一致结果为 : " + isHashEquals); //第六步 生成随机消息加密发送给客户端,并生成消息摘要再次发送 System.out.println("生成密码用于加密服务器端消息,secureRandom : " + byte2hex(secureSeed)); SecureRandom secureRandom = new SecureRandom(secureSeed); String randomMessage = random(); System.out.println("服务器端生成的随机消息为 : " + randomMessage); System.out.println("用DES算法并使用客户端生成的随机密码对消息加密"); byte[] desKey = DesCoder.initSecretKey(secureRandom); key = DesCoder.toKey(desKey); byte serverMessage[] = DesCoder.encrypt(randomMessage.getBytes(), HTTP实现
http实现相对简单很多,服务器需要配置一下tomcat的server.xml即可
请注意,这段配置是不用写的,tomcat的server.xml里本来就有这些配置,只是注释掉了。只需要找到具体所在的位置然后去掉注释,接着再把自己配置公钥和私钥按照上图一般添加进去即可。
然后启动tomcat,以https访问localhost:8443端口,就会发现https生效了。
接着来看一下如何用httpClient向服务器发起https请求。接着上代码
import com.demo.service.net.utils.*; import com.google.gson.Gson; import com.shhxzq.common.rpc.model.CommonRequest; import com.shhxzq.stock.levelTwo.service.request.AddL2MacsInfoRequest; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import java.io.ByteArrayOutputStream; import java.io.InputStream; public class HttpsTest { public static String doJsonPost(String url, String params, int timeOut) throws Exception { ProtocolSocketFactory factory = new MySecureProtocolSocketFactory(); Protocol.registerProtocol("https", new Protocol("https", factory, 443)); HttpClient httpClient = new HttpClient(); PostMethod post = new PostMethod(url); RequestEntity requestEntity = new StringRequestEntity(params, "application/json", "UTF-8");//[application/x-www-form-urlencoded], [multipart/form-data], [application/json], [text/xml] post.setRequestEntity(requestEntity); post.setRequestHeader("Connection", "close"); httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeOut); httpClient.getHttpConnectionManager().getParams().setSoTimeout(timeOut); post.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false)); post.getParams().setParameter("http.protocol.cookie-policy", CookiePolicy.BROWSER_COMPATIBILITY); httpClient.executeMethod(post); return getResponseString(post); } private static String getResponseString(PostMethod post) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); InputStream in = post.getResponseBodyAsStream(); byte[] bys = new byte[1024]; for (int n = -1; (n = in.read(bys)) != -1;) { out.write(bys, 0, n); } return new String(out.toByteArray(), "utf-8"); } public static void main(String[] args) throws Exception { String account = "l22017022330927589"; String sitePoint = "sdfsadf"; Long loginTime = 1234567890223L; String token = TokenUtil.generateToken(account, sitePoint, loginTime.toString()); System.out.println("token:" + token); //https发送 String result = doJsonPost("https://127.0.0.1:8443/updateLevel2Token.service", token, 30 * 1000); System.out.println(result); } }
代码贴完可以测试一下