IKEv2协议协商流程: (IKE-SA-INIT 交换)第二包

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天

1. IKEv2 协商总体框架

IKEv1协议建立一对IPSec SA,使用主动模式需要9个报文,使用野蛮模式需要使用6个报文方能协商成功。IKEv2对IKEv1协议进行了优化,IKEv2只需要进行两次交互,使用 4 条消息就可以完成一个 IKEv2 SA 和一对 IPsec SA 的协商建立。IKEv2 定义了三种交互:

  • 初始交换

  • 创建子 SA 交换

  • 通知交换

    下图简单介绍一下 IKEv2 协商过程中的初始交换过程,之后我们将对ikev2_parent_inI1outR1()接口相关的处理流程做一个简单说明。

在这里插入图片描述

初始化交换通过两次交换共4个报文便可以完成一对IKE SA和IPSec SA的协商。上图主要用来描述协商报文的内容和对应的处理函数入口。下图则是用来说明各接口对应的协商状态。协商过程中是根据该状态来确定当前的协商阶段。

在这里插入图片描述

RFC文档中的报文格式:

Initiator                         Responder
-------------------------------------------------------------------
HDR, SAi1, KEi, Ni  -->
                                  <--  HDR, SAr1, KEr, Nr, [CERTREQ]

HDR, SK {IDi, [CERT,] [CERTREQ,]
     [IDr,] AUTH, SAi2,
     TSi, TSr}  -->
                                  <--  HDR, SK {IDr, [CERT,] AUTH, 
                                  			SAr2, TSi, TSr}
复制代码

其中:

报文字段 说明
HDR 报文头部
SAi1、SAr1 IKE SA建议
SAi2、SAr2 IPSEC SA建议载荷
KEi、KEr DH算法公共值
Ni、Nr Nonce随机数
CERT、CERTREQ 证书载荷、证书请求载荷
IDi、IDr ID载荷
TSi、TSr 流量选择器,使用此载荷完成保护子网的协商
AUTH 认证数据载荷

这里面需要说明的是:报文中的SK并不是一个载荷。而是:==SK {...}表示里面的内容被加密和认证保护==。

下面对响应端对发起端第一包的处理流程做一个详细说明。入口函数为:ikev2_parent_inI1outR1()

2. 第二包流程图

在这里插入图片描述

3. openswan源码学习

3.1 ikev2parent_inI1outR1()

==此函数是IKEv2协议 响应端开始协商的入口函数==。主要功能包括:

  • 根据收到报文四元组(IP和端口)查找连接:c
  • 新建一个协商状态结构:state
  • 根据发起端的地址等信息生成Cookie值
  • 将连接上的参数信息、配置信息初始化state结构上参数
    • 隧道本端IP和端口
    • 隧道对端IP和端口
    • 隧道的出接口
    • 配置策略
    • ... ...
  • 抗重放检测(Cookie challenge)
    • 根据发起端IP、SPI、Nonce计算用于抗重放的cookie
    • 如果报文中包含KE和Cookie类型的通知载荷
      • 比较两端的cookie是否一致
    • 如果报文中不包含Cookie类型的通知载荷
      • 发送携带cookie的通知报文,使发起端重新开始协商
  • 解析报文中的KE载荷,获取到DH组信息
    • DH组猜测成功,则计算DH算法的公共值,用于构建响应报文的KE载荷
static stf_status
ikev2_parent_inI1outR1_tail(struct pluto_crypto_req_cont *pcrc
                            , struct pluto_crypto_req *r);

