安全協議系列(五)---- IKE 與 IPSec(中)


在上一篇中,搭建好了實驗環境。完整運行一次 IKE/IPSec 協議,收集相關的輸出及抓包,就可以進行協議分析。分析過程中,我們將使用 IKE 進程的屏幕輸出和 Wireshark 抓包,結合相關 RFC,利用 python 進行驗證計算。先看協議的一次完整運行(過濾掉無關報文,如下圖)

下面是 RFC 5996 中對 IKEv2 協議的規范說明

由上可知,IKEv2 協議由兩個階段的交互過程(即兩個來回,共四個報文)組成。第一階段稱為 IKE_SA_INIT 交換。第二階段稱為 IKE_AUTH 交換。通觀報文格式,前面都是 IKE 報文頭 HDR,后跟各類不同類型的載荷。HDR 包含協議發起者和響應者的 Security Parameter Indexes(SPIs),協議版本號,報文長度等一些固定的字段。載荷則是:不同的類型,代表不同的含義。典型載荷說明如下(摘抄自 RFC)

   Notation    Payload
   -----------------------------------------
   AUTH        Authentication
   CERT        Certificate
   CERTREQ     Certificate Request
   CP          Configuration
   D           Delete
   EAP         Extensible Authentication
   HDR         IKE header (not a payload)
   IDi         Identification - Initiator
   IDr         Identification - Responder
   KE          Key Exchange
   Ni, Nr      Nonce
   N           Notify
   SA          Security Association
   SK          Encrypted and Authenticated
   TSi         Traffic Selector - Initiator
   TSr         Traffic Selector - Responder
   V           Vendor ID

來看第一階段交互(IKE_SA_INIT),其第一個報文由 Initiator(Windows 7)發出,后面為 Responder(Linux)的響應報文。兩個報文的載荷基本相同,包括 SAi1/SAr1,KEi/KEr 和 Ni/Nr。SAi1/SAr1 記號中的數字1表示第一階段。

SAi1/SAr1 分別表示發起者和響應者可以支持的密碼算法套件,其作用類似於 TLS 報文中的 Cipher Suites,詳見下圖說明。KEi/KEr 和 Ni/Nr 也是密碼協議中的常客,分別表示雙方的 Diffie–Hellman 密鑰交換內容和一次性隨機數。可見在 IKE 協議中,強制使用 Diffie–Hellman 密鑰交換,從而達到 Perfect Forward Secrecy 效果,在這一點上是略勝 TLS 協議的。此外,第二個報文還包含證書請求載荷(CERTREQ)。因為在 ipsec.conf 配置文件中,啟用了證書認證(authby=pubkey)。

再看實際的抓包內容(第一個 IKE_SA_INIT 報文)

 

我們發現,除了剛才提到的 Security Association/Key Exchange/Nonce 載荷外,還出現了兩個 Notify 載荷。
Wireshark 中展開解析協議樹,原來是 NAT_DETECTION_SOURCE_IP 和 NAT_DETECTION_DESTINATION_IP 載荷。它們表示支持 NAT 穿越,與密碼學核心功能無關,不再討論。

現在重點看上圖中的 SAi1 載荷。SAi1 包含有 6 個 Proposal,具體的 Proposal 內容(展開經過重排后)為

    加密算法      完整性算法        偽隨機數生成函數   Diffie-Hellman 組
[1] 3DES_CBC HMAC_SHA1_96 PRF_HMAC_SHA1 MODP_1024
[2] AES_CBC_256 HMAC_SHA1_96 PRF_HMAC_SHA1 MODP_1024
[3] 3DES_CBC HMAC_SHA2_256_128 PRF_HMAC_SHA2_256 MODP_1024
[4] AES_CBC_256 HMAC_SHA2_256_128 PRF_HMAC_SHA2_256 MODP_1024
[5] 3DES_CBC HMAC_SHA2_384_192 PRF_HMAC_SHA2_384 MODP_1024
[6] AES_CBC_256 HMAC_SHA2_384_192 PRF_HMAC_SHA2_384 MODP_1024

每個 Proposal 由密碼學相關的四元組構成,即加密算法、完整性算法、偽隨機數生成函數、Diffie-Hellman 組。這四元組起什么作用?

暫且不表,繼續看第二個 IKE_SA_INIT 報文,strongSwan 回應的報文內容為:HDR, SAr1, KEr, Nr, N, N, CERTREQ, N。其中 KEr, Nr 和 KEi, Ni 一樣,都是一串二進制格式的內容。三個 Notify 我們不關心。只看 SAr1 和 CERTREQ。SAr1 中只包括一個 Proposal,內容為 3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024,這是 strongSwan 所支持的密碼算法套件。
對比上面的 SAi1 可知,雙方都同意:(后面要用到的)加密算法/完整性算法/偽隨機數生成函數/Diffie-Hellman 組分別為
3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024

