DTLS-SRTP 加密,验证

首先
在这里插入图片描述
在DTLS中可以得到SRTP的算法套件是SRTP_AES128_CM_HMAC_SHA1_80

master key获取

首先通过openssl库的(master key和master salt得到方式看本文末尾)

 SSL_export_keying_material(ssl, material, SRTP_MASTER_LENGTH*2, master_key_label, 19, NULL, 0, 0);

函数得到 local master key ,local master salt ,remote master key, remote master salt

 		master_keys_t local_master_keys;
        master_keys_t remote_master_keys;
        // 设置远程主密钥和本地主密钥和MKI
        RAND_bytes(remote_master_keys.mki, sizeof(remote_master_keys.mki));
        memcpy(remote_master_keys.key, material, SRTP_MASTER_KEY_LENGTH);
        RAND_bytes(local_master_keys.mki, sizeof(local_master_keys.mki));
        memcpy(local_master_keys.key, material + SRTP_MASTER_KEY_LENGTH, SRTP_MASTER_KEY_LENGTH);


        // 设置远程主盐值和本地主盐值
        RAND_bytes(local_master_keys.mki, sizeof(local_master_keys.mki));
        memcpy(remote_master_keys.salt, material + SRTP_MASTER_KEY_LENGTH*2, SRTP_MASTER_SALT_LENGTH);
        memcpy(local_master_keys.salt, material + SRTP_MASTER_KEY_LENGTH*2 + SRTP_MASTER_SALT_LENGTH, SRTP_MASTER_SALT_LENGTH);


        // 设置远端buf_key和本端buf_key
        memcpy(local_master_keys.buf_key, local_master_keys.key, SRTP_MASTER_KEY_LENGTH);
        memcpy(local_master_keys.buf_key + SRTP_MASTER_KEY_LENGTH, local_master_keys.salt, SRTP_MASTER_SALT_LENGTH);
        memcpy(remote_master_keys.buf_key, remote_master_keys.key, SRTP_MASTER_KEY_LENGTH);
        memcpy(remote_master_keys.buf_key + SRTP_MASTER_KEY_LENGTH, remote_master_keys.salt, SRTP_MASTER_SALT_LENGTH);

秘钥衍生

在这里插入图片描述
因为remote master和local master不一致,因此在解密和加密需要的session套件是不一样的。

 *  Let r = index DIV key_derivation_rate (with DIV as defined above).
 //roc(服务端自己维持,注意加密和解密可能维持的不是同一个)32位,sequence number(从报文中获取)16位
 //roc左移16位加上sequence number就是index
 //kdr是速率,当前不知计算方式,但是保持0没得问题。
 //roc在sequence number = 2的16次方 -1时,要进位时+1,roc满的时候也是从零开始
 //r = index / kdr;
 *  Let key_id = <label> || r.
 //key_id中后48位也就是6字节就是r。
 //前面lable
 /*
  The session keys and salt SHALL now be derived using:
   - k_e (SRTP encryption): <label> = 0x00, n = n_e.
   - k_a (SRTP message authentication): <label> = 0x01, n = n_a.
   - k_s (SRTP salting key): <label> = 0x02, n = n_s.
 */
 *  Let x = key_id XOR master_salt, where key_id and master_salt are
      aligned so that their least significant bits agree (right-
      alignment).
 //key_id与master salt右边补齐,异或操作,看实例
 
master key:  E1F97A0D3E018BE0D64FA32C06DE4139
master salt: 0EC675AD498AFEEBB6960B3AABE6

session encr_key:
 index DIV kdr:                 000000000000
      label:                       00
      master salt:   0EC675AD498AFEEBB6960B3AABE6
      -----------------------------------------------
      xor:           0EC675AD498AFEEBB6960B3AABE6     (x, PRF input)

      x*2^16:        0EC675AD498AFEEBB6960B3AABE60000 (AES-CM input)

      cipher key:    C61E7A93744F39EE10734AFE3FF7A087 (AES-CM output)

session salt_key:
index DIV kdr:                 000000000000
      label:                       02
      master salt:   0EC675AD498AFEEBB6960B3AABE6

      ----------------------------------------------
      xor:           0EC675AD498AFEE9B6960B3AABE6     (x, PRF input)

      x*2^16:        0EC675AD498AFEE9B6960B3AABE60000 (AES-CM input)

                     30CBBC08863D8C85D49DB34A9AE17AC6 (AES-CM ouptut)

      cipher salt:   30CBBC08863D8C85D49DB34A9AE1

session auth_key:
 index DIV kdr:                   000000000000
      label:                         01
      master salt:     0EC675AD498AFEEBB6960B3AABE6
      -----------------------------------------------
      xor:             0EC675AD498AFEEAB6960B3AABE6     (x, PRF input)

      x*2^16:          0EC675AD498AFEEAB6960B3AABE60000 (AES-CM input)

session encr_key,session auth_key,session salt_key是如何从x的派生的?

PRF_n(k_master, x).函数在文档中,但是实际上怎么派生?
其实直通AES-CM算法就可以派生,在文档中其实也给了答案,只是花了大部分时间实践才得到答案
在这里插入图片描述

 AES_KEY aes_key;
