今天被問到關於https原理的問題,結果因爲知識掌握不牢靠,停留於表面,不少細節都沒法回答清楚,因而決定把https的原理弄個明白,廢話很少說,咱們先看看https的定義java
(因爲好久未寫博客,排版有些凌亂,請諒解)git
一:什麼是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在傳輸數據以前須要客戶端(瀏覽器)與服務端(網站)之間進行一次握手,在握 手過程當中將確立雙方加密傳輸數據的密碼信息,一般狀況下會配合數字證書實現。apache
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)算法後,甲乙雙方公開本身的公鑰,使用對方的公鑰和剛纔產生的私鑰加密數據,同時可使用對方的公鑰和本身的私鑰對數據解密。不僅僅是甲乙雙方兩方,能夠擴展爲多方共享數據通信,這樣就完成了網絡交互數據的安全通信!該算法源於中國的同餘定理——中國餘數定理。appRSA
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、RC二、RC四、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代碼模擬整個握手過程
一:準備工做
一、建立java證書,
C:\> keytool -genkey -alias wangyi -keypass wangyi -keyalg RSA -keysize 1024 -keystore https.keystore -storepass wangyi
二、將建立的證書保存到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算法對數據進行加密和解密)
一、服務端消息記錄
客戶端發送了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
二、客戶端消息記錄
客戶端校驗服務器端證書是否合法: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