首先
在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会得到原始数据