if (AES_set_encrypt_key(master_keys->key, 128, &aes_key))
{
    return;
}
AES_encrypt(key_x, session_keys->key, &aes_key);
PRINT_HEX("session key: ",session_keys->key,(int)sizeof(session_keys->key));

if (AES_set_encrypt_key(master_keys->key, 128, &aes_key))
{
    return;
}
AES_encrypt(salt_x, session_keys->salt, &aes_key);
PRINT_HEX("salt key: ",session_keys->salt,(int)sizeof(session_keys->salt));


if (AES_set_encrypt_key(master_keys->key, 128, &aes_key))
{
    return;
}
unsigned char output[94];
unsigned char auth_tmp[20];
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output,auth_tmp,16);
auth_x[15] += 1;
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output+16,auth_tmp,16);
auth_x[15] += 1;
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output+32,auth_tmp,16);
auth_x[15] += 1;
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output+48,auth_tmp,16);
auth_x[15] += 1;
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output+64,auth_tmp,16);
auth_x[15] += 1;
AES_encrypt(auth_x, auth_tmp, &aes_key);
memcpy(output+80,auth_tmp,14);


PRINT_HEX("auth output key: ",output,94);

memcpy(session_keys->auth_key,output,(int)sizeof(session_keys->auth_key));
PRINT_HEX("auth key: ",session_keys->auth_key,(int)sizeof(session_keys->auth_key));
//可能会有疑问,在auth派生为什么不和前面两个一致,因为AES-CM派生的长度是16字节。而auth需要20字节。经过实践该方法获得结果与文档一致
/修改函数参数128也不能。

解密操作

认证生成

原话:
Throughout this section, M will denote data to be integrity
protected. In the case of SRTP, M SHALL consist of the Authenticated
Portion of the packet (as specified in Figure 1) concatenated with
the ROC, M = Authenticated Portion || ROC; in the case of SRTCP, M
SHALL consist of the Authenticated Portion (as specified in Figure 2)
only.
The pre-defined authentication transform for SRTP is HMAC-SHA1 [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to the session authentication key and M as specified above, i.e., HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag left-most bits.

实现:

//注意验证部分不包含,MKI和最后10字节tag
//需将验证内容与ROC进行拼接形成M
memcpy(output_buffer, input, input_len);

uint32_t roc_network_byte_order = htonl(roc);
memcpy(output_buffer+input_len, &roc_network_byte_order, sizeof(roc_network_byte_order));

//生成认证tag
const EVP_MD *digest = EVP_sha1();


unsigned char full_output[EVP_MAX_MD_SIZE];
unsigned int full_output_len;
//KEY 是session auth_key,data是M


HMAC(digest, key, key_len, data, data_len, full_output, &full_output_len);
memcpy(output, full_output, output_len);
//在libsrtp 中分两次传入M,效果一样
HMAC_CTX_init(&c);
HMAC_Init(&c,key,key_len,evp_md);
HMAC_Update(&c,d,n);//该函数执行两次,第一次传入验证数据,第二次传入ROC
HMAC_Final(&c,md,md_len);
HMAC_CTX_cleanup(&c);

//与tag进行对比

加密

在这里插入图片描述
在这里插入图片描述

//分块
(payloadlength % 16 != 0) ? (n_block = (payloadlength / 16 + 1) ) : (n_block = (payloadlength / 16) );
//生成秘钥流  packet_index当前只传sequence number
void generate_srtp_keystream_segment(
const uint8_t *k_e, const uint32_t ssrc, const uint16_t packet_index,
	const uint8_t *k_s, uint8_t *keystream_output, size_t num_blocks)
{   
	AES_KEY aes_key;
        	if (AES_set_encrypt_key(k_e, 128, &aes_key))
        	{
        		// 错误处理:设置加密密钥失败
        		return;
        	}
	    uint8_t iv[16] = {0};
            // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
            //                         13 14 15 16
	    // 计算 IV = (k_s * 2^16) XOR (SSRC * 2^64) XOR (i * 2^16)  i指的是ROC || sequence number
	    memcpy(iv, k_s, 14); // 假设 k_s 为长度为 14 字节的字节数组
	    *((uint32_t *)(iv + 4)) ^= ssrc; 
	   
            *((uint16_t *)(iv + 12)) ^= packet_index;
        	for (size_t block_index = 0; block_index < num_blocks; ++block_index)
        	{
        		// 对当前块进行 IV + block_index mod 2^128 操作        		
        		uint16_t counter;
                          //该操作就是把IV后两个字节+1,注意IV长度申请是16字节
        		if(block_index) counter = htons(ntohs(*((uint16_t *)(iv + 14))) + 1);
        		memcpy(iv + 14, &counter, sizeof(counter));
        
        		// 计算密钥流片段的当前块:E(k, IV),并输出到原始位置,注意只是计算秘钥流
        		AES_encrypt(iv, keystream_output + (block_index * 16), &aes_key);
        	}        
}

//与payload进行XOR操作
output[i] = data[i] ^ keystream[i];

//得到加密数据

解密

与加密数据一样,只是对加密数据进行XOR会得到原始数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值