zoukankan      html  css  js  c++  java
  • 安全协议系列(五)---- 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

  • 相关阅读:
    leetcode211
    leetcode209
    leetcode201
    leetcode1396
    leetcode1395
    leetcode1394
    leetcode1386
    leetcode1387
    leetcode1382
    leetcode1376
  • 原文地址:https://www.cnblogs.com/efzju/p/5041797.html
Copyright © 2011-2022 走看看