再看 CERTREQ (證書請求)載荷,其內容如下

Certificate Authority Data,這是個什么東西?需要對方提供證書就罷了,怎么還有一串二進制內容?查看 RFC 5996,其中有這么一段文字

Certification Authority value is a concatenated list of SHA-1 hashes
of the public keys of trusted Certification Authorities (CAs). Each
is encoded as the SHA-1 hash of the Subject Public Key Info element
(see section 4.1.2.7 of [RFC3280]) from each Trust Anchor
certificate. The twenty-octet hashes are concatenated and included
with no other formatting.

原來其內容是一連串的 SHA-1 摘要值,而摘要的輸入則是 strongSwan 所信任 CA 證書中的公鑰信息。說得再詳細,就是 RFC 3280 中的 subjectPublicKeyInfo 部分,見下面

   TBSCertificate  ::=  SEQUENCE  {
        version         [0]  EXPLICIT Version DEFAULT v1,
        serialNumber         CertificateSerialNumber,
        signature            AlgorithmIdentifier,
        issuer               Name,
        validity             Validity,
        subject              Name,
        subjectPublicKeyInfo SubjectPublicKeyInfo,
        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        extensions      [3]  EXPLICIT Extensions OPTIONAL
                             -- If present, version MUST be v3
        }

   SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm            AlgorithmIdentifier,
        subjectPublicKey     BIT STRING  }

下圖標明了做摘要計算的公鑰信息在 CA 文件中的位置:第一個字節是 0x30,后跟 0x81 0x9F ……,直到黃色背景的最后一個字節(注意是 SEQUENCE SubjectPublicKeyInfo 的 DER 表示)

下面是驗證過程
D:\>dd if=ca.der bs=1 count=162 skip=195 of=subjectPublicKeyInfo iflag=binary oflag=binary
D:\>openssl dgst -sha1 subjectPublicKeyInfo
SHA1(subjectPublicKeyInfo)= 4f9e47fe96b05611c4d7f66dca3f265b32dde04d

看來 strongSwan 比較嚴謹:不僅要求對方提供證書,還要求提供的證書是由 strongSwan 認可的 CA 簽發的。

至此,IKEv2 的第一階段完成。在此階段,雙方就后續要用到的一系列密碼學算法達成一致,並准備好自己的 Diffie-Hellman 密鑰交換值和隨機數。稱第一階段協商的結果為 IKE SA。此外 strongSwan 還要求進行基於證書的身份認證。當然這一切都是明文傳輸。

隨后進入到第二階段,此階段進行 IKE_AUTH 交換,也包括來回兩個報文。第一個報文仍是 Windows 7 發起,第二個報文是 strongSwan 的響應。

回顧第一階段,經過 IKE_SA_INIT 交換后,雙方對后續通信具備了基本的保護能力,包括:報文加密、報文完整性保護。這是為什么?因為經過 SAi1/SAr1 比較,已經協商出四元組:加密算法、完整性算法、偽隨機數生成函數,及 Diffie-Hellman 組。其中,加密算法(即 3DES_CBC)用於報文的加密保護。HMAC_SHA1_96 算法則用於報文的完整性保護。算法達成一致后,下一個問題就是,密鑰從何而來?我們看到,四元組中還有 Diffie-Hellman 組,再加上 IKE_SA_INIT 交換中的 KEi 和 KEr 載荷。熟悉密碼協議的朋友能夠猜到,密鑰應該來自 KEi/KEr 的交換結果。在實際協議中,Diffie-Hellman 密鑰的交換結果,更多是充當“密鑰種子”的作用,而不會去直接參與加、解密等密碼學的基本運算。在這一點上,IKEv2 協議也不例外。重要的是,一旦有了“密鑰種子”,所有直接參與基本運算的密鑰就可以源源不斷地產生。那么如何產生?這就要靠上述四元組中的最后一個元素:偽隨機數生成函數。

思路厘清后,現在就看看 RFC 是如何將理論付諸實踐。先看密鑰種子的生成,在 IKEv2 中,其生成公式為

SKEYSEED = prf(Ni | Nr, g^ir)