stf_status ikev2parent_inI1outR1(struct msg_digest *md)
{
    struct state *st = md->st;
    lset_t policy = POLICY_IKEV2_ALLOW;
    lset_t policy_hint = LEMPTY;
	/*
	*	根据隧道两端协商地址和端口来查找连接
	*/
    struct connection *c = find_host_connection(ANY_MATCH, &md->iface->ip_addr
                 , md->iface->port
                 , KH_IPADDR
                 , &md->sender
                 , md->sender_port
                 , POLICY_IKEV2_ALLOW, LEMPTY, &policy_hint);

    if(c==NULL) {/*连接查找失败则提示相应的错误信息*/
        if(policy_hint & POLICY_IKEV2_ALLOW) {
            /* connection not found, because IKEv2 was not allowed */
            /* send back AUTHENTICATION_FAILED per WG mailing list discussion */
            openswan_log("connection refused, IKEv2 not authorized");
            return STF_FAIL + v2N_AUTHENTICATION_FAILED;
        }

        /*
     * be careful about responding, or logging, since it may be that we
     * are under DOS
     */
        DBG_log("no connection with matching policy found\n");
        return STF_FAIL + v2N_AUTHENTICATION_FAILED;
    }


    loglog(RC_COMMENT, "tentatively considering connection: %s\n", c ? c->name : "<none>");

    if(!st) {/*如果没有对应的状态,则新建一个state*/
	st = new_state();
	/* set up new state */
	memcpy(st->st_icookie, md->hdr.isa_icookie, COOKIE_SIZE);
	
	/* initialize_new_state expects valid icookie/rcookie values, so create it now */
	
	/*相应方根据发起方的地址信息、当前时间生成一个cookie*/
	get_cookie(FALSE, st->st_rcookie, COOKIE_SIZE, &md->sender);
	initialize_new_state(st, c, policy, 0, NULL_FD, pcim_stranger_crypto);
	 st->st_ikev2      = TRUE;
        st->st_localaddr  = md->iface->ip_addr;
        st->st_localport  = md->iface->port;
        st->st_remoteaddr = md->sender;
        st->st_remoteport = md->sender_port;
        st->st_ike_maj    = md->maj;
        st->st_ike_min    = md->min;
	change_state(st, STATE_PARENT_R1);

        md->st = st;
        md->from_state = STATE_IKEv2_BASE;
        md->transition_state = st;
    }

    /* check,as a responder, are we under dos attack or not
     * if yes go to 6 message exchange mode. it is a config option for now.
     * TBD set force_busy dynamically
     * Paul: Can we check for STF_TOOMUCHCRYPTO ?
     */
    if(force_busy == TRUE)/*开启抗重放!!!!!!!!*/
        {
            u_char dcookie[SHA1_DIGEST_SIZE];
            chunk_t dc;
			/*计算用于cookie challenge的cookie值*/
            ikev2_get_dcookie( dcookie, st->st_ni, &md->sender, st->st_icookie);
            dc.ptr = dcookie;
            dc.len = SHA1_DIGEST_SIZE;/*20bytes*/

            /* check if I1 packet contian KE and a v2N payload with type COOKIE */
			/*报文格式??? : HDR + NOTIF + KE*/
            if ( md->chain[ISAKMP_NEXT_v2KE] &&   md->chain[ISAKMP_NEXT_v2N] &&
                 (md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_type == v2N_COOKIE))
                {/*对方的协商报文中包含了cookie*/
                /*
         *	ISAKMP_NEXT_v2N  : IKEv2 通知载荷
         * ISAKMP_NEXT_v2Ni : 发起端Nonce载荷
         * ISAKMP_NEXT_v2Nr : 接收端Nonce载荷
         */
                    u_int8_t spisize;
                    const pb_stream *dc_pbs;
                    chunk_t blob;
                    DBG(DBG_CONTROLMORE
                        , DBG_log("received a DOS cookie in I1 verify it"));
					
                    /* we received dcookie we send earlier verify it */
                    spisize = md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_spisize;
                    dc_pbs = &md->chain[ISAKMP_NEXT_v2N]->pbs;
                    blob.ptr = dc_pbs->cur + spisize;
                    blob.len = pbs_left(dc_pbs) - spisize;
                    DBG(DBG_CONTROLMORE
                        ,DBG_dump_chunk("dcookie received in I1 Packet", blob);
                        DBG_dump("dcookie computed", dcookie, SHA1_DIGEST_SIZE));

			/*检查收到的cookie和本端发送的cookie是否一致!!!*/
                    if(memcmp(blob.ptr, dcookie, SHA1_DIGEST_SIZE)!=0) {
                        openswan_log("mismatch in DOS v2N_COOKIE,send a new one");
                        SEND_V2_NOTIFICATION_DATA(md, st, v2N_COOKIE, &dc);
                        return STF_FAIL + v2N_INVALID_IKE_SPI;
                    }
                    DBG(DBG_CONTROLMORE
                        ,DBG_log("dcookie received match with computed one"));
                 }
            else/*收到的报文中并不包含COOKIE载荷,因此需要发起cookie challenge*/
                {/*COOKIE challenge通知载荷发送*/
                    /* we are under DOS attack I1 contains no DOS COOKIE */
                    DBG(DBG_CONTROLMORE
                        ,DBG_log("busy mode on. receieved I1 without a valid dcookie");
                        DBG_log("send a dcookie and forget this state"));
                    SEND_V2_NOTIFICATION_DATA(md, st, v2N_COOKIE, &dc);
                    return STF_FAIL;
                }
        }
    else {
        DBG(DBG_CONTROLMORE ,DBG_log("will not send/process a dcookie"));

    }

    /*
     * If we did not get a KE payload, we cannot continue. There should be
     * a Notify telling us why. We inform the user, but continue to try this
     * connection via regular retransmit intervals.
     */
    if(md->chain[ISAKMP_NEXT_v2N]  && (md->chain[ISAKMP_NEXT_v2KE] == NULL))
    {
         const char *from_state_name = enum_name(&state_names, st->st_state);
         const u_int16_t isan_type = md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_type;
         openswan_log("%s: received %s"
                     , from_state_name
                     , enum_name(&ikev2_notify_names, isan_type));
         return STF_FAIL + isan_type;
    } else if( md->chain[ISAKMP_NEXT_v2N]) {
            /* XXX/SML: KE payload came with a notification-- is there a problem? */
         DBG(DBG_CONTROL,DBG_log("received a notify.."));
    }

    /*
     * We have to agree to the DH group before we actually know who
     * we are talking to.   If we support the group, we use it.
     *
     * It is really too hard here to go through all the possible policies
     * that might permit this group.  If we think we are being DOS'ed
     * then we should demand a cookie.
     */
    {
        struct ikev2_ke *ke;
        if (md->chain[ISAKMP_NEXT_v2KE] == NULL)
                    return STF_FAIL;
		
        ke = &md->chain[ISAKMP_NEXT_v2KE]->payload.v2ke;
	/*KE载荷中包含DH GROUP信息*/
        st->st_oakley.group=lookup_group(ke->isak_group);/*查找DH GROUP,并存储在st->st_oakley上*/
        if(st->st_oakley.group==NULL) {
            char fromname[ADDRTOT_BUF];

            addrtot(&md->sender, 0, fromname, ADDRTOT_BUF);
            openswan_log("rejecting I1 from %s:%u, invalid DH group=%u"
                         ,fromname, md->sender_port, ke->isak_group);
            return v2N_INVALID_KE_PAYLOAD;
        }
    }

    /* now. we need to go calculate the nonce, and the KE */
    {
        struct ke_continuation *ke = alloc_thing(struct ke_continuation
                                                 , "ikev2_inI1outR1 KE");
        stf_status e;

        ke->md = md;
        set_suspended(st, ke->md);

        if (!st->st_sec_in_use) {
            pcrc_init(&ke->ke_pcrc);
            ke->ke_pcrc.pcrc_func = ikev2_parent_inI1outR1_continue;
            e = build_ke(&ke->ke_pcrc, st, st->st_oakley.group, pcim_stranger_crypto);
            if(e != STF_SUSPEND && e != STF_INLINE) {
                loglog(RC_CRYPTOFAILED, "system too busy");
                delete_state(st);
            }
        } else {
            e = ikev2_parent_inI1outR1_tail((struct pluto_crypto_req_cont *)ke
                                            , NULL);
        }

        reset_globals();

        return e;
    }
}

