HTTPS流程?
名詞解釋:
?? ?R1:隨機數1 R2:隨機數2 R3:隨機數3 publicKey:公鑰 privateKey:私鑰
要提供https服務,服務端需要安裝數字證書,在(TCP建立連接之后)TLS握手時發給客戶端,客戶端驗證證書,證書包含公鑰。
step1?
客戶端 client hello + R1 ?
服務端 server hello + R2 + publicKey
step2
客戶端 R3 publicKey加密 預主密鑰
服務端 privateKey解密得到R3
step3
客戶端與服務端使用相同的對稱密鑰算法生成會話密鑰
客戶端 R3 + R1 + R2 -> 生成會話密鑰? 主密鑰
服務端 R3 + R1 + R2 -> 生成會話密鑰
step4
正式通信 對稱密鑰(會話密鑰)加密數據
HttpsServer
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class HttpsServer {private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);private static Map<String, Object> keyMap;private static final byte[] randomList = new byte[3];private static volatile Map<SocketChannel, Boolean> channelBooleanMap = new ConcurrentHashMap<>();public static void main(String[] args) throws Exception {keyMap = init();scheduler.scheduleAtFixedRate(() -> {channelBooleanMap.forEach((k, v) -> {if (!v) {channelBooleanMap.remove(k);}});}, 0, 1, TimeUnit.SECONDS);startServer();}public static Map<String, Object> init() throws NoSuchAlgorithmException {Map<String, Object> map = EncryptUtil.initKey();return Collections.unmodifiableMap(map);}public static void startServer() throws Exception {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress(8080));Selector selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務器監聽....");while (true) {int select = selector.select();if (select > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isValid()) {if (key.isAcceptable()) {ServerSocketChannel channel = (ServerSocketChannel) key.channel();SocketChannel socketChannel = channel.accept();System.out.println("連接:" + socketChannel.getRemoteAddress());socketChannel.configureBlocking(false);channelBooleanMap.put(socketChannel, true);// 為每個連接到的channel分配一個緩沖區,channel間互相隔離ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);socketChannel.register(selector, SelectionKey.OP_READ, buffer);}if (key.isReadable()) {SocketChannel channel = (SocketChannel) key.channel();try {ByteBuffer buffer = ByteBuffer.allocate(1024);byte b;int read, count = 0;while (true) {if ((read = channel.read(buffer)) > 0) {count += read;System.out.println("count:" + count);}if (count == 14) {buffer.flip();break;}if (count == 65) {buffer.flip();break;}if (count == 32) { // 正式通信buffer.flip();break;}}b = buffer.get(0);if (b == 13) { // 第一次同步消息byte[] array = buffer.array();System.out.println(new String(array, 1, 12));byte r1 = array[13]; // 隨機數1 客戶端發給服務端System.out.println("隨機數1:" + r1); // 大小端randomList[0] = r1;} else if (b == 64) { // 第二次同步消息byte[] array = buffer.array();byte[] data = new byte[b];System.arraycopy(array, 1, data, 0, b);byte[] bytes = EncryptUtil.decryptByPrivateKey(data, EncryptUtil.getPrivateKey(keyMap));System.out.println("隨機數3=" + bytes[0]);randomList[2] = bytes[0];System.out.println("randomList:" + Arrays.toString(randomList));// 生成會話密鑰byte[] sessionKey = EncryptUtil.hmacSHA256(EncryptUtil.HmacSHA256_key.getBytes(), randomList);SetCache.add(sessionKey);System.out.println("會話密鑰:" + Arrays.toString(sessionKey));} else { // 正式通信byte[] array = new byte[32];buffer.get(array);System.out.println("array=" + Arrays.toString(array));if (Arrays.compare(array, SetCache.get()) == 0) {System.out.println("會話密鑰驗證成功");} else {System.out.println("會話密鑰驗證失敗");}}} catch (Exception e) {channelBooleanMap.put(channel, false);key.cancel();channel.close();System.out.println("有連接關閉...");break;}System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");// 注冊事件:可能會觸發多余的寫事件channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// if (flag) { // 通信結束標識
// channel.register(selector, SelectionKey.OP_READ);
// } else {
// channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// }}if (key.isWritable()) {System.out.println("觸發寫事件....");SocketChannel channel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);String serverHello = "Server Hello";buffer.put(serverHello.getBytes());byte b = (byte) new Random().nextInt(Byte.MAX_VALUE);randomList[1] = b; // 隨機數2 服務端發送給客戶端buffer.put(b);// 發送公鑰給客戶端byte[] publicKey = EncryptUtil.getPublicKey(keyMap);byte len = (byte) publicKey.length;System.out.println("publicKey.length:" + len);buffer.put(len);buffer.put(publicKey);// 注意:往channel中寫緩沖區前,必須切換到讀模式,否則無法觸發讀事件buffer.flip();if (!channelBooleanMap.get(channel)) {System.out.println("通道已關閉...");channel.register(selector, key.interestOps() & ~SelectionKey.OP_WRITE);break;}channel.write(buffer);// channel.socket().getOutputStream().flush();// channel.write(ByteBuffer.wrap(serverHello.getBytes()));
// channel.write(ByteBuffer.wrap(new byte[]{b}));System.out.println(Arrays.toString(buffer.array()));System.out.println("隨機數2:" + b);channel.register(selector, key.interestOps() & ~SelectionKey.OP_WRITE);}}}}}}
}
HttpsClient
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;public class HttpsClient {private static final List<byte[]> key = new ArrayList<>();private static final byte[] randomList = new byte[3];private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);public static void main(String[] args) throws Exception {Socket socket = new Socket("localhost", 8080);socket.setSoLinger(true, 0); // 設置關閉連接時的等待時間
// socket.setReuseAddress(true); // 設置端口重用String clientHello = "Client Hello";int anInt = new Random().nextInt(Byte.MAX_VALUE);System.out.println("Client 隨機數1:" + anInt);randomList[0] = (byte) anInt;socket.getOutputStream().write(new byte[]{13});socket.getOutputStream().write(clientHello.getBytes());socket.getOutputStream().write(anInt);InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[12];int read, count = 0;while (count < 12) {read = inputStream.read(buffer);count += read;}String cmd = new String(buffer);System.out.println("Server " + cmd);// 讀取第二個隨機數int read1 = inputStream.read();System.out.println("Server 隨機數2:" + read1);randomList[1] = (byte) read1;// 讀取公鑰int len = inputStream.read();System.out.println("publicKey len: " + len);byte[] publicKey = new byte[len];int count2 = 0;while (count2 < len) {int read2 = inputStream.read(publicKey);count2 += read2;}key.add(publicKey);System.out.println("輸入任何字符開始第二次通信...");System.in.read();// 客戶端生成第三個隨機數int r3 = new Random().nextInt(Byte.MAX_VALUE);byte[] bytes = {(byte) r3};randomList[2] = bytes[0];System.out.println("隨機數3=" + Arrays.toString(bytes));byte[] data = EncryptUtil.encryptByPublicKey(bytes, publicKey);socket.getOutputStream().write(data.length); // 64socket.getOutputStream().write(data);System.out.println("randomList:" + Arrays.toString(randomList));// 生成會話密鑰byte[] sessionKey = EncryptUtil.hmacSHA256(EncryptUtil.HmacSHA256_key.getBytes(), randomList);SetCache.add(sessionKey);System.out.println("會話密鑰:" + Arrays.toString(sessionKey));System.out.println("密鑰長度:" + SetCache.get().length);System.out.println("開始正式通信...");System.out.println("發送密鑰....");socket.getOutputStream().write(SetCache.get());System.out.println("end...");socket.close();}public void test() throws IOException {SocketChannel channel = SocketChannel.open();channel.configureBlocking(false);Selector selector = Selector.open();channel.register(selector, SelectionKey.OP_CONNECT);channel.connect(new InetSocketAddress("localhost", 8080));while (true) {int select = selector.select();if (select > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isConnectable()) {if (channel.finishConnect()) {System.out.println("客戶端連接成功...");channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);}}if (key.isWritable()) {System.out.println("Client send...");SocketChannel channel1 = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(16);String ClientHello = "Client Hello";int r1 = new Random().nextInt(100);buffer.put(ClientHello.getBytes());buffer.putInt(r1);channel1.write(buffer);channel1.register(selector, key.interestOps() & ~SelectionKey.OP_WRITE);System.out.println("Client send end...");}if (key.isReadable()) {System.out.println("Client receive...");SocketChannel channel1 = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(16);channel1.read(buffer);byte[] array = buffer.array();System.out.println(new String(array, 0, 12));System.out.println(new String(array, 12, 16));channel1.register(selector, SelectionKey.OP_WRITE);}}}}}
}
EncryptUtil
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;public final class EncryptUtil {// 非對稱加密算法private static final String KEY_ALGORITHM = "RSA";// 公鑰private static final String PUBLIC_KEY = "PUBLIC_KEY";// 私鑰private static final String PRIVATE_KEY = "PRIVATE_KEY";// RSA密鑰長度 默認1024 必須為64的倍數private static final int KEY_SIZE = 512;public static final String HmacSHA256_key = "HmacSHA256_key";public static Map<String, Object> initKey() throws NoSuchAlgorithmException {// 實例化密鑰對生成器KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);keyPairGenerator.initialize(KEY_SIZE);KeyPair keyPair = keyPairGenerator.generateKeyPair();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();Map<String, Object> keyMap = new HashMap<>();keyMap.put(PUBLIC_KEY, publicKey);keyMap.put(PRIVATE_KEY, privateKey);return keyMap;}public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {Key key = (Key) keyMap.get(PUBLIC_KEY);return key.getEncoded();}public static byte[] getPrivateKey(Map<String, Object> keyMap) throws NoSuchAlgorithmException {Key key = (Key) keyMap.get(PRIVATE_KEY);return key.getEncoded();}public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {// 取得公鑰X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);// 對數據加密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, publicKey);return cipher.doFinal(data);}public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {// 取得私鑰PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);// 生成私鑰PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);// 對數據解密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(data);}/*** HmacSHA256算法,返回的結果始終是32位** @param key 加密的鍵,可以是任何數據* @param content 待加密的內容* @return 加密后的內容* @throws Exception*/public static byte[] hmacSHA256(byte[] key, byte[] content) throws Exception {Mac hmacSha256 = Mac.getInstance("HmacSHA256");hmacSha256.init(new SecretKeySpec(key, 0, key.length, "HmacSHA256"));byte[] hmacSha256Bytes = hmacSha256.doFinal(content);return hmacSha256Bytes;}
}
SetCache
public class SetCache {private static final byte[] cache = new byte[32];public static void add(byte[] key) {System.arraycopy(key, 0, cache, 0, 32);}public static byte[] get() {return cache;}
}