上述公式中,g^ir 是 Diffie-Hellman 密鑰交換值,prf 就是四元組中的偽隨機數生成函數(即 PRF_HMAC_SHA1,參見 RFC 2104)。而且 SKEYSEED 並沒有直接把 g^ir 拿過來用,還加上了 Ni、Nr 的影響。協議設計者的思考可謂周全。當然要注意,g^ir 是個秘密值,除了真正的通信雙方,第三方是不知道的。(如果第三方發起中間人攻擊,知曉密鑰交換值,則無法通過后面的數字簽名,故中間人攻擊不予考慮)。

下面是計算 SKEYSEED 的 python 腳本(g^ir 來自 strongSwan 的屏幕調試輸出)

# SKEYSEED = prf(Ni | Nr, g_ir)
import binascii
from Crypto.Hash import HMAC, SHA
Ni = binascii.a2b_hex('305243E21BB674F3EBDA3A370C7688C446F5C189391F55F7C26A91F82D03F2689114AE822042ECD8E6CEDCB6128F09FB')
Nr = binascii.a2b_hex('D7E075DB89009F3788B30D2FE6DC77F7CB527D8E2CE091B496A0253767C75188')
# Ni、Nr 的長度不一樣
g_ir = binascii.a2b_hex([Diffie-Hellman 密鑰交換結果]) # []用實際內容代替
key = Ni + Nr
data = g_ir
SKEYSEED = binascii.a2b_hex(HMAC.new(key, data, SHA).hexdigest())
print HMAC.new(key, data, SHA).hexdigest()

密鑰種子 SKEYSEED 得到后,利用偽隨機數生成函數,就可以產生實際參與加解密運算的密鑰。這和 TLS 協議中的思路是一致的。但是,單個偽隨機數生成函數,輸出長度有限,而實際要用到的密鑰,數量比較多,長度會不夠用。所以經常利用計數循環遞增的概念,做到:用一個偽隨機數生成函數,源源不斷地產生需要的密鑰。這是怎么得到的?只要看下 RFC 中的密鑰生成公式,就會明白

   {SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr }
                   = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr )

   prf+ (K,S) = T1 | T2 | T3 | T4 | ...

   where:
   T1 = prf (K, S | 0x01)
   T2 = prf (K, T1 | S | 0x02)
   T3 = prf (K, T2 | S | 0x03)
   T4 = prf (K, T3 | S | 0x04)
   ...
其中
SK_d: 生成 IPSec 密鑰材料的密鑰種子
SK_ai:后續 IKEv2 報文的認證密鑰 -- 用於協議發起者
SK_ar:后續 IKEv2 報文的認證密鑰 -- 用於協議響應者
SK_ei:后續 IKEv2 報文的加密密鑰 -- 用於協議發起者
SK_er:后續 IKEv2 報文的加密密鑰 -- 用於協議響應者
SK_pi:生成身份認證(AUTH)載荷時用到 -- 用於協議發起者
SK_pr:生成身份認證(AUTH)載荷時用到 -- 用於協議響應者

按上述公式,函數 prf+() 生成一連串的密鑰流,然后 SK_d、SK_ai、SK_ar、SK_ei、SK_er、SK_pi、SK_pr 依次從密鑰流中取各自所需長度的密鑰。

下面是生成這些密鑰的 python 腳本

SPIi = binascii.a2b_hex('75dd3961203ca3b1')
SPIr = binascii.a2b_hex('3cc3328025824d2d')
K = SKEYSEED
S = Ni + Nr + SPIi + SPIr
T = ''
TotalKey = ''
for i in range(1, 10): # 10 次循環足夠生成所需密鑰
  count_byte = binascii.a2b_hex('%02d' % i) # 0x01 0x02 0x03 ...
  data = T + S + count_byte
  T = HMAC.new(K, data, SHA).hexdigest()
  T = binascii.a2b_hex(T)
  TotalKey += T

SK_d  = TotalKey[0:20]
SK_ai = TotalKey[20:20+20]
SK_ar = TotalKey[40:40+20]
SK_ei = TotalKey[60:60+24]
SK_er = TotalKey[84:84+24]
SK_pi = TotalKey[108:108+20]
SK_pr = TotalKey[128:128+20]

print 'SK_d  = ' + binascii.hexlify(SK_d)
print 'SK_ai = ' + binascii.hexlify(SK_ai)
print 'SK_ar = ' + binascii.hexlify(SK_ar)
print 'SK_ei = ' + binascii.hexlify(SK_ei)
print 'SK_er = ' + binascii.hexlify(SK_er)
print 'SK_pi = ' + binascii.hexlify(SK_pi)
print 'SK_pr = ' + binascii.hexlify(SK_pr)