复制代码

3.2 ikev2parent_inI1outR1_tail()

此函数最主要的功能就是构建应答报文。具体的步骤包括如下几个方面:

  • 保存对端发送的协商报文,用于后续认证操作
  • 解析协商报文中的通知载荷
    • NAT-D
    • error information
    • ... ...
  • 构建IKEv2报文头部
  • 构建SA建议载荷
    • 将本地配置策略生成并转换为ikev2 SADB
    • 解析对端的SA载荷中所有的建议载荷
    • 使用排列组合匹配本地的SADB算法信息
  • 解析对端KE载荷
  • 解析对端Nonce载荷。
  • 构建KE载荷
  • 构建Nonce载荷
  • 构建NAT-D载荷
  • 保存当前报文供后续的认证等操作
  • 发送报文
static stf_status
ikev2_parent_inI1outR1_tail(struct pluto_crypto_req_cont *pcrc
                            , struct pluto_crypto_req *r)
{
    struct ke_continuation *ke = (struct ke_continuation *)pcrc;
    struct msg_digest *md = ke->md;
    struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_v2SA];
    struct state *const st = md->st;
    stf_status notok;
    int    numvidtosend=0;
#ifdef PLUTO_SENDS_VENDORID
    numvidtosend++;  /* we send Openswan VID */
