SSL
SSL(Secure Sockets Layer,安全套接層),及其繼任者 TLS(Transport Layer Security,傳輸層安全)是爲網絡通信提供安全及數據完整性的一種安全協議。TLS與SSL在傳輸層對網絡連接進行加密。
爲Netscape所研發,用以保障在Internet上數據傳輸之安全,利用數據加密(Encryption)技術,可確保數據在網絡上之傳輸過程中不會被截取及竊聽。
SSL協議位於TCP/IP協議與各種應用層協議之間,爲數據通訊提供安全支持。SSL協議可分爲兩層:
SSL記錄協議(SSL Record Protocol):它建立在可靠的傳輸協議(如TCP)之上,爲高層協議提供數據封裝、壓縮、加密等基本功能的支持。
SSL握手協議(SSL Handshake Protocol):它建立在SSL記錄協議之上,用於在實際的數據傳輸開始前,通訊雙方進行身份認證、協商加密算法、交換加密密鑰等。
提供服務
SSL協議提供的服務主要有:
1)認證用戶和服務器,確保數據發送到正確的客戶機和服務器;
2)加密數據以防止數據中途被竊取;
3)維護數據的完整性,確保數據在傳輸過程中不被改變。
工作流程
服務器認證階段:
1)客戶端向服務器發送一個開始信息“Hello”以便開始一個新的會話連接;
2)服務器根據客戶的信息確定是否需要生成新的主密鑰,如需要則服務器在響應客戶的“Hello”信息時將包含生成主密鑰所需的信息;
3)客戶根據收到的服務器響應信息,產生一個主密鑰,並用服務器的公開密鑰加密後傳給服務器;
4)服務器回覆該主密鑰,並返回給客戶一個用主密鑰認證的信息,以此讓客戶認證服務器。
用戶認證階段:
在此之前,服務器已經通過了客戶認證,這一階段主要完成對客戶的認證。經認證的服務器發送一個提問給客戶,客戶則返回(數字)簽名後的提問和其公開密鑰,從而向服務器提供認證。
SSL協議提供的安全通道有以下三個特性:
機密性:SSL協議使用密鑰加密通信數據。
可靠性:服務器和客戶都會被認證,客戶的認證是可選的。
完整性:SSL協議會對傳送的數據進行完整性檢查。
從SSL 協議所提供的服務及其工作流程可以看出,SSL協議運行的基礎是商家對消費者信息保密的承諾,這就有利於商家而不利於消費者。在電子商務初級階段,由於運作電子商務的企業大多是信譽較高的大公司,因此這問題還沒有充分暴露出來。但隨着電子商務的發展,各中小型公司也參與進來,這樣在電子支付過程中的單一認證問題就越來越突出。雖然在SSL3.0中通過數字簽名和數字證書可實現瀏覽器和Web服務器雙方的身份驗證,但是SSL協議仍存在一些問題,比如,只能提供交易中客戶與服務器間的雙方認證,在涉及多方的電子交易中,SSL協議並不能協調各方間的安全傳輸和信任關係。在這種情況下,Visa和 MasterCard兩大信用卡公組織制定了SET協議,爲網上信用卡支付提供了全球性的標準。
SSL 協議的握手過程
開始加密通信之前,客戶端和服務器首先必須建立連接和交換參數,這個過程叫做握手(handshake)。
假定客戶端叫做愛麗絲,服務器叫做鮑勃,整個握手過程可以用下圖說明(點擊看大圖)。
握手階段分成五步。
第一步,愛麗絲給出協議版本號、一個客戶端生成的隨機數(Client random),以及客戶端支持的加密方法。
第二步,鮑勃確認雙方使用的加密方法,並給出數字證書、以及一個服務器生成的隨機數(Server random)。
第三步,愛麗絲確認數字證書有效,然後生成一個新的隨機數(Premaster secret),並使用數字證書中的公鑰,加密這個隨機數,發給鮑勃。
第四步,鮑勃使用自己的私鑰,獲取愛麗絲髮來的隨機數(即 Premaster secret)。
第五步,愛麗絲和鮑勃根據約定的加密方法,使用前面的三個隨機數,生成"對話密鑰"(session key),用來加密接下來的整個對話過程。
上面的五步,畫成一張圖,就是下面這樣。
結合產品代碼來學習:
描述:
基於TLS v1.1實現信道加密協議。不直接在客戶端和服務器間使用TLS建立加密信道的原因是:(1)客戶端的本地TLS實現規範不統一,不能保證加密強度和實現的質量,(2)一些地區的移動運營商WAP網關對於建立HTTPS通道支持不穩定。所以,我們希望在HTTP之上建立加密信道,採用修改後的TLS作爲信道加密協議。
目標:
當前加密通道的建立與初始化頁面請求在同一個接口getui中實現,這樣對接口職責的獨立產生了一定的影響,同時不利於擴展。只有如老版的getkey接口才實現了接口職責獨立,但實際應用中,getkey又在擴展能力上明顯不足,比如當前登陸操作需要重建加密通道的情況下,getui接口無法實現,而getkey又因爲實際需要必須傳遞clienttype,這時又爲了避免明文傳送,不得不又對這個參數執行了多餘的RSA公鑰加密,此類設計預留的不足往往導致了後期擴展的情況下,客戶端和服務器端不得不引入更多的複雜設計,實現和維護的代價和成本都更高。
爲此,我們希望建立一個獨立的接口用於建立客戶端、服務器間的加密信道。該接口的設計需要參考TLS v1.1的實現。這個接口應和客戶端的用戶登陸等界面區分開;這樣,雖然效率可能降低,但是我們可以在任意業務流程中隨時建立加密通道,而不一定要求同時進行用戶的身份認證,從而將信道加密和身份認證完全分離。這樣的做法也是HTTP、HTTPS的實現方式。在加密信道之上,我們可以對於某些數據進行進一步加密。處理完全由應用層決定,和這裏描述的協議無關。
協議概述:
TLS提供兩類基礎協議:Record Protocol提供了對於應用數據的封裝,Handshaking Protocols提供了客戶端、服務器間對於安全參數的協商。其中,Handshaking Protocols包含三個子協議:加密算法修改協議(Change Cipher Protocol),告警協議(Alert Protocol),握手協議(Handshake Protocol);核心的安全參數協商協議爲握手協議。
我們要求所有的數據傳輸都基於HTTP:例如,所有的參數都基於HTTP POST提供。因此,我們不直接使用TLS,而是在TLS基礎修改。
/**
* 建立加密信道
*
* @param context
* @param sendData 項目中需要向加密信道傳遞的數據
* @param isOfflineUpdate 是否在握手時做離線更新
*
* @throws Exception
*/
public ClientHello(final Context context, String sendData, boolean isOfflineUpdate)
throws Exception {
//是否離線更新
mIsOfflineUpdate = isOfflineUpdate;
//獲取配置參數
mEMPConfig = EMPConfig.newInstance();
//初始化TLS參數
initTlsData(context);
//雙向驗證標記
readClientTwoWaySign();
mCurContext = context;
//服務器地址
String url = mEMPConfig.getServerUri();
String version = Utils.getVersionName(context);//客戶端版本號
// get RNS2.服務器隨機數
byte[] rns2 = readServerRandom2(context);
// get Server Certificate.服務器證書
byte[] cerByts = readServerCertificate(context);
// send ClientHello request.走簡化流程
if (rns2 != null && rns2.length > 0 && cerByts != null && cerByts.length > 0) {
Object certification = RSAAdapter.getCertificate(cerByts);
mServerPubKey = RSAAdapter.getPublicKey(certification);
mRNS = rns2;
mRNS2 = rns2;
facilityClientHello(context, url, version, sendData);
//走全部流程
fullClientHello(context, url, version, sendData);
}
OfflinePerfTestManager.printDuration(OfflinePerfTestManager.CLIENTHEOOL);
}
走全部流程代碼:
/**
* 全流程信道建立
*
* @param context
* @param url
* @param version
* @param sendData
*
* @throws Exception
*/
final void fullClientHello(final Context context, final String url, final String version,
String sendData) throws Exception {
// get ClientHello body組裝客戶端信息爲body,之後發送網絡請求給服務器驗證
final byte[] body = createFullClientHelloBody();
String isfirst = AndroidPreferenceDB.ANDROIDDB.getString(AndroidPreferenceDB.ISFIRST_DB);
if (null == isfirst || isfirst.equals(""))
isfirst = "0";
//根據客戶端信息組裝url
String uri = url.concat(CLIENT_HELLO)
.concat("&clientinfo=").concat("android-").concat(Utils.getPhoneTarget())
.concat("-").concat(version).concat("-").concat(Utils.getClientID())
.concat("&is_first=").concat(isfirst).concat(sendData);
// store the Client Hello request body.
mClientHelloBody = body;
byte[] byts = null;
// get server hello.
//加密body
String bodyStr = Base64.encode(body);
try {
//發送網絡請求
byts = (byte[]) mHttpManager.sendPostRequest(uri, bodyStr, false, null, null, null);
} catch (HttpResponseException ex) {
String msg = EMPTips.getTLSHttpConnectFail();
String errorCode = EMPTips.getErrorCode();
if (!Utils.isEmpty(errorCode)) {
msg += errorCode + String.valueOf(mHttpManager.mResponseCode);
}
throw new Exception(msg);
}
final byte[] temp = byts;
//處理獲取到的服務器信息,保存服務器隨機數,利用本地保存的證書,獲取公鑰,用拿到的客戶端公鑰再驗證從服務器受到證書的有效性。驗證通過之後,通過獲取的服務器證書拿到服務器公鑰並保存到本地。最後,處理服務器是否發送雙向驗證消息,更新客戶端雙向驗證標記
handleFullServerHelloResponse(byts, context);
// send ClientKeyExchange.
/**
1.獲取用客戶端公鑰生成的服務端的RSA公鑰證書(通過客戶端的本地保存的雙向驗證公鑰PK.dat設備ID,組裝服務器認識的URL,發送POST網絡請求,得到用客戶端公鑰生成的服務器證書,並保存到本地)
2.getClientKeyExchangeBody(),預主密鑰mPMS+mRNS(握手得到的服務器隨機數)+額外信息+握手得到的服務器公鑰=組成clientKeyExchange
3.clientCertificate=getClientCertificateBody()拿到客戶端本地保存的雙向驗證公鑰證書
4.certificateVerify證書驗證,getCertificateVerifyBody(context, request1, reponse1, Utils.joinBytes(clientKeyExchange, clientCertificate));request1 = 客戶端握手時組裝的Body,reponse1=服務器返回的握手信息,第四個參數爲客戶端驗證服務器時的body+客戶端本地保存的證書,這步中把這三個參數拼接起來,先進行MD5加密,再進行SHA1加密,得到messageData,之後再用客戶端保存的雙向驗證客戶端私鑰的到簽名後的messageData,加上數據長度組裝成CertificateVerify body。
5.getChangeCipherSpecBody() 發送ChangeCipherSpec聲明切換到加密信道傳輸。
6.Utils.joinBytes(clientKeyExchange, clientCertificate, certificateVerify, changeCipherSpec);吧前面得到的客戶端交換信息,客戶端雙向驗證證書,簽名後的證書驗證信息(客戶端與服務器溝通的各種信息),是否改變加密算法集等組裝成request3Body,之後handshakeMsg = getHandshakeMessage(mClientHelloBody, mServerHelloBody, request3Body);request3Body+客戶端握手信息,服務器響應的握手信息。之後finish = getFinishBody(handshakeMsg);應用PRF加密算法 加密getVerifyData(handshakeMsg)預主密鑰+客戶端服務端隨機數通過PRF算法生成主密鑰,再用PRF算法加密主密鑰+(MD5+SHA1生成的handshakeMsg)生成最後的待驗證信息。
7.之後// create body
byte[] bodyByts = Utils.joinBytes(clientKeyExchange, clientCertificate, certificateVerify, changeCipherSpec, finish, offline);
創建body,String bodyStr = Base64.encode(bodyByts);加密body,reply = (byte[]) mHttpManager.sendPostRequest(url, bodyStr, false, null, HttpManager.MIME_ARC, task)發送網絡請求,服務器返回replay。此處爲byts
*/
byts = sendClientKeyExchange(context, url, version, body, temp, null);
Utils.printLog("fullClientHello", "");
/**處理上面服務器返回的數據,byts
服務器收到ClientKeyExchange並處理,返回消息,完成信道協商:
a. 使用私鑰解密並取出{PMS,ServerHello.Timestamp , ServerHello.Random, extensionField(最大32字節) }。使用PMS、(ClientHello.Timestamp+ClientHello.Random)[RNC]、(ServerHello.Timestamp+ServerHello.Random) [RNS]計算MS,並提取需要的extensionField數值。
b. 生成服務器的預主密鑰premaster secret2 [PMS2]。使用PMS2、RNC、RNS生成服務器主密鑰master secret2 [MS2],在會話中保存MS2作爲傳輸密鑰。
c. 如服務器選擇CipherSuite爲傳輸一次一密的特性,則使用MS2與每次請求報文頭中的X-EMP-SessionNum執行傳輸的一次一密。
d. 生成下次使用的服務器緩存隨機數(ServerHello.Timestamp+ServerHello.Random) [RNS2]。根據協商好的對稱加密算法,使用MS對稱加密tuple{ RNS2, PMS2},結果以二進制形式保存在消息ServerKeyExchange。其中該消息後有hmac的摘要簽名保障完整性。
e. 如果有ClientCertificate,驗證相關信息。驗證客戶端的Finished信息。如果錯誤,返回標準失敗信息。
f. 生成服務器的Finished,將Finished之前發出的和接收到消息(不包括Finished本身)的二進制數據,按照順序連接後,使用MS做PRF簽名。
g. 回傳:(1)ServerKeyExchange,(2)確認加密算法集ChangeCipherSpec,(3)確認傳輸密鑰的安全級別,(4)發送自己的Finished消息。
5. 客戶端收到服務器信息後:
a. 驗證服務器的Finished消息。如失敗,切斷當前連接。
b. 根據協商好的對稱加密算法,使用本地保存的MS解密ServerKeyExchange,使用MS對該消息後的hmac摘要簽名執行驗證,取出tuple{ RNS2, PMS2}。使用PMS2、RNC、RNS生成MS2作爲信道密鑰。在緩存中保存RNS2。
c. 如CipherSuite爲傳輸一次一密的特性,則使用MS2與每次請求或響應報文頭中的X-EMP-SessionNum執行傳輸的一次一密。
*/
String initContent = handleFullServerKeyExchangeResponse(byts, context);
setText(initContent);
mConnectTimes++;
}
握手時組裝的Body:
/**
* 【ClientHello】
*
* @return 組裝好的Body
*
* @throws Exception
*/
private final byte[] createFullClientHelloBody() throws Exception {
// ClientVesion
byte[] protocolVersion = getClientProtocolVersion();//拿到客戶端信道版本號
// ClientRandom
byte[] clientGmtUnixTime = Utils.getClientGMTUnixTime();//獲取客戶端時區信息的byte數組
byte[] clientRandom = getClientRandom(28);//生成一個28位的客戶端隨機數
mRNC = Utils.joinBytes(clientGmtUnixTime, clientRandom);//時區信息和28位隨機數,共同組成最終的客戶端隨機數
//獲取組編號
byte[] groupInfor = getGroupInfor();
//獲取加密算法
byte[] cipherSuiteInfor = getCipherSuiteInfor();
// Certificate_SerialNumber
//證書編號
byte[] certificateSerialNumberInfor = getCertSerialNumberInfor();
//組裝握手信息傳遞給服務器的數據
byte[] clientHelloData = Utils.joinBytes(protocolVersion, mRNC, groupInfor, cipherSuiteInfor, certificateSerialNumberInfor);
// message type and length
byte[] messageType = new byte[1];
messageType[0] = Constant.HandshakeType[Constant.htIndex.client_hello.ordinal()];
int mLen = clientHelloData.length;
byte[] messageLength = Utils.intToByteArrayInNBO(mLen);
// add MessageType
byte[] clientHelloBody = Utils.joinBytes(messageType, messageLength, clientHelloData);
//返回加入消息類型的body
return clientHelloBody;
}
SSL/TLS協議簇加解密流程 :http://blog.csdn.net/sealyao/article/details/5901510