到現在為止,各種算法和密鑰都已算出。注意,這是在第一階段 IKE_SA_INIT 交換后就發生的事。然后進入第二階段(IKE_AUTH 交換)。自然,第二階段的報文就可以應用上面協商出的各種密碼學要素進行保護。具體是如何保護的?先看交互的第一個 IKE_AUTH 報文

IKE_AUTH 報文中除去報文頭,其余所有的載荷都已經受到保護,並且專門用一種 Encrypted and Authenticated 類型的載荷來表示。該載荷的內部格式參見下圖

果然,除了 SK_d(后面將討論),前面討論的所有密鑰(SK_ai/SK_ar、SK_ei/SK_er、SK_pi/SK_pr)都在這里派上用場。黃色背景中的內容表示加密之前的明文。當然僅僅加密還是不夠的,還要對報文進行完整性保護(目前更流行的是加密認證一體化處理,比如用在塊加密中的 AEAD)。密文后跟稱為 Integrity Checksum Data(ICD) 的字段,起到完整性保護作用。需要注意,ICD 是用前面協商的 HMAC_SHA1_96 算法計算得到,並且其保護內容是除去最后的 ICD 字段的整個 IKEv2 報文。現在按此邏輯解開加密報文的面紗,相應 python 腳本如下(以第一個 IKE_AUTH 報文為例)

