一、SSL協(xié)議的設(shè)計思想
上一篇文章通過三個例子說明了HTTP協(xié)議存在的三個安全性問題:
- 通信內(nèi)容可以被竊聽
- 通信內(nèi)容可以被篡改
- 通信對象可以被冒充
或許有人會想在WEB應(yīng)用層面解決這個問題,但是這樣做有幾個缺點:
- 大大增加了WEB應(yīng)用的實現(xiàn)難度,除了WEB應(yīng)用的業(yè)務(wù)邏輯,還必須為WEB應(yīng)用單獨設(shè)計編寫一套加密和驗證的方案;
- WEB應(yīng)用本身無法對整個HTTP數(shù)據(jù)包進(jìn)行加密,只能對數(shù)據(jù)包里面的關(guān)鍵內(nèi)容進(jìn)行加密,其他信息仍然有被竊取的風(fēng)險,所以基于WEB應(yīng)用的安全解決方案并不可靠;
- 基于WEB應(yīng)用的解決方案不具備通用性。
其實不光HTTP協(xié)議存在這個問題,幾乎大部分應(yīng)用層的協(xié)議都會存在這些問題,因為應(yīng)用層的數(shù)據(jù)包是直接遞送給傳輸控制層明文傳送的。
上個世紀(jì)90年代中期,網(wǎng)景公司為了解決HTTP協(xié)議明文傳送的安全性問題,設(shè)計了SSL(Secure Sockets Layer 安全套接層)協(xié)議。
SSL協(xié)議的思想是基于傳輸控制層協(xié)議(例如TCP)建立一個安全的網(wǎng)絡(luò)連接層。
簡單來說,就是應(yīng)用層和傳輸層之間多了一個安全套接層,應(yīng)用層的數(shù)據(jù)先遞送給安全套接層,安全套接層對應(yīng)用層的數(shù)據(jù)進(jìn)行分段、壓縮、添加消息認(rèn)證碼和加密之后,再往下遞送給傳輸層進(jìn)行傳送。同樣,傳輸層把接收到的數(shù)據(jù)先傳給安全套接層,由安全套接層解密、驗證消息完整性、解壓并且組裝之后再傳給應(yīng)用層。
這樣一來,傳輸層負(fù)責(zé)提供可靠的網(wǎng)絡(luò)連接,應(yīng)用層負(fù)責(zé)處理業(yè)務(wù),中間的數(shù)據(jù)安全由一個單獨的層面來負(fù)責(zé),大家各司其職,分工明確,安全套接層替代傳輸層為應(yīng)用層直接提供安全且可靠的數(shù)據(jù)傳輸。這樣,所有應(yīng)用層的協(xié)議都可以配合SSL協(xié)議實現(xiàn)安全的數(shù)據(jù)傳輸,相對于基于應(yīng)用的安全解決方案,這個方案更加通用可靠,也使得應(yīng)用開發(fā)者可以專心處理業(yè)務(wù)邏輯而無需為應(yīng)用的數(shù)據(jù)安全作過多的思考。
二、SSL協(xié)議的組成
SSL協(xié)議由以下四個子協(xié)議組成:
-
記錄協(xié)議(Record Protocol)
記錄協(xié)議工作在SSL的底層,主要職責(zé)是接收上層協(xié)議或下層協(xié)議的消息并進(jìn)行一系列的處理,然后再將處理后的消息繼續(xù)向下或向上傳遞。對于從上層協(xié)議接收的消息,記錄層的處理步驟是:將消息分段、壓縮、添加消息認(rèn)證碼以及加密;對于從下層協(xié)議接收的消息,記錄層的處理步驟是:解密、驗證消息完整性、解壓以及重新組裝消息。其實,記錄層的角色就像一個郵遞員,只負(fù)責(zé)按照記錄協(xié)議的規(guī)則進(jìn)行搬磚工作。另外,記錄層的上層協(xié)議除了應(yīng)用層協(xié)議之外,還有SSL的其他子協(xié)議。 -
握手協(xié)議(Handshake Protocol)
SSL的握手協(xié)議又是干嘛的呢?跟TCP的握手協(xié)議有什么區(qū)別?
SSL協(xié)議的主要功能是為應(yīng)用層提供數(shù)據(jù)加密等安全服務(wù),那在開始對應(yīng)用層的數(shù)據(jù)進(jìn)行傳輸之前,通信雙方必須得知道該使用什么算法加密數(shù)據(jù)以及對應(yīng)的加密密鑰是什么吧?因此,在開始對應(yīng)用層的數(shù)據(jù)進(jìn)行傳輸之前,通信雙方必須得有一個協(xié)商的過程,SSL的握手協(xié)議就是對這個過程該協(xié)商什么以及怎么協(xié)商的一個規(guī)定。
SSL的握手協(xié)議是建立在TCP握手協(xié)議之上的。TCP握手協(xié)議的作用是建立一個可靠的網(wǎng)絡(luò)連接,在TCP連接建立之后,SSL層就可以利用建立的TCP連接傳輸數(shù)據(jù),此時進(jìn)入SSL的握手協(xié)商階段。SSL的握手協(xié)議主要目的是為通信雙方確立安全連接所需要的安全參數(shù),通常也會在此階段對通信雙方身份的真實性進(jìn)行驗證。 -
警告協(xié)議(Alert Protocol)
無論是在握手階段還是在對應(yīng)用層數(shù)據(jù)的傳輸階段,都有可能出現(xiàn)差錯。警告協(xié)議規(guī)定了在SSL協(xié)議工作過程中可能出現(xiàn)的差錯、錯誤的嚴(yán)重等級以及相應(yīng)的處理方式。 -
密碼規(guī)范改變協(xié)議(Change Cipher Protocol)
在SSL握手剛開始的時候,由于加密參數(shù)還沒確定,消息都是明文傳送的;當(dāng)雙方在協(xié)商好加密參數(shù)之后,通信雙方在發(fā)送握手結(jié)束消息之前,需要發(fā)送一個密碼規(guī)范改變消息(Change Cipher Message)來通知對方隨后的消息都使用剛剛協(xié)商好的加密算法和加密密鑰進(jìn)行加密。
對于握手協(xié)議和警告協(xié)議,它們包含許多種消息類型,而對于密碼規(guī)范改變協(xié)議,它只有包含一種消息,該消息只是一個簡單的通知。也許你會覺得奇怪,為什么不將這個如此簡單的通知消息作為握手消息的一個子類型,而需要單獨為它設(shè)置一個消息類型呢?后面會對此作出解釋。
SSL協(xié)議的組成如下圖所示:
三、SSL會話(Session)和連接(Connection)
會話是一個虛擬概念,在很多地方我們都會碰到這個術(shù)語。那在SSL協(xié)議中,會話代表什么意思呢?
前面我們提到,在應(yīng)用層的數(shù)據(jù)開始傳輸之前,通信雙方需要通過握手協(xié)議來協(xié)商一些安全參數(shù)。但是,握手過程的開銷比較大,如果每次建立連接都要完整的走一遍握手過程來重新協(xié)商一遍安全參數(shù),那么客戶端每一次建立連接都會很慢,而且還會占用過多的網(wǎng)絡(luò)帶寬資源。為了避免這種情況發(fā)生,客戶端和服務(wù)器可以把協(xié)商好的結(jié)果緩存起來并賦予它唯一的標(biāo)識,當(dāng)客戶端在一定期限內(nèi)再次發(fā)起連接請求的時候,服務(wù)器和客戶端就可以重用之前協(xié)商好的結(jié)果而不用走完整個握手流程。因此,會話可以理解為:
在特定的時間內(nèi),特定的客戶端和特定的服務(wù)器之間的所有通信內(nèi)容的總和
也就是說,一次會話,可能會包含多次連接、多次消息往返。一次會話內(nèi)的所有連接,可以共享一些信息。比如,壓縮方法、加密算法、哈希算法等。此外,SSL會話還必須保存一個稱之為主秘密(master secret)的信息,客戶端和服務(wù)器可以根據(jù)這個信息導(dǎo)出本次連接所需要的密鑰及其他一些重要參數(shù)。
以下是SSL會話(Session)的屬性:
-
會話標(biāo)識(session identifier)
服務(wù)器端生成的隨機(jī)字節(jié)序列。 -
對端證書(peer certificate)
對端的X509.v3證書,該狀態(tài)可能為null。 - 壓縮方法(compression method)
-
密碼規(guī)范(cipher spec)
指定數(shù)據(jù)加密算法(例如null,DES等)和MAC算法(例如MD5或者SHA),密碼規(guī)范還定義了一些加密屬性,例如hash_size。 -
主秘密(master secret)
服務(wù)端和客戶端共享的48字節(jié)秘密。根據(jù)它可以導(dǎo)出每個連接所需的安全參數(shù)。 -
是否可重用(is resumable)
標(biāo)志位,用來表明當(dāng)前會話是否還可以用來初始化新的連接。
以下則是SSL連接(Connection)所必備的一些屬性:
-
服務(wù)器端和客戶端隨機(jī)數(shù)(server and client random)
服務(wù)器和客戶端為每次連接生成的隨機(jī)字節(jié)序列。要這個有什么用呢?這個兩個隨機(jī)數(shù)會和會話的主秘密(master secret)一起經(jīng)過一定的運算生成連接所需的加密密鑰和MAC秘密。這兩個隨機(jī)數(shù)保證了每次連接都會生成不同的加密密鑰和MAC秘密,保證了連接的安全性。 -
服務(wù)端寫MAC秘密(server write MAC secret)
服務(wù)端用于生成消息認(rèn)證碼(Message Authentication Code)的秘密。 -
客戶端寫MAC秘密(client write MAC secret)
客戶端用于生成消息認(rèn)證碼(Message Authentication Code)的秘密。 -
服務(wù)端寫密鑰(server write key)
服務(wù)端加密數(shù)據(jù)用的密鑰和客戶端解密數(shù)據(jù)用的密鑰。 -
客戶端寫密鑰(client write key)
客戶端加密數(shù)據(jù)用的密鑰和服務(wù)端解密數(shù)據(jù)用的密鑰。 -
初始向量(initialization vectors)
當(dāng)使用CBC模式的分組密碼算法時,對應(yīng)每一個加密密鑰都要維持一個初始化向量(IV)。也就是說,對應(yīng)于服務(wù)端寫密鑰(server write key)要有一個IV,對應(yīng)于客戶端寫密鑰(client write key)也要有一個。初始向量的作用是避免多次對同一文本加密產(chǎn)生相同的密文,它保證了密文的隨機(jī)性。初始向量的值由握手協(xié)議初始化,此后每一條記錄的最后一個密文分組作為下一條記錄的初始化向量。 -
序列號(sequence numbers)
通信雙方會各自為發(fā)送出去和接收到的消息維護(hù)自己的序列號。
注:上面提到的消息認(rèn)證碼MAC的作用和計算方法后文會有介紹
四、記錄層(Record Layer)
前面已經(jīng)對記錄層的職能作了描述,這里就不再重復(fù)了。接下來主要詳細(xì)介紹一下記錄層的三個工作階段:分段和組裝、記錄壓縮和解壓、記錄保護(hù)。
4.1 分段和組裝
記錄層把從上層接收到的數(shù)據(jù)塊分成小于或者等于2^14
字節(jié)的SSLPlaintext
記錄。SSLPlaintext
的數(shù)據(jù)結(jié)構(gòu)如下:
struct {
uint8 major, minor;
} ProtocolVersion;
enum {
change_cipher_spec(20),
alert(21),
handshake(22),
application_data(23),
(255)
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[SSLPlaintext.length];
} SSLPlaintext;
- type:消息類型,記錄層傳輸?shù)南㈩愋陀?種:密碼規(guī)范改變消息、警告消息、握手消息和應(yīng)用數(shù)據(jù)消息;
- version: 協(xié)議版本,本文介紹的是SSL 3.0;
-
length:消息長度,即
SSLPlaintext.fragment
的長度,不能超過2^14
字節(jié); - fragment:消息內(nèi)容。
相反,解壓后的記錄可能會被重新組裝成更大的數(shù)據(jù)塊再往上層傳送。
注意1:如果上層傳遞下來的消息體積比較小,那么多個同種類型的上層消息有可能會被合并成一個SSLPlaintext
記錄。密碼規(guī)范改變消息被設(shè)計成一個單獨的消息類型而不是作為握手消息的一部分跟這個特性有關(guān),后面會講具體原因。
注意2:不同類型數(shù)據(jù)的傳輸可能不是嚴(yán)格按先后順序的,有可能是交叉的。通常應(yīng)用層數(shù)據(jù)的傳輸優(yōu)先級要低于其他類型的。
4.2 記錄壓縮解壓
所有記錄都使用會話狀態(tài)中定義的壓縮算法進(jìn)行壓縮。初始的時候壓縮算法的值被定義成CompressionMethod.null
,此時壓縮是一個恒等操作,即壓縮前后的數(shù)據(jù)是一樣的。壓縮操作將記錄從SSLPlaintext
轉(zhuǎn)換成SSLCompressed
。如下:
struct {
ContentType type; /* same as SSLPlaintext.type */
ProtocolVersion version;/* same as SSLPlaintext.version */
uint16 length;
opaque fragment[SSLCompressed.length];
} SSLCompressed;
-
length:
SSLCompressed.fragment
的長度,不能超過2^14 + 1024字節(jié); -
fragment:
SSLPlaintext.fragment
的壓縮形式。
壓縮必須是無損壓縮,并且壓縮后的體積增長不能超過1024字節(jié)。如果解壓函數(shù)遇到一個解壓后體積會超過2^14字節(jié)的SSLCompressed.fragment
,它會發(fā)出一個錯誤等級為嚴(yán)重的decompression_failure
警告消息。
解壓操作則是反過來,將記錄從SSLCompressed
轉(zhuǎn)換成SSLPlaintext
。
4.3 記錄保護(hù)
所有記錄都使用當(dāng)前會話狀態(tài)中的密碼規(guī)范CipherSpec
中定義的加密算法和MAC算法實現(xiàn)數(shù)據(jù)保護(hù)。密碼規(guī)范的初始值是SSL_NULL_WITH_NULL_NULL
,此時不提供任何安全保護(hù)。
一旦握手階段結(jié)束,通信雙方就有了共享的秘密(secrets)來加密記錄并且計算信息內(nèi)容的Message Authentication Code(MAC)。進(jìn)行加密操作和MAC操作的方法在密碼規(guī)范CipherSpec
中定義并且受密碼類型CipherSpec.cipher_type
的影響。加密函數(shù)和MAC函數(shù)將記錄從SSLCompressed
轉(zhuǎn)換成SSLCiphertext
,解密函數(shù)則相反。
struct {
ContentType type; /* same as SSLCompressed.type */
ProtocolVersion version; /* same as SSLCompressed.version */
uint16 length;
select (CipherSpec.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
} fragment;
} SSLCiphertext;
-
length
:SSLCiphertext.fragment
的長度,不能超過2^14+2048字節(jié); -
fragment
:SSLCompressed.fragment
的加密形式,包括MAC。
如上密文結(jié)構(gòu)所示,密碼類型不同,得到的加密數(shù)據(jù)的結(jié)構(gòu)也不同。密碼類型有兩種:序列密碼stream cipher
和分組密碼block cipher
。下面簡單介紹一下這兩種類型的密碼。
4.3.1 Null 或者標(biāo)準(zhǔn)序列密碼(standard stream cipher)
序列密碼算法(包括BulkCipherAlgorithm.null
)把SSLCompressed.fragment
結(jié)構(gòu)體轉(zhuǎn)換成序列密碼形式的SSLCiphertext.fragment
結(jié)構(gòu)體,如下。
stream-ciphered struct {
opaque content[SSLCompressed.length];
opaque MAC[CipherSpec.hash_size];
} GenericStreamCipher;
MAC的計算方法如下:
hash(MAC_write_secret + pad_2 +
hash(MAC_write_secret + pad_1 + seq_num +
SSLCompressed.type + SSLCompressed.length +
SSLCompressed.fragment));
-
pad_1:如果
hash
函數(shù)是MD5
,pad_1
為字符0x36
重復(fù)48次,如果hash
函數(shù)是SHA
,pad_1
為字符0x36
重復(fù)40次; -
pad_2:如果
hash
函數(shù)是MD5
,pad_2
為字符0x5c
重復(fù)48次,如果hash
函數(shù)是SHA
,pad_2
為字符0x5c
重復(fù)40次; - seq_num:該消息的序列號;
- hash:密碼套件中指定的哈希算法。
4.3.2 分組密碼(block cipher)
對于分組密碼算法(例如RC2或DES),加密函數(shù)和MAC函數(shù)會把SSLCompressed.fragment
結(jié)構(gòu)體轉(zhuǎn)換成分組密碼形式的結(jié)構(gòu)體SSLCiphertext.fragment
,如下。
block-ciphered struct {
opaque content[SSLCompressed.length];
opaque MAC[CipherSpec.hash_size];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
} GenericBlockCipher;
MAC的計算方法同上;
-
padding
:為了讓被加密內(nèi)容長度等于分組密碼長度整數(shù)倍而填充的字符; -
padding_length
:填充字符的長度,不超過分組密碼的長度,也可能是0。
注意:如果分組密碼采用的是CBC模式,第一條傳送紀(jì)錄的初始化向量(IV)是由握手協(xié)議初始化的,而接下來的記錄的初始化向量則是上一條記錄的最后一個密文分組。
五、密碼規(guī)范改變協(xié)議(Change Cipher Spec Protocol)
密碼規(guī)范改變協(xié)議是用來通知密碼策略變化的。該協(xié)議只包含一個消息,該消息由值為1的單字節(jié)組成,如下。
struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
改變密碼規(guī)范消息由客戶端或者服務(wù)器發(fā)送,來通知接收方接下來的消息記錄將會由剛剛協(xié)商好的密碼規(guī)范和密鑰進(jìn)行加密保護(hù)。
六、警告協(xié)議(Alert Protocol)
警告類型是SSL記錄層支持的消息類型之一。警告消息的內(nèi)容包含警告消息的嚴(yán)重程度和關(guān)于警告消息的一個描述。如下所示。
enum { warning(1), fatal(2), (255) } AlertLevel;
enum {
close_notify(0),
unexpected_message(10),
bad_record_mac(20),
decompression_failure(30),
handshake_failure(40),
no_certificate(41),
bad_certificate(42),
unsupported_certificate(43),
certificate_revoked(44),
certificate_expired(45),
certificate_unknown(46),
illegal_parameter (47) (255)
} AlertDescription;
struct {
AlertLevel level;
AlertDescription description;
} Alert;
一個fatal
等級的警告消息將導(dǎo)致連接的立即中斷。跟其他消息類型一樣,警告消息同樣會經(jīng)過記錄層進(jìn)行壓縮和加密。
6.1 關(guān)閉警告(Closure Alerts)
通信的雙方,在關(guān)閉連接的寫入端之前,要求發(fā)出一個close_notify
警告,對方也要響應(yīng)一個close_notify
警告并且立即關(guān)閉連接,丟棄正在寫入的內(nèi)容。連接關(guān)閉的發(fā)起方并不需要等到對方的close_notify
響應(yīng)才關(guān)閉讀出端。如果一個連接中斷了但是沒有發(fā)出close_notify
警告,那么與該連接相關(guān)聯(lián)的會話將不能重用。
6.2 錯誤警告(Error Alerts)
握手協(xié)議中的錯誤處理非常簡單。當(dāng)檢測到錯誤的時候,錯誤的檢測方發(fā)送一個消息個對方。當(dāng)發(fā)出或者接收到一個fatal
級別的警告消息時,雙方立即關(guān)閉連接。服務(wù)器和客戶端要丟棄跟此連接相關(guān)聯(lián)的會話ID(session identifiers)、密鑰(keys)和秘密(secrets)。
七、握手協(xié)議(Handshake Protocol)
在開始介紹握手協(xié)議之前,首先要明確握手協(xié)議的目標(biāo),即在開始傳輸應(yīng)用層數(shù)據(jù)之前協(xié)商出安全通信所需的安全參數(shù)。這些參數(shù)主要包括:采用的協(xié)議版本、壓縮算法、加密算法、哈希算法以及密鑰等。此外,還可能會對服務(wù)器和客戶端的真實身份進(jìn)行認(rèn)證。
握手過程的消息時序圖如下:
握手消息的數(shù)據(jù)結(jié)構(gòu)如下:
enum {
hello_request(0),
client_hello(1),
server_hello(2),
certificate(11),
server_key_exchange (12),
certificate_request(13),
server_hello_done(14),
certificate_verify(15),
client_key_exchange(16),
finished(20), (255)
} HandshakeType;
struct {
HandshakeType msg_type; /* handshake type */
uint24 length; /* bytes in message */
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
7.1 ClientHello
ClientHello
消息的結(jié)構(gòu)如下:
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-1>;
CompressionMethod compression_methods<1..2^8-1>;
} ClientHello;
- client_version:客戶端希望使用的協(xié)議版本,一般填寫客戶端支持的最新版本;
- random:客戶端生成的32字節(jié)的隨機(jī)數(shù),后面導(dǎo)出SSL連接所需的密鑰和秘密時需要用到它。客戶端隨機(jī)數(shù)與后面介紹到的服務(wù)器隨機(jī)數(shù)一起保證了SSL連接密鑰的隨機(jī)性。它的數(shù)據(jù)結(jié)構(gòu)如下:
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
- session_id:客戶端希望重用的會話ID。如果客戶端沒有可用的會話或者客戶端希望生成新的會話,那么這個值為空;
-
cipher_suites:客戶端支持的密碼套件列表,客戶端希望優(yōu)先使用的密碼套件排在前面。如果
session_id
的值不為空,那么該向量至少要包含對應(yīng)會話的密碼套件。密碼套件的內(nèi)容主要包括:密鑰交換算法、對稱加密算法、哈希算法等。 -
compression_methods:客戶端支持的壓縮方法列表,客戶端希望優(yōu)先使用的壓縮方法排在前面。如果
session_id
的值不為空,那么該向量至少要包含對應(yīng)會話的壓縮方法。
7.2 ServerHello
struct {
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
} ServerHello;
- server_version:服務(wù)器會從自己支持的協(xié)議的最高版本和客戶端支持的協(xié)議的最高版本中選擇一個較低版本的協(xié)議;
- random:服務(wù)端生成的32字節(jié)隨機(jī)數(shù),與客戶端的隨機(jī)數(shù)結(jié)構(gòu)相同;
-
session_id:本次會話ID。如果
ClientHello.session_id
不為空,服務(wù)器會查詢它的session
緩存。如果查到了匹配的會話信息并且服務(wù)器愿意使用指定的會話建立新的連接,服務(wù)器會返回一樣的會話ID。否則,服務(wù)器會新建一個會話并且新建會話的ID。 - cipher_suite:服務(wù)端從客戶端支持的密碼套件列表里面選擇的一個密碼套件;
- compression_method:服務(wù)端從客戶端支持的壓縮方法列表里面選擇的一個壓縮方法。
7.3 Server Certificate
如果服務(wù)器要求被認(rèn)證(通常情況都是這樣),在ServerHello
消息之后,服務(wù)器會立即發(fā)送它的證書給客戶端。證書的類型必須與選擇的密碼套件的密鑰交換算法相匹配。通常都是X.509.V3證書,當(dāng)密鑰交換算法為FORTEZZA
算法時,則是經(jīng)過修改的X.509證書。
證書的內(nèi)容一般可以分為三部分:
- 服務(wù)器的身份信息,例如域名、公司信息等;
- 服務(wù)器的公鑰;
- CA的簽名。
客戶端接收到服務(wù)器的證書后,首先驗證證書的簽名,如果沒有問題,則可進(jìn)一步認(rèn)證服務(wù)器的信息并可放心使用證書上附帶的服務(wù)器公鑰。
服務(wù)器的公鑰可能有兩種用法。最直接的用法就是將服務(wù)器公鑰用于密鑰交換。但是,在某些情況下,服務(wù)器的公鑰并不直接用于密鑰交換。服務(wù)器會根據(jù)選擇的密鑰交換算法發(fā)送額外的密鑰交換參數(shù)給客戶端。為了防止中間人攻擊,服務(wù)器會使用自己的私鑰給這些密鑰交換參數(shù)進(jìn)行簽名,此時客戶端可利用服務(wù)器證書中的公鑰驗證密鑰交換參數(shù)消息的簽名。
Certificate
消息的結(jié)構(gòu)如下:
struct {
ASN.1Cert certificate_list<1..2^24-1>;
} Certificate;
- certificate_list:證書鏈。證書鏈的概念請參考 SSL教程:什么是SSL證書鏈?。
7.4 Server Key Exchange
一般情況下,服務(wù)器是不用發(fā)送密鑰交換信息的,直接使用服務(wù)器證書中附帶的公鑰進(jìn)行密鑰交換即可。但是,在以下幾種情況下,服務(wù)器需要發(fā)送ServerKeyExchange
消息用于密鑰交換:
- 服務(wù)器沒有證書;
- 服務(wù)器有證書,但是證書中的公鑰長度過長,不能用來進(jìn)行密鑰交換;
- 密鑰交換算法為Diffie-Hellman算法且服務(wù)器證書中不包含Diffie-Hellman參數(shù);
- 密鑰交換算法為
FORTEZZA KEA
算法。
注:根據(jù)美國當(dāng)前的出口法,在從美國出口的軟件中,模數(shù)大于512位的RSA公鑰不能用來進(jìn)行密鑰交換。
根據(jù)選擇的密鑰交換算法的不同,ServerKeyExchange
消息要發(fā)送的參數(shù)是不同的。SSL使用的密鑰交換算法有三種:RSA
、Diffie-Hellman
和FORTEZZA KEA
。
ServerKeyExchange
消息的結(jié)構(gòu)如下:
struct {
select (KeyExchangeAlgorithm) {
case diffie_hellman:
ServerDHParams params;
Signature signed_params;
case rsa:
ServerRSAParams params;
Signature signed_params;
case fortezza_kea:
ServerFortezzaParams params;
};
} ServerKeyExchange;
對于RSA算法:
struct {
opaque rsa_modulus<1..2^16-1>;
opaque rsa_exponent<1..2^16-1>;
} ServerRSAParams;
對于DH算法:
struct {
opaque dh_p<1..2^16-1>;
opaque dh_g<1..2^16-1>;
opaque dh_Ys<1..2^16-1>;
} ServerDHParams;
對于FORTEZZA KEA算法:
struct {
opaque r_s [128];
} ServerFortezzaParams;
7.5 Certificate Request
當(dāng)服務(wù)器要對客戶端的身份進(jìn)行認(rèn)證時,需要發(fā)送此消息。
7.6 ServerHello Done
ServerHelloDone
消息表示Server Hello階段的結(jié)束。發(fā)出該消息后,服務(wù)器會等待客戶端的響應(yīng)。如果客戶端需要對服務(wù)器的身份進(jìn)行認(rèn)證,那么在客戶端收到ServerHelloDone
消息時,就需要認(rèn)證服務(wù)器發(fā)過來的證書。
ServerHelloDone
的消息結(jié)構(gòu)如下:
struct { } ServerHelloDone;
7.7 Client Certificate
當(dāng)服務(wù)器要對客戶端的身份進(jìn)行認(rèn)證時,需要發(fā)送此消息。
7.8 Client Key Exchange
客戶端密鑰交換消息的內(nèi)容取決于選擇了哪一種密鑰交換算法,如下:
struct {
select (KeyExchangeAlgorithm) {
case rsa: EncryptedPreMasterSecret;
case diffie_hellman: ClientDiffieHellmanPublic;
case fortezza_kea: FortezzaKeys;
} exchange_keys;
} ClientKeyExchange;
-
RSA
當(dāng)使用RSA密鑰交換算法時,由客戶端生成一個48字節(jié)的預(yù)備主秘密(premaster secret)并用服務(wù)器證書中提供的公鑰或者ServerKeyExchange
消息中提供的臨時RSA公鑰加密后發(fā)送給服務(wù)器。 -
Diffie-Hellman
當(dāng)使用Diffie-Hellman密鑰交換算法時,客戶端是需要把它的DH公開值傳回給服務(wù)器。這時分兩種情況。如果客戶端證書中已經(jīng)包含了DH公開值,則客戶端密鑰交換消息不用再傳送DH公開值,而是傳送一個空消息;如果客戶端證書沒有包含DH公開值,則客戶端密鑰交換消息需要傳送DH公開值。這樣,根據(jù)DH算法,客戶端和服務(wù)器都有足夠的參數(shù)來生成共同的預(yù)備主秘密(premaster secret)。 -
FORTEZZA KEA
當(dāng)使用FORTEZZA KEA密鑰交換算法時,客戶端會根據(jù)FORTEZZA KEA算法首先計算出一個令牌加密密鑰TEK。然后,由客戶端生成客戶端寫密鑰client_write_key
、服務(wù)端寫密鑰server_write_key
、客戶端寫密鑰對應(yīng)的初始向量IVclient_write_iv
、服務(wù)端寫密鑰對應(yīng)的初始向量IVserver_write_iv
以及48字節(jié)的預(yù)備主秘密(premaster secret)并用TEK進(jìn)行加密后傳給服務(wù)器。當(dāng)然了,除了這幾個參數(shù)外,還要傳送其他的參數(shù)來讓服務(wù)器能夠產(chǎn)生根客戶端相同的TEK。
到此,應(yīng)該說客戶端和服務(wù)器已經(jīng)完成了密鑰的交換。因為利用預(yù)備主秘密(premaster secret)可以導(dǎo)出主秘密(master secret),利用主秘密(master secret)最后可以導(dǎo)出最后使用的各種secret、key和IV。
主秘密(master secret)的計算方式如下:
master_secret =
MD5(pre_master_secret + SHA(’A’ + pre_master_secret +
ClientHello.random + ServerHello.random)) +
MD5(pre_master_secret + SHA(’BB’ + pre_master_secret +
ClientHello.random + ServerHello.random)) +
MD5(pre_master_secret + SHA(’CCC’ + pre_master_secret +
ClientHello.random + ServerHello.random));
最終使用的密鑰、MAC秘密和初始向量IV的生成方式如下:
key_block =
MD5(master_secret + SHA(‘A’ + master_secret +
ServerHello.random +
ClientHello.random)) +
MD5(master_secret + SHA(‘BB’ + master_secret +
ServerHello.random +
ClientHello.random)) +
MD5(master_secret + SHA(‘CCC’ + master_secret +
ServerHello.random +
ClientHello.random)) + [...];
依次類推,直到有足夠的輸出為止。然后,key_block
會被分割成所需的參數(shù)。
client_write_MAC_secret[CipherSpec.hash_size]
server_write_MAC_secret[CipherSpec.hash_size]
client_write_key[CipherSpec.key_material]
server_write_key[CipherSpec.key_material]
client_write_IV[CipherSpec.IV_size] /* non-export ciphers */
server_write_IV[CipherSpec.IV_size] /* non-export ciphers */
key_block
中多余的數(shù)據(jù)會被丟棄。
對于出口加密算法(CipherSpec.is_exportable
為true
),還需經(jīng)過以下步驟才能得出最終的密鑰和初始向量。
final_client_write_key = MD5(client_write_key +
ClientHello.random +
ServerHello.random);
final_server_write_key = MD5(server_write_key +
ServerHello.random +
ClientHello.random);
client_write_IV = MD5(ClientHello.random + ServerHello.random);
server_write_IV = MD5(ServerHello.random + ClientHello.random);
注:對于FORTEZZA KEA密鑰交換算法,主秘密(master secret)只用來生成MAC秘密。
7.9 Certificate Verify
CertificateVerify
消息的作用是證明客戶端擁有與剛剛所發(fā)送證書對應(yīng)的私鑰。它是一個簽名消息,服務(wù)器接收到此消息之后,只要使用客戶端證書中提供的公鑰驗證簽名即可。
7.10 Finished
結(jié)束消息總是在ChangeCipherSpec
消息之后立即發(fā)送。結(jié)束消息是用來確認(rèn)密鑰交換和認(rèn)證過程的成功結(jié)束的。結(jié)束消息是第一個使用剛剛協(xié)商好的加密算法、密鑰和秘密進(jìn)行加密保護(hù)和完整性保護(hù)的消息。通信雙方在發(fā)送結(jié)束消息之后就可以開始傳送應(yīng)用層數(shù)據(jù)了。結(jié)束消息的接收方要驗證結(jié)束消息的正確性。結(jié)束消息的結(jié)構(gòu)如下:
enum {
client(0x434C4E54), server(0x53525652)
} Sender;
struct {
opaque md5_hash[16];
opaque sha_hash[20];
} Finished;
- md5_hash
MD5(master_secret + pad2 + MD5(handshake_messages + Sender + master_secret + pad1));
- sha_hash
SHA(master_secret + pad2 + SHA(handshake_messages + Sender + master_secret + pad1));
其中,handshake_messages
包括除結(jié)束消息外的所有握手消息。ChangeCipherSpec
消息不算在內(nèi),因為它不屬于握手消息的一部分。那為什么不把ChangeCipherSpec
消息作為握手消息的一部分而要把它獨立出去呢?因為記錄層有可能會把幾個類型相同的消息合并成一個消息記錄傳送呢,如果ChangeCipherSpec
作為握手消息的一部分,那么ChangeCipherSpec
消息很有可能會和其它握手消息合并在一起傳送。然而,我們希望看到的結(jié)果是,通信的雙方在收到ChangeCipherSpec
消息之后,立即將協(xié)商好的密碼規(guī)范應(yīng)用到ChangeCipherSpec
之后的消息。如果是將ChangeCipherSpec
作為握手消息的一部分的話,就有可能存在問題。因此,需要將它獨立出去。詳細(xì)的解析請參考 Why is change cipher spec an independent protocol content type and not part of Handshake Messages?
另外,當(dāng)客戶端和服務(wù)器重用會話而非新建會話時,握手消息的時序如下圖所示:
在
ServerHello
消息之后,服務(wù)器直接發(fā)送ChangeCipherSpec
消息和Finished
消息;客戶端緊接著也直接發(fā)送ChangeCipherSpec
消息和Finished
消息。握手過程結(jié)束。在重用會話的情況下,客戶端和服務(wù)器不用走完整個握手流程,因為通信雙方可以根據(jù)會話狀態(tài)中保存的主秘密(master secret)、ClientHello.random以及ServerHello.random直接導(dǎo)出加密密鑰、MAC秘密和初始向量IV。
八、總結(jié)
- SSL協(xié)議在傳輸控制層的基礎(chǔ)上建立了安全的連接,它作為一種通用可靠的安全解決方案,可與多種應(yīng)用層協(xié)議結(jié)合使用,實現(xiàn)應(yīng)用數(shù)據(jù)的安全傳輸。我們常見的https即為http協(xié)議與SSL協(xié)議(或者TLS協(xié)議)的結(jié)合;
- SSL協(xié)議是一個分層協(xié)議,由記錄協(xié)議(Record Protocol)、警告協(xié)議(Alert Protocol)、密碼規(guī)范改變協(xié)議(Change Cipher Protocol)和握手協(xié)議(Handshake Protocol)組成,其中記錄協(xié)議工作在最底層;
- SSL協(xié)議在進(jìn)行應(yīng)用數(shù)據(jù)傳輸之前,需要通過握手協(xié)議來協(xié)商安全通信所需的安全參數(shù)。
另外,此文對于SSL協(xié)議的解析是基于 RFC6101。個人水平有限,文中所述難免有誤,歡迎批評指正。