#endif

    if (sa_pd == NULL) {
                return STF_FAIL;
    }

    /* note that we don't update the state here yet */

    /* record first packet for later checking of signature */
    clonetochunk(st->st_firstpacket_him, md->message_pbs.start
                 , pbs_offset(&md->message_pbs), "saved first received packet");


    /*
     * verify the NAT DETECTION notify messages before answering.
     * on the responder side, this allows us to detect when *we* are behind
     * at NAPT (probably with a port-forward).
     *
     * If we are, then we set a bit saying so, which later on will make us pick the
     * UDP encapsulation for packets.  It is up to the initiator to switch ports
     * from 500 to 4500.  Could be they have already done so, we do not care here.
     */
    if(md->chain[ISAKMP_NEXT_v2N]) {/*处理通知载荷,其中包括NAT-D探测*/
        ikev2_process_notifies(st, md);
    }


    /* make sure HDR is at start of a clean buffer */
    zero(reply_buffer);
    init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer), "reply packet");

    /* HDR out */
    {
        struct isakmp_hdr r_hdr = md->hdr;

        memcpy(r_hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);

        r_hdr.isa_version = IKEv2_MAJOR_VERSION << ISA_MAJ_SHIFT | IKEv2_MINOR_VERSION;
        r_hdr.isa_np = ISAKMP_NEXT_v2SA;
        r_hdr.isa_flags = ISAKMP_FLAGS_R|IKEv2_ORIG_INITIATOR_FLAG(st);
        r_hdr.isa_msgid = st->st_msgid;
        if (!out_struct(&r_hdr, &isakmp_hdr_desc, &reply_stream, &md->rbody))
            return STF_INTERNAL_ERROR;
    }

    /* start of SA out */
    {
        struct ikev2_sa r_sa = sa_pd->payload.v2sa;/*收到的报文中的SA头部信息*/
        v2_notification_t rn;
        pb_stream r_sa_pbs;

        r_sa.isasa_np = ISAKMP_NEXT_v2KE;  /* XXX */
        if (!out_struct(&r_sa, &ikev2_sa_desc, &md->rbody, &r_sa_pbs))
            return STF_INTERNAL_ERROR;

        /* SA body in and out *//*解析对方的SA建议载荷,并将选择的建议载荷填充到r_sa_pbs中*/
        rn = ikev2_parse_parent_sa_body(&sa_pd->pbs, &sa_pd->payload.v2sa,
                                        &r_sa_pbs, st, FALSE);

        if (rn != v2N_NOTHING_WRONG)
            return STF_FAIL + rn;
    }
/*解析对端的KE载荷,存储在st_gi中*/
    if((notok = accept_v2_KE(md, st, &st->st_gi, "Gi"))!=STF_OK) {
        return notok;
    }

    /* Ni in *//*解析对端的Nonce载荷,存储在st_ni中*/
    RETURN_STF_FAILURE(accept_v2_nonce(md, &st->st_ni, "Ni"));

    /* send KE *//*填充KE*/
    if(!ship_v2KE(st, r, &st->st_gr, &md->rbody, ISAKMP_NEXT_v2Nr))
        return STF_INTERNAL_ERROR;

    /* send NONCE *//*填充Nonce*/
    unpack_nonce(&st->st_nr, r);
    if(!justship_v2Nonce(st, &md->rbody, &st->st_nr, 0)) {
        return STF_INTERNAL_ERROR;
    }

    if(!justship_v2nat(st, &md->rbody)) {/*填充NAT-D*/
        return STF_INTERNAL_ERROR;
    }

    /* Send VendrID if needed VID */
    {
        pbs_set_np(&md->rbody, ISAKMP_NEXT_v2V);
        if (!out_generic_raw(0, &isakmp_vendor_id_desc, &md->rbody
                             , pluto_vendorid, strlen(pluto_vendorid), "Vendor ID"))
            return STF_INTERNAL_ERROR;
    }

    close_message(&md->rbody);
    close_output_pbs(&reply_stream);

    /* let TCL hack it before we mark the length. */
    TCLCALLOUT("v2_avoidEmitting", st, st->st_connection, md);

    /* keep it for a retransmit if necessary */
    freeanychunk(st->st_tpacket);
    clonetochunk(st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream)
                 , "reply packet for ikev2_parent_inI1outR1_tail")

        /* save packet for later signing */
        freeanychunk(st->st_firstpacket_me);
    clonetochunk(st->st_firstpacket_me, reply_stream.start
                 , pbs_offset(&reply_stream), "saved first packet");


    /* while waiting for initiator to continue, arrange to die if nothing happens */
    delete_event(st);
    event_schedule(EVENT_SO_DISCARD, 300, st);

    return STF_OK;

}
复制代码

