1. 程式人生 > >Java中的https證書問題

Java中的https證書問題

越來越多的網站由http遷移至https,Apple甚至要求必須使用https,現在iOS中的app一律都得使用https。

https與http之最大不同便在於安全性,多了一個證書。我們使用瀏覽器訪問https站點的時候,會首先下載該站點的證書並安裝,然後驗證該證書的權威性。如果證書合法,有些瀏覽器會在網址欄處出現一個鉤狀或者綠色的標記;如果證書錯誤,但不正確,則無法訪問;如果證書正確,但不是權威機構頒發的,例如網站自己簽發的證書,則瀏覽器會需要使用者確認是否繼續訪問。

Java中訪問https站點和瀏覽器又有不同,其中一個差別便是對於權威機構的認定,一些瀏覽器能夠正常訪問的https站點,在Java中卻訪問不了,出現javax.net.ssl.SSLHandshakeException,其實就是因為Java認為證書不夠權威。

如何解決這一問題呢?兩種方式:

1 不進行證書驗證,即所有https站點的證書都認為合法,可以訪問所有https站點,缺點就是犧牲了https的安全性。

2 修改證書驗證,即讀取特定站點的證書,加入證書信任列表,這樣就可以對指定的https進行訪問了。

程式碼如下;

package com.dancen.util.https;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collection;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class MyHttpsUtil
{
	public static void main(String[] args)
	{
		try
		{
			HttpsURLConnection connection = MyHttpsUtil.getMyHttpsURLConnection("https://src.test.com/home/config.json");
			System.out.println(connection.getContentType());
			InputStream is = connection.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String data;
			
			while(null != (data = br.readLine()))
			{
				System.out.println(data);
			}
			
			br.close();
			isr.close();
			is.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * 獲取不安全的HttpsConnection
	 * 關閉證書驗證,可以和任何https站點建立連線
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getMyHttpsURLConnection(String url) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
				TrustManager[] trustManagers = {new MyX509TrustManager()};
				SSLContext sslContext = SSLContext.getInstance("TLS"); 
				sslContext.init(null, trustManagers, new SecureRandom());
				rs.setSSLSocketFactory(sslContext.getSocketFactory());
				rs.setHostnameVerifier(new MyHostnameVerifier());
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 獲取自定義的HttpsConnection
	 * 讀取證書後,和特定的https站點建立連線
	 * @param url
	 * @param cerFilePath
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getHttpsURLConnection(String url, String cerFilePath) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				if(null == cerFilePath || cerFilePath.isEmpty())
				{
					rs = getDefaultHttpsURLConnection(url);
				}
				else
				{
					rs = getHttpsURLConnection(url, new FileInputStream(cerFilePath.trim()));
				}
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 獲取自定義的HttpsConnection
	 * 讀取證書流後,可以和特定的https站點建立連線
	 * @param url
	 * @param cerInputStream
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getHttpsURLConnection(String url, InputStream cerInputStream) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
				
				if(null != cerInputStream)
				{
					CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
		            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(cerInputStream);
		            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
		            keyStore.load(null, null);
		            int index = 0;
		            
		            if(null != certificates)
		            {
			            for(Certificate certificate : certificates)
			            {
			                String certificateAlias = Integer.toString(index++);
			                keyStore.setCertificateEntry(certificateAlias, certificate);
			            }
		            }
	
		            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
		            keyManagerFactory.init(keyStore, null);
		            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		            trustManagerFactory.init(keyStore);
		            SSLContext sslContext = SSLContext.getInstance("TLS");
		            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
		            rs.setSSLSocketFactory(sslContext.getSocketFactory());
				}
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 獲取預設的HttpsURLConnection
	 * 只能與系統信任的https站點建立連線
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getDefaultHttpsURLConnection(String url) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
		}
		
		return rs;
	}
}
package com.dancen.util.https;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class MyX509TrustManager implements X509TrustManager
{
	@Override
	public void checkClientTrusted(X509Certificate ax509certificate[], String s) throws CertificateException
	{
		//TODO nothing
	}

	@Override
	public void checkServerTrusted(X509Certificate ax509certificate[], String s) throws CertificateException
	{
		//TODO nothing
	}

	@Override
	public X509Certificate[] getAcceptedIssuers()
	{
		return new X509Certificate[]{};
	}
}
package com.dancen.util.https;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

public class MyHostnameVerifier implements HostnameVerifier
{
	public MyHostnameVerifier()
	{
		
	}
	
	@Override
	public boolean verify(String hostname, SSLSession session)
	{
		return true;
	}
}


附錄:https相關知識

地址:http://kingj.iteye.com/blog/2103662

一:什麼是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演算法

這裡我們先看看這上面提到的幾種技術(如果你對這些技術已經非常瞭解,那麼請跳過該段落,直接到段落三)

  1. 數字證書

        數字證書是一種權威性的電子文件,由權威公正的第三方機構,即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),同時也支援單個證書,不含私鑰

  1. 非對稱加密演算法

          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加密演算法,目前沒有開源元件提支援

  1. 對稱加密演算法

        對加密和解密使用相同金鑰的加密演算法。由於其速度,對稱性加密通常在訊息傳送方需要加密大量資料時使用。對稱性加密也稱為金鑰加密。對稱式資料加密的方式的工作原理如圖。所謂對稱,就是採用這種加密方法的雙方使用方式用同樣的金鑰進行加密和解密。金鑰實際上是一種演算法,通訊傳送方使用這種演算法加密資料,接收方再以同樣的演算法解密資料。因此對稱式加密本身不是安全的。常用的對稱加密有: 

DES、IDEA、RC2、RC4、SKIPJACK演算法等 。

採用單鑰密碼系統的加密方法,同一個金鑰可以同時用作資訊的加密和解密,這種加密方法稱為對稱加密,也稱為單金鑰加密。

  1. 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摘要演算法。