开篇必看
我本机环境:
win7、idea2016、jdk1.8、maven、tomcat7
我的每一个实现都包含服务端和客户端,分别为TCP实现和HTTP实现。其中TCP实现仅靠jdk本身jar包就可 实现,HTTP实现需要依赖其他jar包。
文章中斜体字都是我添加的需要注意的额外解释。
首先,添加maven依赖。
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
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<a.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<seedLength;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(), <span style="color: #9876aa; font-style: itali
这里用基本上都是原生JDK有的类
接下来我们来看看HTTP方式的实现
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);
}
}
代码贴完可以测试一下