今天被問到關於https原理的問題,結果由於知識掌握不牢靠,停留於表面,很多細節都無法回答清楚,於是決定把https的原理弄個明白,廢話不多說,我們先看看https的定義
(由於很久未寫博客,排版有些凌亂,請諒解)
一:什么是https協議
在說HTTPS之前先說說什么是HTTP,HTTP就是我們平時瀏覽網頁時候使用的一種協議。HTTP協議傳輸的數據都是未加密的,也就是明文的,因此使 用HTTP協議傳輸隱私信息非常不安全。為了保證這些隱私數據能加密傳輸,於是網景公司設計了SSL(Secure Sockets Layer)協議用於對HTTP協議傳輸的數據進行加密,從而就誕生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定義在RFC 6101中,之后IETF對SSL 3.0進行了升級,於是出現了TLS(Transport Layer Security) 1.0,定義在RFC 2246。實際上我們現在的HTTPS都是用的TLS協議,但是由於SSL出現的時間比較早,並且依舊被現在瀏覽器所支持,因此SSL依然是HTTPS的 代名詞,但無論是TLS還是SSL都是上個世紀的事情,SSL最后一個版本是3.0,今后TLS將會繼承SSL優良血統繼續為我們進行加密服務。目前 TLS的版本是1.2,定義在RFC 5246中,暫時還沒有被廣泛的使用。對歷史感興趣的朋友可以參考http://en.wikipedia.org/wiki/Transport_Layer_Security,這里有對TLS/SSL詳盡的敘述。
二:https的工作原理是什么
HTTPS在傳輸數據之前需要客戶端(瀏覽器)與服務端(網站)之間進行一次握手,在握 手過程中將確立雙方加密傳輸數據的密碼信息,通常情況下會配合數字證書實現。
TLS/SSL協議不僅僅是一套加密傳輸的協議,更是一件經過藝術家精心設計的藝術品,TLS/SSL中使用 非對稱加密,對稱加密以及HASH算法。
這里我們先看看這上面提到的幾種技術(如果你對這些技術已經非常了解,那么請跳過該段落,直接到段落三)
- 數字證書
數字證書是一種權威性的電子文檔,由權威公正的第三方機構,即CA中心簽發的證書。它以數字證書為核心的加密技術可以對網絡上傳輸的信息進行加密和解密、數字簽名和簽名驗證,確保網上傳遞信息的機密性、完整性。 使用了數字證書,即使您發送的信息在網上被他人截獲,甚至您丟失了個人的賬戶、密碼等信息,仍可以保證您的賬戶、資金安全。
它能提供在Internet上進行身份驗證的一種權威性電子文檔,人們可以在互聯網交往中用它來證明自己的身份和識別對方的身份。當然在數字證書認證的過程中證書認證中心(CA)作為權威的、公正的、可信賴的第三方,其作用是至關重要的.如何判斷數字認證中心公正第三方的地位是權威可信的。VeriSign、GeoTrust、Thawte 是國際權威數字證書頒發認證機構的“三巨頭”,其中,應用最廣的為VerSign簽發的電子商務數字證書。
CER(Canonical Encoding Rules,規范編碼格式) 是數字證書的一種編碼格式,它是BER(Basic Encoding Rules 基本編碼格式) 的一個變種,比BER 規定得更嚴格。后綴的證書文件有兩種編碼:
DER(Distinguished Encoding Rule 卓越編碼格式) 同樣是BER的一個變種,DER使用定長模式。
PKCS(Public-Key Cryptography Standards,公鑰加密標准) 由RSA實驗室和其他安全系統開發商為公鑰密碼的發展而制定的一系列標准。
pfx是指以pkcs#12格式存儲的證書和相應私鑰。
在Security編程中,有幾種典型的密碼交換信息文件格式:
DER-encoded certificate: .cer, .crt
PEM-encoded message: .pem
PKCS#12 Personal Information Exchange: .pfx, .p12
PKCS#10 Certification Request: .p10 .csr
PKCS#7 cert request response: .p7r
PKCS#7 binary message: .p7b .p7c .spc
cer/.crt 是用於存放證書,它是2進制形式存放
pem 跟crt/cer的區別是它以Ascii來表示
pfx/p12 用於存放個人證書/私鑰,他通常包含保護密碼,2進制方式
p10 .csr 是證書請求
p7r是CA對證書請求的回復,只用於導入
p7b .p7c .spc 以樹狀展示證書鏈(certificate chain),同時也支持單個證書,不含私鑰
- 非對稱加密算法
1976年,美國學者Dime和Henman為解決信息公開傳送和密鑰管理問題,提出一種新的密鑰交換協議,允許在不安全的媒體上的通訊雙方交換信息,安全地達成一致的密鑰,這就是"公開密鑰系統"。相對於"對稱加密算法"這種方法也叫做"非對稱加密算法"。與對稱加密算法不同,非對稱加密算法需要兩個密鑰:公開密鑰(publickey)和私有密(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數據進行加密,那么只有用對應的公開密鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
非對稱加密算法實現機密信息交換的基本過程是:甲方生成一對密鑰並將其中的一把作為公用密鑰向其它方公開;得到該公用密鑰的乙方使用該密鑰對機密信息進行加密后再發送給甲方;甲方再用自己保存的另一把專用密鑰對加密后的信息進行解密。甲方只能用其專用密鑰解密由其公用密鑰加密后的任何信息。非對稱加密算法的保密性比較好,它消除了最終用戶交換密鑰的需要,但加密和解密花費時間長、速度慢,它不適合於對文件加密而只適用於對少量數據進行加密。 經典的非對稱加密算法如RSA算法等安全性都相當高. 非對稱加密的典型應用是數字簽名。采用雙鑰密碼系統的加密方法,在一個過程中使用兩個密鑰,一個用於加密,另一個用於解密,這種加密方法稱為非對稱加密,也稱為公鑰加密,因為其中一個密鑰是公開的(另一個則需要保密)。
DH (Diffie-Hellman)
Diffie-Hellman算法(D-H算法),密鑰一致協議。是由公開密鑰密碼體制的奠基人Diffie和Hellman所提出的一種思想。簡單的說就是允許兩名用戶在公開媒體上交換信息以生成"一致"的、可以共享的密鑰。換句話說,就是由甲方產出一對密鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方密鑰對(公鑰、私鑰)。以此為基線,作為數據傳輸保密基礎,同時雙方使用同一種對稱加密算法構建本地密鑰(SecretKey)對數據加密。這樣,在互通了本地密鑰(SecretKey)算法后,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密數據,同時可以使用對方的公鑰和自己的私鑰對數據解密。不單單是甲乙雙方兩方,可以擴展為多方共享數據通訊,這樣就完成了網絡交互數據的安全通訊!該算法源於中國的同余定理——中國餘數定理。RSA
RSA公鑰加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美國麻省理工學院)開發的。RSA取名來自開發他們三者的名字。RSA是目前最有影響力的公鑰加密算法,它能夠抵抗到目前為止已知的所有密碼攻擊,已被ISO推薦為公鑰數據加密標准。RSA算法基於一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。EL Gamal
EL Gamal算法是公鑰密碼體制中的一種 ,在密碼學中占有重要的地位。但該算法所采用的冪剩余計算耗時太多的問題 ,一直是制約其廣泛應用的瓶頸問題。提出一種通過建表 ,以及對傳統二進制算法進行改進 ,即將指數進行 2 k進制化 ,減少原 BR算法迭代次數 ,提高加密解密速度的算法。ECC
ECC (Elliptical Curve Cryptography,橢圓曲線加密)算法不橢圓曲線理論為基礎,在創建密鑰時可以更快,更小,並且更有效,它是用大質數的積來產生。目前Java 6公提供了DH和RSA兩種算法實現,通過Bouncy Castle可以實現Elmal算法支持,另ECC加密算法,目前沒有開源組件提支持
- 對稱加密算法
對加密和解密使用相同密鑰的加密算法。由於其速度,對稱性加密通常在消息發送方需要加密大量數據時使用。對稱性加密也稱為密鑰加密。對稱式數據加密的方式的工作原理如圖。所謂對稱,就是采用這種加密方法的雙方使用方式用同樣的密鑰進行加密和解密。密鑰實際上是一種算法,通信發送方使用這種算法加密數據,接收方再以同樣的算法解密數據。因此對稱式加密本身不是安全的。常用的對稱加密有:
DES、IDEA、RC2、RC4、SKIPJACK算法等 。
采用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。
- HASH算法
常用的摘要算法包括MD5,SHA1,SHA256
消息摘要算法的特點:
① 無論輸入的消息有多長,計算出來的消息摘要的長度總是固定的。
② 消息摘要看起來是“隨機的”。這些比特看上去是胡亂的雜湊在一起的。
③ 一般地,只要輸入的消息不同,對其進行摘要以后產生的摘要消息也必不相同;但相同的輸入必會產生相同的輸出。
④ 消息摘要函數是無陷門的單向函數,即只能進行正向的信息摘要,而無法從摘要中恢復出任何的消息,甚至根本就找不到任何與原信息相關的信息。
⑤ 好的摘要算法,無法找到兩條消息,是它們的摘要相同。
消息摘要(Message Digest)又稱為數字摘要(Digital Digest)。它是一個唯一對應一個消息或文本的固定長度的值,它由一個單向Hash加密函數對消息進行作用而產生。如果消息在途中改變了,則接收者通過對收到消息的新產生的摘要與原摘要比較,就可知道消息是否被改變了。因此消息摘要保證了消息的完整性。消息摘要采用單向Hash 函數將需加密 的明文"摘要"成一串128bit的密文,這一串密文亦稱為數字指紋(Finger Print),它有固定的長度,且不同的明文摘要成密文,其結果總是不同的,而同樣的明文其摘要必定一致 。這樣這串摘要便可成為驗證明文是否是"真身"的"指紋"了。
HASH函數的抗沖突性使得如果一段明文稍有變化,哪怕只更改該段落的一個字母,通過哈希算法作用后都將產生不同的值。而HASH算法的單向性使得要找到到哈希值相同的兩個不 同的輸入消息,在計算上是不可能的。所以數據的哈希值,即消息摘要,可以檢驗數據的完整性。哈希函數的這種對不同的輸入能夠生成不同的值的特性使得無法找到兩個具有相同哈希值的輸入。因此,如果兩個文檔經哈希轉換后成為相同的值,就可以肯定它們是同一文檔。 所以,當希望有效地比較兩個數據塊時,就可以比較它們的哈希值。例如,可以通過比較郵件發送前和發送后的哈希值來驗證該郵件在傳遞時是否修改
消息摘要算法的主要特征是加密過程不需要密鑰,並且經過加密的數據無法被解密,只有輸入相同的明文數據經過相同的消息摘要算法才能得到相同的密文。消息摘要算法不存在 密鑰的管理與分發問題,適合於分布式網絡相同上使用。由於其加密計算的工作量相當可觀,所以以前的這種算法通常只用於數據量有限的情況下的加密,例如計算機的口令就是 用不可逆加密算法加密的。
三 https握手的過程詳細描述
1.瀏覽器將自己支持的一套加密規則發送給網站,如RSA加密算法,DES對稱加密算法,SHA1摘要算法
2.網站從中選出一組加密算法與HASH算法,並將自己的身份信息以證書的形式發回給瀏覽器。證書里面包含了網站地址,加密公鑰,以及證書的頒發機構等信息(證書中的私鑰只能用於服務器端進行解密,在握手的整個過程中,都用到了證書中的公鑰和瀏覽器發送給服務器的隨機密碼以及對稱加密算法)
3.獲得網站證書之后瀏覽器要做以下工作:
a) 驗證證書的合法性(頒發證書的機構是否合法,證書中包含的網站地址是否與正在訪問的地址一致等),如果證書受信任,則瀏覽器欄里面會顯示一個小鎖頭,否則會給出證書不受信的提示。
b) 如果證書受信任,或者是用戶接受了不受信的證書,瀏覽器會生成一串隨機數的密碼,並用證書中提供的公鑰加密。
c) 使用約定好的HASH算法計算握手消息(如SHA1),並使用生成的隨機數對消息進行加密,最后將之前生成的被公鑰加密的隨機數密碼,HASH摘要值一起發送給服務器
4.網站接收瀏覽器發來的數據之后要做以下的操作:
a) 使用自己的私鑰將信息解密並取出瀏覽器發送給服務器的隨機密碼,使用密碼解密瀏覽器發來的握手消息,並驗證HASH是否與瀏覽器發來的一致。
b) 使用隨機密碼加密一段握手消息,發送給瀏覽器。
5.瀏覽器解密並計算握手消息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,之后所有的通信數據將由之前瀏覽器生成的隨機密碼並利用對稱加密算法進行加密。
從上面的4個大的步驟可以看到,握手的整個過程使用到了數字證書、對稱加密、HASH摘要算法,接下來我們用實際代碼來實現整個過程
四 使用java代碼模擬整個握手過程
一:准備工作
1、創建java證書,
C:\> keytool -genkey -alias wangyi -keypass wangyi -keyalg RSA -keysize 1024 -keystore https.keystore -storepass wangyi
2、將創建的證書保存到C盤(為了方便演示)
C:\>keytool -export -keystore https.keystore -alias wangyi -file https.crt -storepass wangyi
二:代碼實現
代碼包含6個類,分別為:
名稱 | 說明 |
CertifcateUtils | 證書操作類 |
DesCoder | Des對稱加密和解密工具類 |
HttpsMockBase | https父類 |
HttpsMockClient | client類 |
HttpsMockServer | 服務器類 |
SocketUtils | socket工具類 |
- package httpsmock;
- import java.io.ByteArrayInputStream;
- import java.io.FileInputStream;
- import java.io.InputStream;
- import java.security.KeyStore;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.cert.CertificateFactory;
- /**
- * Created by kingj on 2014/8/13.
- */
- public class CertifcateUtils {
- public static byte[] readCertifacates() throws Exception{
- CertificateFactory factory=CertificateFactory.getInstance("X.509");
- InputStream in=new FileInputStream("c:/https.crt");
- java.security.cert.Certificate cate=factory.generateCertificate(in);
- return cate.getEncoded();
- }
- public static byte[] readPrivateKey() throws Exception{
- KeyStore store=KeyStore.getInstance("JKS");
- InputStream in=new FileInputStream("c:/https.keystore");
- store.load(in,"wangyi".toCharArray());
- PrivateKey pk=(PrivateKey)store.getKey("wangyi","wangyi".toCharArray());
- return pk.getEncoded();
- }
- public static PrivateKey readPrivateKeys() throws Exception{
- KeyStore store=KeyStore.getInstance("JKS");
- InputStream in=new FileInputStream("c:/https.keystore");
- store.load(in,"wangyi".toCharArray());
- PrivateKey pk=(PrivateKey)store.getKey("wangyi","wangyi".toCharArray());
- return pk;
- }
- public static PublicKey readPublicKeys() throws Exception{
- CertificateFactory factory=CertificateFactory.getInstance("X.509");
- InputStream in=new FileInputStream("c:/https.crt");
- java.security.cert.Certificate cate=factory.generateCertificate(in);
- return cate.getPublicKey();
- }
- public static java.security.cert.Certificate createCertiface(byte b[]) throws Exception{
- CertificateFactory factory=CertificateFactory.getInstance("X.509");
- InputStream in=new ByteArrayInputStream(b);
- java.security.cert.Certificate cate=factory.generateCertificate(in);
- return cate;
- }
- public static String byte2hex(byte[] b) {
- String hs = "";
- String stmp = "";
- for (int n = 0; n < b.length; n++) {
- stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
- if (stmp.length() == 1) {
- hs = hs + "0" + stmp;
- } else {
- hs = hs + stmp;
- }
- }
- return hs.toUpperCase();
- }
- }
- package httpsmock;
- /**
- * Created by kingj on 2014/8/13.
- */
- import org.apache.commons.codec.binary.Hex;
- import java.security.Key;
- import java.security.SecureRandom;
- import javax.crypto.Cipher;
- import javax.crypto.KeyGenerator;
- import javax.crypto.SecretKey;
- import javax.crypto.SecretKeyFactory;
- import javax.crypto.spec.DESKeySpec;
- /**
- * DES Coder<br/>
- * secret key length: 56 bit, default: 56 bit<br/>
- * mode: ECB/CBC/PCBC/CTR/CTS/CFB/CFB8 to CFB128/OFB/OBF8 to OFB128<br/>
- * padding: Nopadding/PKCS5Padding/ISO10126Padding/
- * @author Aub
- *
- */
- public class DesCoder {
- /**
- * 密鑰算法
- */
- private static final String KEY_ALGORITHM = "DES";
- private static final String DEFAULT_CIPHER_ALGORITHM = "DES/ECB/PKCS5Padding";
- // private static final String DEFAULT_CIPHER_ALGORITHM = "DES/ECB/ISO10126Padding";
- /**
- * 初始化密鑰
- *
- * @return byte[] 密鑰
- * @throws Exception
- */
- public static byte[] initSecretKey(SecureRandom random) throws Exception{
- //返回生成指定算法的秘密密鑰的 KeyGenerator 對象
- KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
- //初始化此密鑰生成器,使其具有確定的密鑰大小
- kg.init(random);
- //生成一個密鑰
- SecretKey secretKey = kg.generateKey();
- return secretKey.getEncoded();
- }
- /**
- * 轉換密鑰
- *
- * @param key 二進制密鑰
- * @return Key 密鑰
- * @throws Exception
- */
- public static Key toKey(byte[] key) throws Exception{
- //實例化DES密鑰規則
- DESKeySpec dks = new DESKeySpec(key);
- //實例化密鑰工廠
- SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALGORITHM);
- //生成密鑰
- SecretKey secretKey = skf.generateSecret(dks);
- return secretKey;
- }
- /**
- * 加密
- *
- * @param data 待加密數據
- * @param key 密鑰
- * @return byte[] 加密數據
- * @throws Exception
- */
- public static byte[] encrypt(byte[] data,Key key) throws Exception{
- return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
- }
- /**
- * 加密
- *
- * @param data 待加密數據
- * @param key 二進制密鑰
- * @return byte[] 加密數據
- * @throws Exception
- */
- public static byte[] encrypt(byte[] data,byte[] key) throws Exception{
- return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
- }
- /**
- * 加密
- *
- * @param data 待加密數據
- * @param key 二進制密鑰
- * @param cipherAlgorithm 加密算法/工作模式/填充方式
- * @return byte[] 加密數據
- * @throws Exception
- */
- public static byte[] encrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception{
- //還原密鑰
- Key k = toKey(key);
- return encrypt(data, k, cipherAlgorithm);
- }
- /**
- * 加密
- *
- * @param data 待加密數據
- * @param key 密鑰
- * @param cipherAlgorithm 加密算法/工作模式/填充方式
- * @return byte[] 加密數據
- * @throws Exception
- */
- public static byte[] encrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception{
- //實例化
- Cipher cipher = Cipher.getInstance(cipherAlgorithm);
- //使用密鑰初始化,設置為加密模式
- cipher.init(Cipher.ENCRYPT_MODE, key);
- //執行操作
- return cipher.doFinal(data);
- }
- /**
- * 解密
- *
- * @param data 待解密數據
- * @param key 二進制密鑰
- * @return byte[] 解密數據
- * @throws Exception
- */
- public static byte[] decrypt(byte[] data,byte[] key) throws Exception{
- return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
- }
- /**
- * 解密
- *
- * @param data 待解密數據
- * @param key 密鑰
- * @return byte[] 解密數據
- * @throws Exception
- */
- public static byte[] decrypt(byte[] data,Key key) throws Exception{
- return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
- }
- /**
- * 解密
- *
- * @param data 待解密數據
- * @param key 二進制密鑰
- * @param cipherAlgorithm 加密算法/工作模式/填充方式
- * @return byte[] 解密數據
- * @throws Exception
- */
- public static byte[] decrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception{
- //還原密鑰
- Key k = toKey(key);
- return decrypt(data, k, cipherAlgorithm);
- }
- /**
- * 解密
- *
- * @param data 待解密數據
- * @param key 密鑰
- * @param cipherAlgorithm 加密算法/工作模式/填充方式
- * @return byte[] 解密數據
- * @throws Exception
- */
- public static byte[] decrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception{
- //實例化
- Cipher cipher = Cipher.getInstance(cipherAlgorithm);
- //使用密鑰初始化,設置為解密模式
- cipher.init(Cipher.DECRYPT_MODE, key);
- //執行操作
- return cipher.doFinal(data);
- }
- private static String showByteArray(byte[] data){
- if(null == data){
- return null;
- }
- StringBuilder sb = new StringBuilder("{");
- for(byte b:data){
- sb.append(b).append(",");
- }
- sb.deleteCharAt(sb.length()-1);
- sb.append("}");
- return sb.toString();
- }
- }
- package httpsmock;
- import com.sun.org.apache.bcel.internal.generic.NEW;
- import javax.crypto.*;
- import javax.crypto.spec.DESKeySpec;
- import java.security.*;
- import java.security.spec.InvalidKeySpecException;
- import java.util.Random;
- /**
- * Created by kingj on 2014/8/13.
- */
- public class HttpsMockBase {
- static PrivateKey privateKey;
- static PublicKey publicKey;
- 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("SHA1");
- 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'
- };
- }
- package httpsmock;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.net.Socket;
- import java.security.Key;
- import java.security.SecureRandom;
- /**
- * Created by kingj on 2014/8/13.
- */
- 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 s=new Socket("localhost",port);
- s.setReceiveBufferSize(102400);
- s.setKeepAlive(true);
- in=new DataInputStream(s.getInputStream());
- out=new DataOutputStream(s.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算法
- String supportHash="SHA1";
- int length=supportHash.getBytes().length;
- out.writeInt(length);
- SocketUtils.writeBytes(out, supportHash.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);
- //第三步 客戶端校驗服務器端發送過來的證書成功,生成隨機數並用公鑰加密
- System.out.println("客戶端校驗服務器端發送過來的證書成功,生成隨機數並用公鑰加密");
- SecureRandom seed=new SecureRandom();
- int seedLength=2;
- byte seedBytes[]=seed.generateSeed(seedLength);
- System.out.println("生成的隨機數為 : " + byte2hex(seedBytes));
- System.out.println("將隨機數用公鑰加密后發送到服務器");
- 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);
- System.out.println("客戶端向服務器發送消息完成,開始接受服務器端發送回來的消息和摘要");
- System.out.println("接受服務器端發送的消息");
- 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));
- System.out.println("判斷服務器端發送過來的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);
- }
- }
- package httpsmock;
- 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;
- /**
- * Created by kingj on 2014/8/13.
- */
- 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);
- System.out.println("獲取客戶端通過公鑰加密后的隨機數");
- 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 serverHash[]=cactHash(message);
- System.out.println("獲取客戶端計算的SHA1摘要");
- int hashSkip=in.readInt();
- byte[] clientHashBytes=SocketUtils.readBytes(in,hashSkip);
- System.out.println("客戶端SHA1摘要為 : " + byte2hex(clientHashBytes));
- System.out.println("開始比較客戶端hash和服務器端從消息中計算的hash值是否一致");
- boolean isHashEquals=byteEquals(serverHash,clientHashBytes);
- System.out.println("是否一致結果為 : " + isHashEquals);
- System.out.println("第一次校驗客戶端發送過來的消息和摘譯一致,服務器開始向客戶端發送消息和摘要");
- 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(), key);
- SocketUtils.writeBytes(out,serverMessage,serverMessage.length);
- System.out.println("服務器端發送的機密后的消息為:"+byte2hex(serverMessage)+",加密密碼為:"+byte2hex(secureSeed));
- System.out.println("服務器端開始計算hash摘要值");
- byte serverMessageHash[]=cactHash(randomMessage.getBytes());
- System.out.println("服務器端計算的hash摘要值為 :" +byte2hex(serverMessageHash));
- SocketUtils.writeBytes(out,serverMessageHash,serverMessageHash.length);
- System.out.println("握手成功,之后所有通信都將使用DES加密算法進行加密");
- }
- }
- package httpsmock;
- import java.io.ByteArrayInputStream;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.net.Socket;
- import java.util.Arrays;
- /**
- * Created by kingj on 2014/8/13.
- */
- public class SocketUtils {
- public static void close(Socket s){
- try {
- s.shutdownInput();
- s.shutdownOutput();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static byte[] readBytes(DataInputStream in,int length) throws IOException {
- int r=0;
- byte[] data=new byte[length];
- while(r<length){
- r+=in.read(data,r,length-r);
- }
- return data;
- }
- public static void writeBytes(DataOutputStream out,byte[] bytes,int length) throws IOException{
- out.writeInt(length);
- out.write(bytes,0,length);
- out.flush();
- }
- }
通過運行上述代碼,我們可以看看服務器端和客戶端控制台打印的消息記錄(https握手完成后,整個過程數據傳輸都需要客戶端和服務端使用約定的DES算法對數據進行加密和解密)
1、服務端消息記錄
客戶端發送了hash算法為:SHA1
發送證書給客戶端,字節長度為:618
證書內容為: 30 8202 66 30 8201 CF A0030201020204 51 84 FA AF 300D0609 2A 86 48 86 F70D01010B0500 30 66 310F 300D0603 550406 1306 77 61 6E 67 79 69 310F 300D0603 550408 1306 77 61 6E 67 79 69 310F 300D0603 550407 1306 77 61 6E 67 79 69 310F 300D0603 55040A 1306 77 61 6E 67 79 69 310F 300D0603 55040B 1306 77 61 6E 67 79 69 310F 300D0603 550403 1306 77 61 6E 67 79 69 30 1E 170D 31 34 30 38 31 33 30 35 32 30 35 34 5A 170D 31 34 31 31 31 31 30 35 32 30 35 34 5A 30 66 310F 300D0603 550406 1306 77 61 6E 67 79 69 310F 300D0603 550408 1306 77 61 6E 67 79 69 310F 300D0603 550407 1306 77 61 6E 67 79 69 310F 300D0603 55040A 1306 77 61 6E 67 79 69 310F 300D0603 55040B 1306 77 61 6E 67 79 69 310F 300D0603 550403 1306 77 61 6E 67 79 69 30 81 9F 300D0609 2A 86 48 86 F70D010101050003 81 8D00 30 81 8902 81 8100 89 20 2A F6 BF 1E F9 95 F8 E5 E2 C2 C6 14 22 DB 23 10 2F 44 E0 AD0B FB 89 62 8C A6 E2 14 52 E7 5D FE 7B CC A4 D2 F4 F9 C5 8E E0 75 CC F3 71 E9 29 85 A9 DA D2 BD 93 73 12 74 2B 4C D2 74 1A 13 82 64 20 E0 8B 68 FF 9A F0 6F0C 880F 91 A5 FE 42 44 DE 81 F0 47 C7 67 2001 C7 7E 8B 36 87 E8 1B 7E 6907 D0 39 77 DE 53 D4 F5 67 57 BD 15 8E 51 E5 44 10 CD BE 81 EB E3 86 E8 73 B5 1D 1F FF0203010001 A3 21 30 1F 30 1D0603 55 1D0E04 1604 14 E2 81 F2 3E 81 92 8B DE 7A 1D 93 A9 28 23 A7 5D E7 65 63 EB 300D0609 2A 86 48 86 F70D01010B050003 81 810002 E6 BF00 FB CE 3A 4A AC 9E 5F 10 6C 4F FE 44 93 A4 6D 89 BC 4F CB 25 30 1F B4 C7 67 E3 E6 A1 1D 66 4B DA E4 6D D8 90 CC D2 74 34 48 6C 9B 33 2E C2 4E 9E AA 470B 9B 4000 7A 59 67 3E C2 75 1A A0 7A 48 16 53 D6 C4 53 97080B F4 23 49 2E06 60 DF 9D B4 5B 76 B2 AC 35 CF 2E 3C CA E3 B6 25 7D F7 BA 69 6F 15 CE AF B4 9D 83 94 2E 5E 37 6E C5 C2 B9 94 54 DB06 5D 7F B6 70 1C 91 E6 E3
獲取客戶端通過公鑰加密后的隨機數
讀取到的客戶端的隨機數為: 86 16 A9 65 F6 EC A3 57 D6 23 A2 43 8F F4 52 F5 37 14 F9 5B 27 6F 75 A3 25 C9 9E D4 DD CC 68 BA03 A2 1B E6 8D 74 61 3B 28 28 9F 1F 5A AD 5F 32 4B 40 81 98 54 AC0F 840B 80 BF 53 80 50 1E A7 24 16 10 2A 2B 6A 8709 86 7C 20 75 20 14 7E 38 F3 FA 76 6207 D1 E1 37 28 93 D9 C1 2F D4 9B 6E 9A 5205 9A 6D 54 8B DD 1D 8205 DF BC AE BB 6C 24 F5 6E BC F2 DE 26 AB B1 87 1F DA DE 3B 25 1E
解密后的隨機數密碼為: 5B D4
第三步 獲取客戶端加密消息,消息長度為 :128
客戶端發送的加密消息為 : 32 76 EB 3E 93 E7 F1 590E 67 EB FA 29 24 5D F4 A2 3E 78 BE 61 49 B1 4C 91 1A 450A B7 D7 E0 71 A1 30 C0 12 F905 9C CF B9 C9 75 6B C4 39 3C EF 5F 1005 75 AD 50 9A09 6F 8A 7F C0 F4 20 E0 BC DF 74 90 F3 6A 46 5E 6C 47 FC 16 EC 4D DD 10 F9 87 ED E4 47 83 37 B8 6A 5B 5B B2 17 9306 7707 72 8E 3008 73 59 89 F5 F7 E6 66 89 4F F7 B6 2B 41 7B 3B 1B 29 63 D0 11 D4 52 60 4A 3B 74 CA 1E
用私鑰對消息解密,並計算SHA1的hash值
獲取客戶端計算的SHA1摘要
客戶端SHA1摘要為 : 01 56 CB DF D3 EF 5A 8F BB 85 BE 15 FB 83 D9 10 1F 64 F6 D8
開始比較客戶端hash和服務器端從消息中計算的hash值是否一致
是否一致結果為 : true
第一次校驗客戶端發送過來的消息和摘譯一致,服務器開始向客戶端發送消息和摘要
生成密碼用於加密服務器端消息,secureRandom : 5B D4 (使用客戶端第一次傳過來的密碼)
服務器端生成的隨機消息為 : 2355384499
用DES算法並使用客戶端生成的隨機密碼對消息加密
服務器端發送的機密后的消息為: 34 DE 39 CE 7A 280D 4F 44 83 51 2D C3 EB 4F 1B,加密密碼為: 5B D4 (使用客戶端第一次傳過來的密碼)
服務器端開始計算hash摘要值
服務器端計算的hash摘要值為 : DD 3D 66 B5 C8 B6 A2 36 5E D1 55 9A B6 F7 C0 39 3C 97 1402
握手成功,之后所有通信都將使用DES加密算法進行加密
--------------------------------------------------------
讀取未解密消息: 9D 2D C2 D7 5D 2F 3C F5
客戶端指令內容為: 64 75 63 6B
寫入加密后消息: 52 91 2C 62 E3 B9 5E 80 CF 3D 39 B4 7D 55 B7 3A 97 46 34 98 5603 DA FC A9 E1 D1 61 8F 24 64 D8
2、客戶端消息記錄
客戶端校驗服務器端證書是否合法:true (校驗證書)
客戶端校驗服務器端發送過來的證書成功,生成隨機數並用公鑰加密
生成的隨機數為 : 5B D4 (客戶端生成了隨機密碼,用於整個握手過程中)
將隨機數用公鑰加密后發送到服務器
加密后的seed值為 : 86 16 A9 65 F6 EC A3 57 D6 23 A2 43 8F F4 52 F5 37 14 F9 5B 27 6F 75 A3 25 C9 9E D4 DD CC 68 BA03 A2 1B E6 8D 74 61 3B 28 28 9F 1F 5A AD 5F 32 4B 40 81 98 54 AC0F 840B 80 BF 53 80 50 1E A7 24 16 10 2A 2B 6A 8709 86 7C 20 75 20 14 7E 38 F3 FA 76 6207 D1 E1 37 28 93 D9 C1 2F D4 9B 6E 9A 5205 9A 6D 54 8B DD 1D 8205 DF BC AE BB 6C 24 F5 6E BC F2 DE 26 AB B1 87 1F DA DE 3B 25 1E
客戶端生成消息為:9080292229
使用隨機數並用公鑰對消息加密
加密后消息位數為 : 128
客戶端使用SHA1計算消息摘要
摘要信息為:01 56 CB DF D3 EF 5A 8F BB 85 BE 15 FB 83 D9 10 1F 64 F6 D8
消息加密完成,摘要計算完成,發送服務器
客戶端向服務器發送消息完成,開始接受服務器端發送回來的消息和摘要
接受服務器端發送的消息
服務器端的消息內容為 : 34 DE 39 CE 7A 280D 4F 44 83 51 2D C3 EB 4F 1B
開始用之前生成的隨機密碼和DES算法解密消息,密碼為: 5B D4
解密后的消息為: 32 33 35 35 33 38 34 34 39 39
開始接受服務器端的摘要消息: DD 3D 66 B5 C8 B6 A2 36 5E D1 55 9A B6 F7 C0 39 3C 97 1402
計算服務器端發送過來的消息的摘要 : DD 3D 66 B5 C8 B6 A2 36 5E D1 55 9A B6 F7 C0 39 3C 97 1402
判斷服務器端發送過來的hash摘要是否和計算出的摘要一致
驗證完成,握手成功
------------------------------------------------------------------
寫入加密后消息: 9D 2D C2 D7 5D 2F 3C F5
讀取未解密消息: 52 91 2C 62 E3 B9 5E 80 CF 3D 39 B4 7D 55 B7 3A 97 46 34 98 5603 DA FC A9 E1 D1 61 8F 24 64 D8
服務器反饋消息: E6 9C 8D E5 8A A1 E5 99 A8 E5 B7 B2 E7 BB 8F E6 8E A5 E5 8F 97 E8 AF B7 E6 B1 82