3.3 ikev2_parse_parent_sa_body()

该函数主要的作用是解析协商报文中的SA建议载荷,并从中选择本地支持的算法进行协商。

主要流程如下

  • 根据配置的SA建议载荷转换为SADB结构
  • 将SADB结构转换为IKEv2的SA结构
  • 解析报文中的SA载荷
  • 将报文SA中的算法进行==排列组合==, 并与本地SADB中的算法进行匹配
    • ==加密算法==
    • ==完整性哈希算法==
    • ==PRF哈希算法==
    • ==DH算法==
  • 根据算法标识获取算法详细参数,并将其存储在state上
  • 如果为响应端
    • 将选中的算法填充到报文SA载荷中
v2_notification_t
ikev2_parse_parent_sa_body(
    pb_stream *sa_pbs,              /* body of input SA Payload */
    const struct ikev2_sa *sa_prop UNUSED, /* header of input SA Payload */
    pb_stream *r_sa_pbs,	    /* if non-NULL, where to emit winning SA */
    struct state *st,  	            /* current state object */
    bool selection UNUSED           /* if this SA is a selection, only one
				     * tranform can appear. */
    )
{
    pb_stream proposal_pbs;
    struct ikev2_prop proposal;
    unsigned int np = ISAKMP_NEXT_P;
    /* we need to parse proposal structures until there are none */
    unsigned int lastpropnum=-1;
    bool conjunction, gotmatch;
    struct ikev2_prop winning_prop;
    struct db_sa *sadb;
    struct trans_attrs ta;
    struct connection *c = st->st_connection;
    
    /*根据配置确定选用的SADB模板*/
    int    policy_index = POLICY_ISAKMP(c->policy
					, c->spd.this.xauth_server
					, c->spd.this.xauth_client);

    struct ikev2_transform_list itl0, *itl;

    memset(&itl0, 0, sizeof(struct ikev2_transform_list));
    itl = &itl0;

    /* find the policy structures */
	/*根据策略配置生成SADB信息*/
    sadb = st->st_sadb;
    if(!sadb) {
		st->st_sadb = &oakley_sadb[policy_index];
		sadb = oakley_alg_makedb(st->st_connection->alg_info_ike
				 , st->st_sadb, 0);
		if(sadb != NULL) {
	    	st->st_sadb = sadb;
		}
		sadb = st->st_sadb;
    }
	/*将SA结构由IKEv1转换为IKEv2*/
    sadb = st->st_sadb = sa_v2_convert(sadb);

    gotmatch = FALSE;
    conjunction = FALSE;
    zero(&ta);

    while(np == ISAKMP_NEXT_P) {/*一般情况下只有一个建议载荷*/
	/*
	 * note: we don't support ESN,
	 * so ignore any proposal that insists on it
	 */

	if(!in_struct(&proposal, &ikev2_prop_desc, sa_pbs, &proposal_pbs))
	    return PAYLOAD_MALFORMED;

	if(proposal.isap_protoid != PROTO_ISAKMP) {
	    loglog(RC_LOG_SERIOUS, "unexpected PARENT_SA, expected child");
	    return PAYLOAD_MALFORMED;
	}
	... ...
	gotmatch = FALSE;

	{/*将报文中的建议载荷转换为struct ikev2_transform_list*/
		stf_status ret = ikev2_process_transforms(&proposal
						    , &proposal_pbs, itl);
	    if(ret != STF_OK) return ret;
	}

	np = proposal.isap_np;/*下一个建议载荷 或者为空*/

	if(ikev2_match_transform_list_parent(sadb
					     , proposal.isap_propnum
					     , itl)) {/*匹配成功的算法组合存储在itl中*/

	    winning_prop = proposal;
	    gotmatch = TRUE;
	    /* gotmatch is true, so will never go inside if*/

	}
    }

    /*
     * we are out of the loop. There are two situations in which we break
     * out: gotmatch == FALSE, means nothing selected.
     */
    if(!gotmatch) {
		return NO_PROPOSAL_CHOSEN;
    }
    /*
     * since we found something that matched, we might need to emit the
     * winning value.
     *
     * 从对方的建议载荷中找到了合适的算法信息;
     */
    /*加密算法*/
    ta.encrypt   = itl->encr_transforms[itl->encr_i];
    ta.enckeylen = itl->encr_keylens[itl->encr_i] > 0 ?
			itl->encr_keylens[itl->encr_i] : 0;
    ta.encrypter = (struct encrypt_desc *)ike_alg_ikev2_find(IKE_ALG_ENCRYPT
							     , ta.encrypt
							     , ta.enckeylen);
    passert(ta.encrypter != NULL);
    if (ta.enckeylen <= 0)
	ta.enckeylen = ta.encrypter->keydeflen;

	/*完整性哈希算法*/
    ta.integ_hash  = itl->integ_transforms[itl->integ_i];
    ta.integ_hasher= (struct hash_desc *)ike_alg_ikev2_find(IKE_ALG_INTEG,ta.integ_hash, 0);
    passert(ta.integ_hasher != NULL);

	/*PRF 哈希算法*/
    ta.prf_hash    = itl->prf_transforms[itl->prf_i];
    ta.prf_hasher  = (struct hash_desc *)ike_alg_ikev2_find(IKE_ALG_HASH, ta.prf_hash, 0);
    passert(ta.prf_hasher != NULL);

	/*DH 算法*/
    ta.groupnum    = itl->dh_transforms[itl->dh_i];
    ta.group       = lookup_group(ta.groupnum);

    st->st_oakley = ta;/*将算法信息存储到state上*/

    if (r_sa_pbs != NULL)/*将选中的算法填充到应答报文的SA载荷上*/
    {
	return ikev2_emit_winning_sa(st, r_sa_pbs
				     , ta
				     , /*parentSA*/TRUE
				     , winning_prop);
    }
    return NOTHING_WRONG;
}
复制代码

4. 小结

4.1 SA载荷中加密套件算法选择

  • IKEv2加密套件的转换详细说明见下表
用途 名称 编号
IKE转换类型Ⅰ(==加密==) ENCR_3DES 3
ENCR_NULL 11
ENCR_AES_CBC 12
ENCR_AES_CTR 13
IKE转换类型Ⅱ(==PRF==) PRF_HMAC_MD5 1
PEF_HMAC_SHA1 2
PRF_AES128_CBC 4
IKE转换类型Ⅲ(==完整性==) AUTH_HMAC_MD5_96 1
AUTH_HMAC_SHA1_96 2
AUTH_AES_XCBC_96 5
IKE转换类型Ⅳ(==DH组==) 1024 MODP (Group 2) 2
2048 MODP (Group 14) 14

安全关联(SA)负载包含了一个SPI数值和一套建议(==通常是一个==)。这些建议是通过一些复杂的建议结构关联起来的。每一个建议结构都会被编号(参见上表)并且包含一个IPsec协议标识符,此标识符用来支持采用的协议,如IKE, ESP, AH。

每一个==建议载荷==包含一个或者多个==变换载荷==用来描述指定协议的算法。==通常情况下,AH协议仅有一个变换载荷(与完整性检验算法有关),ESP协议有两个变换载荷(与完整性检验算法和加密算法相对应),DH算法有四个转换结构(DH组编号、PRF算法、完整性检验算法、加密算法)==。而第二个报文中的SA加密套件中的建议载荷便属于DH转换结构。

4.2 密钥交换KE和随机负载Nonce

IKE_SA_INIT交换报文中处理SA载荷外,还包括KE载荷和Nonce载荷。

  • KE载荷包括DH组编号和密钥交换数据(DH算法公共值)
  • Nonce载荷则是随机生成的随机数,用于生成密钥材料。

DH一旦交换完成,双方便可以计算出自己的SKEYSEED,该值用来生成所有与IKE_SA相关的子密钥,包括:SK_d, SK_ai, SK_ar, SK_ei, SK_er, SK_pi, SK_pr

SKEYSEED的计算方式如下:

S K E Y S E E D = p r f ( N i N r , g i r ) SKEYSEED = prf(Ni | Nr, g^{ir} )
密钥 用途
SK_ai, SK_ar ==用于认证==
SK_ei, SK_er ==用于加密==
SK_d ==用于派生CHILD_SA密钥==
SK_p ==用于在IKE_AUTH交换中生成AUTH载荷==

版本所有,谢绝转载!!!

猜你喜欢

转载自juejin.im/post/7109118741194997797