from Crypto.Cipher import DES, DES3
import binascii
IV = binascii.a2b_hex([Initialization Vector]) # []用實際內容代替
cipher = DES3.new(SK_ei, DES.MODE_CBC, IV)
ciphertext = binascii.a2b_hex([Encrypted Data]) # []用實際內容代替
plaintext = cipher.decrypt(ciphertext

 對比下面 strongSwan 的輸出,完全一致

13[ENC] data after decryption with padding => 1872 bytes @ 0xaf0036a0
13[ENC]    0: 25 00 00 47 09 00 00 00 30 3D 31 0B 30 09 06 03  %..G....0=1.0...
13[ENC]   16: 55 04 06 13 02 43 4E 31 0B 30 09 06 03 55 04 08  U....CN1.0...U..
13[ENC]   32: 0C 02 48 5A 31 0C 30 0A 06 03 55 04 0A 0C 03 56  ..HZ1.0...U....V
13[ENC]   48: 50 4E 31 13 30 11 06 03 55 04 03 0C 0A 56 50 4E  PN1.0...U....VPN
13[ENC]   64: 20 43 6C 69 65 6E 74 26 00 02 77 04 30 82 02 6E   Client&..w.0..n

得到明文后,就可以直接計算 Integrity Checksum Data,腳本如下

# 驗證 IKE_AUTH 的 Integrity Checksum Data
# 計算范圍:從 IKE_AUTH 報文頭一直延續到報文末尾(除去 Integrity Checksum Data 字段)
from Crypto.Hash import HMAC, SHA
import binascii
data = binascii.a2b_hex([IKE_AUTH 報文除去 ICD 字段]) # []用實際內容代替
IntegrityChecksumData = HMAC.new(SK_ai, data, SHA).hexdigest()
print IntegrityChecksumData

得到 IKE_AUTH 明文后,我們來看下它到底是什么內容?這就可以求助強大的 Wireshark,它自帶解密 IKE 載荷的功能,前提是你要告訴它密鑰。下圖是開啟解密功能的界面(Wireshark Version 1.8.5)

解密后的報文如下

故實際報文的結構為:HDR, SK{IDi, CERT, CERTREQ, AUTH, N, CP, SAi2, TSi, TSr},我們來看這些載荷代表什么?

IDi -- 表示發起者的身份信息,展開 Wireshark 協議樹,其內容是發起者證書中的主題字段(Subject: C=CN, ST=HZ, O=VPN, CN=VPN Client),並采用 DER_ASN1_DN 類型編碼

CERT -- 不言而喻,承載內容是發起者的證書。

CERTREQ -- 發起者要求響應者提供證書(用於驗證對方的數字簽名),前面已經討論過細節。

AUTH -- 認證載荷,這是重點

回憶一下,目前只對 IKE_AUTH 報文進行了加密和完整性保護,這做到了 CIA 中的 C(Confidentiality) 和 I(Integrity)。還有一個我個人認為更重要的 A(Authentication,即身份認證。另一種解釋是 Availability,這里采用前者解釋)沒有體現。因為身份認證,或者說敵我識別,應該是在不安全環境中要考慮的首要問題。作為經典的安全協議,IKEv2 自然也考慮了這點,它在 AUTH 載荷中包含了身份認證的信息,只有 AUTH 載荷通過了驗證,才能認為是真正的對方在和自己通信。那么 AUTH 載荷究竟包含什么內容?容易猜到,應該是數字簽名。簽名密鑰是證書對應的私鑰,剩下簽名算法和簽名內容需要確定。對於 RSA 密鑰,簽名算法采用 RFC 3447 中的 RSASSA-PKCS1-v1_5 標准。而簽名內容,則比較復雜,仍以 Initiator 發送的 IKE_AUTH 報文為例,其計算過程如下

RealMessage1 = 發起者的 IKE_SA_INIT 消息 # 完整的 IKE_SA_INIT 報文,從 IKE 頭到報文末尾
InitIDPayload = IDType | RESERVED | IdentificationDataOfInitiator # 見上圖,就是發起者 Identification 載荷的實際內容部分
MACedIDForI = prf(SK_pi, InitIDPayload) # 使用 prf 生成發起者身份信息的 HMAC 值
InitiatorSignedOctets = RealMessage1 | NonceRData | MACedIDForI # 簽名內容

由上可見,簽名內容包括:發起者的 IKE_SA_INIT 報文、響應者的 Nonce 和發起者身份信息的校驗值。最后一項還涉及從雙方 Diffie–Hellman 密鑰交換結果 g_ir 衍生出來的 SK_pi 密鑰。所有這些消息組合起來,如果其簽名正確,足以讓對方相信發起者的身份。下面是最終的計算腳本。

from Crypto.Hash import HMAC, SHA
from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA
# InitIDPayload = IDType + RESERVED + IdentificationDataOfInitiator
InitIDPayload = binascii.a2b_hex([IDi 載荷的實際內容]) # []用實際內容代替
MACedIDForI = binascii.a2b_hex(HMAC.new(SK_pi, InitIDPayload, SHA).hexdigest())
RealMessage1 = binascii.a2b_hex([發起者的 IKE_SA_INIT 報文]) # []用實際內容代替
InitiatorSignedOctets = RealMessage1 + Nr + MACedIDForI
key = RSA.importKey(open('clientkey.pem').read(), '123456')
hash = SHA.new(InitiatorSignedOctets)
signer = PKCS1_v1_5.new(key)
AuthenticationPayloadOfInitiator = signer.sign(hash) # PKCS1_v1_5 簽名
print binascii.hexlify(AuthenticationPayloadOfInitiator

與認證載荷相比,剩下的幾個載荷就簡單多了。

N -- 通知載荷:MOBIKE_SUPPORTED

CP -- 配置載荷:Windows 7 向對端請求分配 IP/DNS 等地址

SAi2 -- 這是第二階段交互(IKE_AUTH)中的 Security Association,用 SAi2 表示。此時,雙方還沒有商定如何保護后續的業務流量,所以它們在各自的 IKE_AUTH 交換報文中發送 SAi2 和 SAr2,再次進行協商。這次協商的結果稱為 IPSec SA。可以想像,IPSec SA 的內容,也是相關密碼的要素,包括算法、密鑰等關鍵信息。在抓包文件中,SAi2/SAr2 的主要內容如下

                加密算法       完整性算法
SAi1/Proposal-1 ENCR_AES_CBC  AUTH_HMAC_SHA1_96
SAi1/Proposal-2 ENCR_3DES     AUTH_HMAC_SHA1_96
SAr2/Proposal-1 ENCR_3DES     AUTH_HMAC_SHA1_96

最終,雙方同意使用 3DES 和 HMAC_SHA1_96 作為加密和完整性算法。由於雙方身份已經得到確認,所以后續報文的保護,只需要這兩者就可以了。如果你比較細心,會問密鑰從哪里來?沒有密鑰,IPSec SA 是不完整的。答案是密鑰來自 SK_d,后面將討論。

TSi 和 TSr -- 流選擇載荷,與本篇主旨無關,不展開。

基於同樣思路,可以分析響應者發出的 IKE_AUTH 報文。在此不再贅述。

最后歸納 IKEv2 的協議過程:通信雙方在第一階段進行 IKE_SA_INIT 交換,協商的結果為 IKE SA。第二階段 IKE_AUTH 交換受 IKE SA 中定義的密碼學要素保護,雙方同時完成身份認證,並協商出 IPSec SA。IPSec SA 保護后續的通信流量。

至此,IKEv2 協議分析完畢。


參考

RFC 2104 -- HMAC: Keyed-Hashing for Message Authentication
RFC 3280 -- Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
RFC 3447 -- Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1
RFC 5996 -- Internet Key Exchange Protocol Version 2 (IKEv2)
RFC 4718 -- IKEv2 Clarifications and Implementation Guidelines


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM