摘
要:
Java Security在Java存在已久了並且它是一個很是重要且獨立的版塊,包含了不少的知識點,常見的有MD5,DigitalSignature等,而Android在Java Seurity以外,拓展了一個android.security包,此包中就提供了KeyChain。它包含了主要三個重要的規範:JavaCryptography Extension(簡寫爲JCE),JCE所包含的內容有加解密,密鑰交換,消息摘要(Message Digest,好比MD5等),密鑰管理等。本文所涉及的大部份內容都屬於JSSE的範疇,JSSE所包含的內容就是Java層的SSL/TLS。簡單點說,使用JSSE就能夠建立SSL/TLS
socket了。JavaAuthentication and Authorization Service(簡寫爲JAAS),JSSA和認證/受權有關,這部份內容在客戶端接觸得會比較少一點。
在上述三個子模塊或規範中,JCE是JavaSecurity的大頭,其餘兩個子模塊JSSE和JAAS都依賴於它,好比SSL/TLS在工做過程當中須要使用密鑰對數據進行加解密,那麼密鑰的建立和使用就依靠JCE子模塊了。可是本篇將主要JSSE版塊中的SSL/TLS協議,重點關注SSL/TLS協議的安全性以及通訊細節詳解,同時就Android或Java程序中對此應用對比並加以輔助說明。
一 : 簡介
SSL (Secure Socket Layer)即:安全套接字層, 它是由Netscape 所研發,用以保障在 Web的安全傳輸協議,目的是爲網絡通訊提供機密性、認證性及數據完整性保障,利用數據加密 (Encryption) 技術,可確保數據在網絡上之傳輸過程當中不會被截取及竊聽。它已被普遍地用於 Web 瀏覽器與服務器之間的身份認證和加密數據傳輸。 SSL 協議位於 TCP/IP 協議與各類應用層協議之間,爲數據通信提供安全支持。先今,SSL已經成爲互聯網保密通訊的工業標準。
SSL最初的幾個版本(SSL 1.0、SSL2.0、SSL 3.0)由網景公司設計和維護,從3.1版本開始,SSL協議由因特網工程任務小組(IETF)正式接管,並改名爲TLS(Transport Layer Security),發展至今已有TLS 1.0、TLS1.一、TLS1.2這幾個版本。
如TLS名字所說,SSL/TLS協議僅保障傳輸層安全。同時,因爲協議自身特性(數字證書機制),SSL/TLS不能被用於保護多跳(multi-hop)端到端通訊,而只能保護點到點通訊。
SSL/TLS協議可以提供的安全目標主要包括以下幾個:
認證性——藉助數字證書認證服務器端和客戶端身份,防止身份僞造,確保數據發送到正確的 客戶機和服務器
機密性——藉助加密防止第三方竊聽
完整性——藉助
消息認證碼(MAC)保障數據完整性,防止消息篡改
重放保護——經過使用隱式序列號防止重放攻擊
爲了實現這些安全目標,SSL/TLS協議被設計爲一個兩階段協議,分爲握手階段和應用階段:
握手階段也稱協商階段,在這一階段,客戶端和服務器端會認證對方身份(依賴於PKI體系,利用數字證書進行身份認證),並協商通訊中使用的安全參數、密碼套件以及MasterSecret。後續通訊使用的全部密鑰都是經過MasterSecret生成。
在握手階段完成後,進入應用階段。在應用階段通訊雙方使用握手階段協商好的密鑰進行安全通訊。
SSL/TLS協議有一個高度模塊化的架構,分爲不少子協議,以下圖所示:
Handshake協議:包括協商安全參數和密碼套件、服務器身份認證(客戶端身份認證可選)、密鑰交換;
ChangeCipherSpec 協議:一條消息代表握手協議已經完成;
Alert 協議:對握手協議中一些異常的錯誤提醒,分爲fatal和warning兩個級別,fatal類型的錯誤會直接中斷SSL連接,而warning級別的錯誤SSL連接仍可繼續,只是會給出錯誤警告;
Record 協議:包括對消息的分段、壓縮、消息認證和完整性保護、加密等。
二:SSL/TSL協議流程
SSL/TSL協議能夠分爲如下兩種類型:
1>.單向認證
客戶端和服務器端只存單方面認證 即:客戶端須要服務器端的證書認證,而服務端則不須要客戶端證書認證。起先,客戶端會發送一條打招呼信息裏面攜帶了版本、加密體系、壓縮算法等等相關信息,服務端收到後,向客戶端確認選擇的版本、加密體系等等信息。隨後服務端會將本身的證書包含了公鑰等信息發給客戶端,客戶端在取得服務器端的公鑰,將會對要發送的數據進行加密,併發送給服務器。服務器端收到後,會用本地私鑰對收到的客戶端加密數據進行解密。而後,通訊兩邊都應用這些數據來產生兩邊之間通訊的加密密鑰。接下來,兩邊就能夠開始通訊過程了。
2>.雙向認證
雙向認證和單向基本相似,只不過多個幾個認證環節。既然是雙向認證,也就意味着服務端也須要獲取client端證書從而取得客戶端的公鑰。隨後客戶端會發送一條用本身私鑰加密的數據給服務端,服務端收到後就會用其得到的公鑰進行解密認證。其它環節都跟單向同樣咯。
下圖是一個典型的TLS 1.0協議交互流程以下圖所示:
每個SSL/TLS連接都是從握手開始的,握手過程包含一個消息序列,用以協商安全參數、密碼套件,進行身份認證以及密鑰交換。握手過程當中的消息必須嚴格按照預先定義的順序發生,不然就會帶來潛在的安全威脅。
ClientSayHi:首先client端發出這樣的一個消息,此消息中攜帶了客戶端所支持的密碼套件列表、最高SSL/TLS協議版本列表、加密算法種類列表以及壓縮算法等等服務器和客戶端之間通信所須要的各類信息。
此外ClientSayHi中還包含一個隨機數,這個隨機數由4個字節的當前GMT UNIX時間以及28個隨機選擇的字節組成,共32字節。該隨機數會在服務器端公鑰生成過程當中被使用。ClientSayHi中還可能包含客戶端支持的TLS擴展。(TLS擴展能夠被用來豐富TLS協議的功能或者加強協議的安全性)
ServerSayHi:服務器接受到後,會返回ServerSayHi消息。服務器從客戶端在ClientSayHi消息中提供的密碼套件、SSL/TLS版本、加密算法等列表裏選擇它所支持的項,並把它的選擇包含在ServerSayHi中告知客戶端。接下來SSL協議的創建就基於服務器選擇的密碼套件類型、SSL/TLS協議版本以及加密算法。ServerSayHi中一樣會包含一個隨機數,一樣4+28 字節類型,由服務器生成。
Certificate:客戶端和服務器均可以發送證書消息來證實本身的身份,可是一般客戶端證書不被使用。 服務器通常在ServerSayHi後會接着發一條Certificate消息,該證書則包含了數據加密算法種類、server端的產生的隨機數以及公鑰等等信息,客戶利用服務器傳過來的證書提取相關的信息驗證服務器的合法性,服務器的合法性包括:證書是否過時,發行服務器證書的 CA 是否可靠,發行者證書的公鑰可否正確解開服務器證書的 「 發行者的數字簽名 」 ,服務器證書上的域名是否和服務器的實際域名相匹配。若是合法性驗證沒有經過,通信將斷開; 目前主流的證書通用格式則是X509證書格式,在用的X.509證書包含Version 1和Version 3兩種版本,其中v1版本的證書存在安全隱患,同時不支持TLS擴展,被逐漸棄用。如今大多數在用的SSL證書都是V3版本。同時證書會附帶與協商好的密鑰交換算法對應的密鑰。
ServerKeyExchange:該消息僅當如下加密算法被使用時由服務器發出(也就是會說當server端在client端提供的加密算法列表裏選擇如下列出的算法之一,接下來server端就會繼續發送一條ServerKeyExchange信息): RSA_EXPORT(僅當服務器的公鑰大於512bit時)、DHE_DSS、DHE_DSS_EXPORT、DHE_RSA、DHE_RSA_EXPORT、DH_anon 使用其它密鑰交換算法時,服務器不能發送此消息。ServerkeyExchange消息會攜帶對應加密算法所須要的額外參數信息,之後這些信息在client端會進一步升級與servery端約定的加密算法。同時這些參數須要被簽過名。
CertificateRequest:這個消息一般在要求認證客戶端身份時纔會有也就是雙向認證。消息中包含了證書類型以及可接受的CA列表。
ServerSayHiDone:服務器發送這條消息代表服務器部分的密鑰交換信息已經發送完了,等待客戶端的消息以繼續接下來的步驟。這條消息只用做提醒,不包含數據域。
ClientKeyExchange:這條消息包含的數據與所選用的密鑰交換算法有關。 在此以前客戶端會隨機產生一個用於後面通信的 「 對稱密碼 「也就是臨時祕鑰
(key),它有48個字節,前2個字節表示客戶端支持的最高協議版本,後46個字節是隨機選擇的。該祕鑰和client端公鑰同時調用包含在以前證書中的或者是ServerKeyExchange中的傳過來的server端公鑰進行加密,接着就會隨着ClientKeyExchange這條消息發送給服務器,服務器將用本身持有的私鑰來解密該"對稱密碼",這將做爲二者之後通訊中的數據加密算法,保證數據傳輸的安全性。若是選擇的密鑰交換算法是DH或者DHE,則可能有兩種狀況:隱式DH公開值:包含在Certificate消息裏; 顯示DH公開值:公開值是本消息的一部分。
CertificateVerify:這條消息用來證實客戶端擁有以前提交的客戶端證書的私鑰,當使用了雙向認證的時候,這條消息將會攜帶一條客戶端用本身的私鑰生產一個
數字,服務器收到這條數據後,會將以前收到客戶端的公鑰進行解密驗證。
ChangeCipherSpec 協議:client端接着發一條該消息代表握手協議已經完成同時也切換到了加密模式了;
Finished:代表握手階段結束。這是第一條用協商的算法和密鑰保護的消息。由於是用協商好的密鑰加密的消息,它能夠用來確認已經協商好的密鑰。同時Finished消息包含一個verify_data域,能夠用來校驗以前發送和接收的信息。 Verify_data域是一個PRF函數的輸出(pseudo-random function)。這個僞隨機函數的輸入爲:(1)兩個hash值:一個SHA-1,一個MD5,對以前握手過程當中交換的全部消息作
加密哈希功能(消息摘要),在這裏server就能夠根據數據的哈希值和MD5值進行驗證傳輸數據的完整性;(2)the MasterSecret,由預備主密鑰生成。
服務器端收到client端ChangeCipherSpec 和Finished消息以後,接着也會發送一條ChangeCipherSpec 消息表面服務器端也切換到了加密模式,握手協議到此結束了。同時也會接着發一條Finished代表已經收到了client 端的Finished請求正式結束握手。此外,Finished 消息不可以在ChangeCipherSpec前發送。
咱們不難發如今SSL/TSL通訊協議其實就是兩種算法的使用:
對稱加密和
非對稱加密。在協議握手環節雙方獲取彼此的公鑰解析數據從而產生和獲取接下來的通訊祕鑰這裏就是一個非對稱加密過程。接下來使用產生的通訊祕鑰進行解密通訊數據,這就是一個對稱加密的過程。
除此以外,如下幾個名詞咱們也須要了解:
Key:屬於JCE的範疇,可分爲對稱key和非對稱key。對稱key就是接下來的通訊雙方用來加密數據的祕鑰,非對稱key就是公鑰和私鑰了。其內部表示形式就是一個類,其外部表示形式就 是一個比特(bit)字符串,key就是用來參與加密解密數據的,就像是一把開鎖的鑰匙。php
對稱加密(symmetric cryptography):就是需要兩邊應用同樣的 key 來加密解密消息算法,經常使用密鑰算法有 Data Encryption Standard(DES)、triple-strength DES(3DES)、Rivest Cipher 2 (RC2)和 Rivest Cipher 4(RC4)。由於對稱算法效力相對較高,是以 SSL 會話中的敏感數據都用經由過程密鑰算法加密。html
非對稱加密(asymmetric cryptography):就是 key 的構成是公鑰私鑰對 (key-pair),公鑰傳遞給對方私鑰自己保存。公鑰私鑰算法是互逆的,一個用來加密,另外一個能夠解密。經常使用的算法有 Rivest Shamir Adleman(RSA)、Diffie-Hellman(DH)。非對稱算法策畫量大斗勁慢,是以僅實用於少許數據加密,如對密鑰加密,而不合適多量數據的通訊加密。java
加密哈希功能(Cryptographic Hash Functions): 加密哈希功能與 checksum 功能相相似。區別在於,checksum 用來偵測不測的數據變動而前者用來偵測有心的數據批改。數據被哈希後產生一小串比特字符串,眇小的數據改變將致使哈希串的變動。發送加密數據時,SSL 會應用加密哈希功能來確保數據一致性,用來阻攔第三方破損通訊數據徹底性。SSL 經常使用的哈希算法有 Message Digest 5(MD5)和 Secure Hash Algorithm(SHA)。android
消息認證碼(Message Authentication Code): 消息認證碼與加密哈希功能相相似,在哈希加密機基礎上它需要將密鑰信息與加密哈希功能產生的數據連絡就是哈希消息認證碼(HMAC)。如果 A 要確保給 B 發的消息不被 C 批改,他要按以下步調作 --A 起首要生成一個 HMAC值;,將其添加到原始消息後面。用 A 與 B 之間通訊的密鑰加密消息體,而後發送給 B。B 收到消息後用密鑰解密,而後就通訊數據從新建立一個 HMAC值,將先後兩個值進行對比來判定消息是否在傳輸中被批改。git
數字(Digital Signature):一個消息的加密哈希被建立後,哈希值用發送者的私鑰加密,加密的成果就是叫作數字。算法
三:實際應用說明
這個案例分兩個部分,一個是服務端,另一個是客戶端。服務端打印出接收到的客戶端數據,即打印客戶端發來的一個字符串。
server端:
void initServer(){
try {
//獲取ssl協議的安全環境,或者TLS
SSLContext sContext = SSLContext. getInstance("SSL");
//獲取jks算法格式的祕鑰存儲器-常見的有JKS, JCEKS,and PKCS12。其中功能比較全的是JCEKS
KeyStore store = KeyStore. getInstance("JKS");
AssetManager manager = mContext.getAssets();
InputStream is = manager.open( "test_key_store");
//將證書導入到祕鑰存儲器中,同時給其配置一個打開密碼。有時候咱們須要有不一樣類型的祕鑰須要多個祕鑰存儲器來存儲
store.load( is, "kevin".toCharArray());
is.close();
//爲了管理多個或多種祕鑰存儲器,這裏引入了一祕鑰管理器這個概念,就是專門管理多個或多種祕鑰存儲器
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory. getDefaultAlgorithm());
//給每一個祕鑰管理器的安置的祕鑰存儲器配置一個獲取密碼
factory.init( store, "123456".toCharArray());
/**
* 初始化 ssl協議安全環境。 init函數有三個參數,第一個是KeyManager數組,server端須要用它裏面的保存的私鑰來簽名證書
* 第二個是TrustManager數組,第三個是SecureRandom,用來建立隨機數的
* 對於server端而言,它不須要驗證客戶端證書,因此很顯然第一個參數用來建立服務端Socket的,而第二個參數用於建立客戶端Socket(也能夠都不填)
*/
sContext.init( factory.getKeyManagers(), null, null );
//如下就是配置 ip和端口來連接
InetAddress address = Inet4Address. getLocalHost();
SSLServerSocket serverSocket = (SSLServerSocket) sContext.getServerSocketFactory().createServerSocket(8666,1,address);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
byte[] data = new byte[1024];
int count = in .read(data );
Log. i("TAG", "DATA:"+new String(data , 0, count ));
in.close();
socket.close();
serverSocket.close();
} catch (NoSuchAlgorithmException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyManagementException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
client端:
void initClient(){
try {
//獲取ssl協議的安全環境
SSLContext sContext = SSLContext. getInstance("SSL");
//獲取BKS算法格式的祕鑰存儲器-Android中通用的格式
KeyStore store = KeyStore. getInstance("BKS");
AssetManager manager = mContext.getAssets();
//這裏是使用Java自帶的工具 keytool建立的一個證書放在工程assert文件中
InputStream is = manager.open( "test_key_store");
//將證書導入到祕鑰存儲器中,同時給其配置一個打開密碼。有時候咱們須要有不一樣類型的祕鑰須要多個祕鑰存儲器來存儲
store.load( is, "kevin".toCharArray());
is.close();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory. getDefaultAlgorithm());
//初始化trustManagerFactory也就是將證書內容導入進去
trustManagerFactory.init(store );
/**
* 初始化 ssl協議安全環境。 init函數有三個參數,第一個是KeyManager數組,
* 第二個是TrustManager數組,第三個是SecureRandom,用來建立隨機數的
* 對於client端而言,它須要驗證服務端證書,因此只須要能夠去而第二個參數(也能夠都不填)
*/
sContext.init( null, trustManagerFactory .getTrustManagers(), null);
//如下就是配置 ip和端口來連接
InetAddress address = Inet4Address. getLocalHost();
SSLSocket socket = (SSLSocket) sContext.getSocketFactory().createSocket(address ,8666);
OutputStream out = socket.getOutputStream();
String data = "hello i'm spencer but you can call me kevin" ;
out.write( data.getBytes());
out.close();
socket.close();
} catch (NoSuchAlgorithmException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyManagementException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
四:小結
JSSE介紹就到此爲止。
有關JCE的內容之後有時間會給他整理出來,以上有哪裏描述不對的或者有疑問的能夠說出來一塊兒探討探討!