通过前面几篇内容,我们大概了解了zrtp的调用过程,但是每一个抓包中相关数据分别是什么含义呢?又是如何产生,如何加密和解密的呢?带着这些疑问,我们开始本次探险之旅,彻底搞懂zrtp协议内容。
通用字段
首先我们看一下zrtp的通用字段含义:
这些字段是构建是通过zrtpMessageSetHeader等API进行构建,没啥难度自行查阅即可:
Hash Image
为了在收到错误数据包后立即廉价且快速地检测和拒绝错误数据包,ZRTP 使用单向哈希链,它是一系列连续的哈希图像。在每次会话之前,计算以下值:
每一方生成的初始随机 H0 随机数必须对攻击者来说是不可预测的,并且在 ZRTP 会话中是唯一的,从而迫使派生的哈希图像 H1-H3 也是唯一且不可预测的。接收者通过对数据包进行散列并将结果与前一个数据包的散列值进行比较来检查数据包是否具有正确的散列值。 对于包含不正确的哈希值的数据包不得被接收者使用,但它们可以作为安全异常进行处理,可能通过记录或警告用户来处理。只要不使用这些伪造的数据包,并且仍然收到正确的数据包,就应该允许协议运行完成,从而使这种拒绝服务攻击无效。
这部分内容在RFC 6189的第九章节有详细介绍,有兴趣的可以参考 一下:https://datatracker.ietf.org/doc/html/rfc6189#section-9
各端Hash Image是在初始化阶段完成构建的:
Hello消息
构建Hello消息
其他消息也基本类似,不再累述,有兴趣的可以查看RFC6189 第五章节即可。我们更加重点关注几个关键字段如何产生,以及如何进行计算和协商的。
Hello消息创建时针对H3散列值拷贝:
Hello消息的HMAC的值是ZRTP 通道上下文的 selfH[2] 字段,即包含 Hello 数据包的 HMAC,用于为 ZRTP 消息提供 MAC 密钥。
最后查看实例如下图所示:
解析Hello消息
收到Hello时进行hello hash校验:
当发送方第一次收到从对端发送的Hello 消息时,将解析Hello消息以检查它是否与当前配置匹配并根据上下文执行操作
* -当前发送端可能处于的状态为 state_discovery_init 或 state_discovery_waitingForHello 。
* – 就使用的算法达成一致
* – 检查我们是否在缓存中保留了与对等 ZID 匹配的机密
* – 如果为 DHM 模式:计算公共值并准备 DHPart2 数据包(假设我们是发起者,如果需要稍后更改)
* – 如果为非 DHM 模式:不支持 PreShared,此时 Multistream 无需执行任何操作
此时会构建rs1ID等相关值:
相关代码如下:
Commit消息
构建Commit消息
commit消息创建时,拷贝H2散列值
hvi = hash(initiator’s DHPart2 message || responder’s Hello message)
HVI在ZRTP提交消息中用于验证ZRTP协商过程的完整性。发起者和响应者都会生成 HVI 并进行比较。如果两个 HVI 匹配,则 ZRTP 协商过程已成功,并且两个对等方可以开始使用 SRTP 进行安全通信。
hvi的计算是在commit构建zrtp报文时完成的, HVI是ZRTP会话的唯一标识符,用于验证ZRTP协商过程的完整性,如下代码所示:
(1)Step1 创建一个包含连接的 DHPart2 和 Hello 消息的字符串。字符串的长度是通过将两个消息的长度相加来计算的。
(2)Step2 为字符串分配内存并将两条消息复制到其中。
(3)Step3 使用商定的哈希函数对字符串进行哈希处理,并将哈希值存储在提交消息的 HVI 字段中。
commit消息实例
解析Commit消息
commit解析流程如下图:
收到commit消息时,进行H3校验:
DHPart1或DHPart2消息
DHpart1或DHpart2创建时,完成H1的拷贝:
构建DHPart1消息实例
构建DHPart2消息实例
DHpart1或DHpart2解析过程
DHpart1或DHpart2,H2校验过程:
检查 ZRTP 提交消息中收到的 HVI(哈希值标识符):
(1)计算预期 HVI:通过使用商定的哈希函数对发起方的 DHPart2 消息和响应方的 Hello 消息进行哈希计算来计算的。然后哈希值被截断为 256 位。
(2)使用协商的哈希函数对字符串进行哈希处理,并将哈希值存储在computeHvi 缓冲区中。
(3)计算出的 HVI 与提交消息中收到的 HVI 进行比较。如果两个 HVI 匹配,则 HVI 检查成功。否则,HVI 检查失败并且代码返回错误。
请注意,对于多流和预共享模式,不会发送 DHPart1 或 DHPart2 消息:
收到DHpart1或DHPart2消息之后,还计算S0、KDF 上下文和 ZRTPSess的值,用于SRTP加密的密钥:
Confirm1或Confirm2消息
创建confirm1和confirm2消息时,拷贝H0散列值:
构建Confirm1消息实例
构建Confirm2消息实例
解析Confirm1和Confirm2消息
整体的交互过程如下:
至此每个阶段产生的重要Key值,以及相关之间的创建和解析过程就说明完成了。而真正涉及到加密的算法,linphone调用的是另外的库文件,下一篇我们再作解释,欢迎期待。
我是一枚爱跑步的程序猿,维护公众号和知乎专栏《MediaStack》,有兴趣可以关注,一起学习音视频知识,时不时分享实战经验。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。