ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SRTP开源库实战:从RTP加密原理到libsrtp集成避坑指南

SRTP开源库实战:从RTP加密原理到libsrtp集成避坑指南 简介这是一份基于安全实时传输协议SRTP的开源库资源包主要面向需要在RTP通信中加入加密与完整性保护的C/C开发者也适合学习、研究实时传输安全或在VoIP、WebRTC等项目中快速集成安全功能的工程人员。资源已预先提供VC7编译好的工程文件可直接在Visual Studio环境中打开和编译避免复杂的交叉编译配置同时可在此基础上修改和扩充。压缩包内共168个文件包体约547KB正文以C源文件与头文件为主涵盖AES加密、SRTP核心逻辑、驱动测试程序、认证与密钥管理等模块另附makefile、configure配置脚本、VC工程解决方案sln/project以及readme、license等说明文档目录结构清晰便于按需查阅和二次开发。目前已有196人学习浏览。借助该库可获得完整的SRTP开源实现既能深入理解数据包加解密、完整性校验和密钥生命周期管理也能利用自带驱动示例进行调试与验证是实时通信安全领域便捷的入门资料和工程参考。1. SRTP开源库排查实战不是装完就完先搞懂RTP加密再动手经常有同事拿着 srtp.rar 找到我开口就是“这个 SRTP 开源库是不是装上就能给 RTP 流加密”。我一般先反问他一句你知道 RTP 头里哪个字段会变、哪个不会变吗答不上来装完也跑不通。SRTP 不是给 RTP 包整体包一层 TLS而是在载荷加密、认证标签、防重放三个层面单独做手脚还要维护一套独立的序号计数器。换句话说就算你成功调用了 srtp_open只要 SSRC、ROC 或者 master key 对不上抓包看到的还是一堆无法解开的密文。这份资源适合正在做 VoIP、WebRTC 网关或者视频流加密的开发者尤其是被“加密了但对方解不开”“偶发性的几秒杂音”这类问题困住的人。2. 先统一认知SRTP 的认证标签、加密套件与 libsrtp 选型边界2.1 为什么单独用“RTP 加密”认证标签与防重放是双保险RTP 和普通 TCP 流量最大的区别在于它有实时性要求不允许重传。SRTP 就是在不改动 RTP 头语义的前提下对 RTP 载荷部分做加密同时对整个包包括头做 HMAC 完整性校验。这样即便有人篡改了 RTP 头里的时间戳或 SSRC接收端也能通过认证标签直接丢弃非法包。libsrtp 里最容易被忽略的是防重放窗口。它本质上是一个滑动窗口默认窗口大小是 128。也就是说如果包序号跳变超过 128后到的旧包会被当成重放包丢掉。很多人在调试时发现“偶尔有几帧丢失”其实不是网络丢包而是 ROCRollover Counter序号翻转计数器没同步导致接收端看到的序号和预期的差了一万多直接被判定为重放。另外需要明确一点SRTP 只保证机密性和完整性不保证可用性。如果密钥协商走的是明文 SIP攻击者照样可以抓走 master key 再离线解密。所以在实际部署里SRTP 通常搭配 DTLS-SRTP 或者 SIP over TLS 使用密钥不会裸奔在信令里。2.2 选型对比libsrtp 的 AES_CM 与 AES_GCM参数怎么设libsrtp 支持的加密套件主要有两个方向AES_CM_128_HMAC_SHA1_80 和 AES_256_GCM。前者是 WebRTC 早期最常见的组合加密用 AES-CMCounter Mode认证用 HMAC-SHA1只取前 80 bit 作为认证标签。后者是 RFC 7714 定义的扩展一个 AES-GCM 同时完成加密和认证标签长度可选 16 字节安全性更高、CPU 开销也更小。参数选择的边界在于兼容性。如果你只需要和 WebRTC 的 Chrome/Edge 互通AES_CM_128_HMAC_SHA1_80 仍然是默认值改成就可能会握手失败。如果是一套完全自己控制的软交换系统建议直接用 AES_256_GCM省掉 HMAC 的计算量在低端 ARM 板子上也能跑动。我整理了一张常用套件的参数对比方便你对照手头资源的 test 向量做验证参数项AES_CM_128_HMAC_SHA1_80AES_256_GCM加密算法AES-CTRAES-GCM密钥长度128 bit256 bit盐长度112 bit96 bit12 字节认证标签10 字节16 字节典型场景WebRTC 互通私有 VoIP 网关libsrtp profile 名srtp_aes128_cm_hmac_sha1_80srtp_aes256_gcm选型时记住一个原则加密套件的选择不是越强越好而是要跟对端协商结果一致。你在配置 SRTP 策略时填了 AES_256_GCM但对方 SDK 只支持 AES_CM最终表现就是收不到任何媒体包而且没有明文报错只能在日志里看到srtp_unprotect failed。3. 从 srtp.rar 到可用库编译、初始化、第一个加密 RTP 包3.1 源码包结构configure 与 make 的边界把 srtp.rar 解包之后你大概率能看到 configure 文件、srtp 目录、test 目录和 crypto 目录。libsrtp 的构建系统是 autotools比较老的版本还能看到configure.in新版则直接用cmake。建议先跑一遍 configure 生成 Makefile不要手动去改 Makefile 里的 CFLAGS因为库里有大量用编译器宏控制的内联汇编和 AES-NI 优化。常见做法是先确认当前系统有没有 OpenSSL因为新版 libsrtp 的 GCM 模式可以依赖 OpenSSL 的 EVP 接口也可以用自己的 crypto 实现。默认情况下它会做自动探测但如果你想避免 OpenSSL 版本差异带来的困扰可以显式关掉./configure --disable-openssl make -j4 sudo make install这个命令的语义是把 OpenSSL 依赖排除让 libsrtp 使用内置的 AES 和 SHA 实现。好处是编译出来的静态库体积更小避免了系统里多个 OpenSSL 版本造成的符号冲突。坏处是 AES-GCM 的性能会略低于直接用 OpenSSL 的版本。参数说明--disable-openssl是 autotools 约定的开关名不同小版本可能写成--without-openssl建议先跑./configure --help | grep openssl确认。如果你所在的环境是 ARM 交叉编译还需要额外加--hostarm-linux-gnueabihf同时指定CC和CROSS_COMPILE否则 configure 探测的跑的是 x86 测试程序交叉编译时会直接卡住。编译完后pkg-config --cflags --libs libsrtp2能输出了说明库已经装进系统。接下来要干的事就是在自己的代码里初始化它。3.2 初始化会话与添加数据流srtp_create / srtp_add_stream 详解libsrtp 的最小使用流程分四步全局初始化、创建会话、添加数据流、保护/解保护。下面这段代码是单路流的初始化骨架我写注释时特意标出了几个容易被忽略的参数#include srtp2/srtp.h #include string.h int main() { srtp_t session; srtp_policy_t policy; srtp_init(); memset(policy, 0, sizeof(policy)); // 加密和认证算法选择注意 key size 要和 profile 对应 crypto_policy_set_aes_cm_128_hmac_sha1_80(policy.rtp); crypto_policy_set_rtcp_default(policy.rtcp); policy.ssrc.value 0x12345678; // 智能固定一个 SSRC方便抓包排查 policy.ssrc.type ssrc_specific; policy.key (uint8_t *)0123456789012345678901234567890123456789; // 前16字节是 master key后14字节是 master salt policy.next_seq 0; policy.allow_list NULL; policy.window_size 128; err_status_t err srtp_create(session, policy); if (err ! err_status_ok) { return -1; } // 如果后面要加第二个流的 SSRC只能靠 srtp_add_stream srtp_add_stream(session, policy); return 0; }这段逻辑里最关键的是policy.key的长度AES_CM_128 的 profile 要求 master key 16 字节、master salt 14 字节合在一起 30 字节。如果只填 16 字节运行时会报err_status_bad_param。而 AES_256_GCM 则要求 key 32 字节、salt 12 字节合起来 44 字节。我见过很多人从网上复制的代码key 长度写死 16换到 GCM 模式就翻车。next_seq参数要重点说它表示这个流的起始序列号。如果你的加密是在 RTP 发送端做的next_seq应该等于当前 SSRC 即将发送的下一个序号。如果你做的是一个旁路加密设备从现有 RTP 流中间开始加密就必须把next_seq设置为当前抓到的包序号否则接收端解密时序号对不上认证标签全部失败。window_size默认 128如果网络环境中存在乱序比较严重的情况比如跨公网 Wi-Fi可以适当增大到 512。注意这个值不能超过MAX_REPLAY_WINDOW改大了内存开销也随之增加每个流都要维护对应的位图窗口。初始化完成后加密一个 RTP 包的调用也简单uint8_t rtp_buf[1600]; int len 120; // 已填充好的 RTP 包长度 srtp_protect(session, rtp_buf, len); // 返回值 err_status_ok 才算保护成功len是输入输出参数进去时是原始 RTP 包长度出来时是加密后的长度。因为 SRTP 要追加认证标签所以输出长度通常会比输入多 10 或 16 字节。你需要确保rtp_buf的容量足够大至少要能容纳len 16否则就会发生缓冲区越界表现结果是加密函数成功但缓冲区后面的内存被覆盖最终运行期随机崩溃。4. 密钥从哪来、抓包怎么验SRTP 会话参数与调试闭环4.1 密钥导出与参数映射SSRC、ROC、index 的关系搞定了库的调用下一关是密钥协商。SRTP 密钥协商在 WebRTC 里走的是 DTLS-SRTP在传统 SIP 里走的是 SDES。无论怎么协商最终双方手上得有同一套参数master key、master salt、profile、SSRC、ROC。这五个参数里master key 和 salt 是密钥协商产生的profile 是双方能力协商出来的SSRC 来自 RTP 头ROC 是收包方根据序号翻转自己推导出来的。也就是说解密端必须知道当前 ROC 是多少才能正确还原出包的序号。SRTP 的包序号由ROC || RTP sequence number拼接而成完整序号用来计算 AES 的计数器初值。所以你可以这么理解 ROC 的作用当 RTP 序号从 65535 翻转到 0 时ROC 加 1。如果接收端多翻了一次或者少翻了一次所有后续包的解密都会错位。这不像密钥错了会连续报错而是会表现为“前面一两秒能解后面全乱”——因为序号翻转之前认证标签还是能验过的。libsrtp 内部针对每个 SSRC 都维护了一个rdbrollover detection buffer它在尝试解保护时会猜测当前 ROC 是 n 或 n-1用两个候选 ROC 各算一遍认证标签。只有标签能匹配上的那个候选才会被接受。这也就是说如果你的 master key 正确但 SSRC 没设对认证永远匹配不上那srtp_unprotect每次返回的都是err_status_auth_fail而不是类似“找不到流”的提示。4.2 Wireshark 解密设置把 key 和 salt 填入 SDES调试 SRTP 最有效的闭环不是盲目打日志而是用 Wireshark 把密文还原成明文然后直接比对 RTP 头里的字段。Wireshark 3.x 自带 SRTP 解密支持操作路径是Preferences → Protocols → SRTP → Add decryption key。界面会让你填 Key、Salt、加密类型和 SSRC。按照上面代码里的测试参数填写方式如下字段值Key16 字节十六进制对应01234567890123456789012345678901Salt14 字节十六进制对应23456789012345678901234567Crypto SuiteAES_CM_128_HMAC_SHA1_80SSRC0x12345678填完后Wireshark 会把匹配的 SRTP 包自动解析成 RTP 包你可以在RTP 头部里查看 sequence number 和 timestamp确认密文里的序号是否连续。我一般会拿 Wireshark 的 RTP 分析功能同时打开Telephony → RTP → RTP Streams查看有没有丢包、乱序、抖动。如果在 Wireshark 里能看到正常 RTP 流但接收端播放仍异常问题大概率存在于接收端自己的 SRTP 状态机里跟加密本身无关。5. SRTP 避坑指南五个我在集成时踩过的典型错误5.1 现象protect 后 RTP 流无法解码抓包全是乱码一次调试视频会议网关时我把srtp_protect加进去Wireshark 里能看到 SRTP 包但接收端说全部黑屏。抓包确认包发出来了也加了认证标签可对方日志反复出现authentication failure。原因最后定位在 SSRC 不一致。我在发送端初始化策略时写死的 SSRC 是 0xdeadbeef但实际 RTP 头里的 SSRC 是 0x12345678。libsrtp 的srtp_protect并不检查头里的 SSRC 是否和策略一致它只是把载荷加密、标签加上就发出去。接收端按包里的 SSRC 找对应的流发现流的 master key 不匹配自然认证失败。解决方法是保证一个准则策略里的 SSRC 必须和实际发送的 RTP 包头的 SSRC 相同并且在调用srtp_protect前把这个值写到 RTP 头里不能让库代劳。从那以后我习惯在初始化前从 RTP 头结构体里ntohl读出 SSRC再填进 policy而不是写死。5.2 现象音频开头有几十毫秒杂音后续正常这个问题很刁钻。现象是通话建立后的第一秒有非常明显的爆音之后恢复正常。用 Wireshark 解密看到前面的包也能解出来序号也没跳。原因还是next_seq。接收端流创建时的next_seq默认从 0 开始发送端却从 RTP 实际序号比如 23567 开始加密。接收端会拿 0 当作起始序号尝试解密第 23567 号包时会发现 ROC 对不上于是内部判定为超远序号启动 ROC 猜测逻辑去试 n-1。这种猜测往往能成功但对第一个包来说相当于“强行对齐”前几个包在 ROC 未刷新前可能被丢弃或解密出错于是开头那段解码出杂音。解决方法是协商媒体时把启动序号带上或者让发送端的前几个包直接以明文 RTP 形式发送用一个特殊 payload type 告诉接收端“这个不是 SRTP”。很多硬件 SIP 话机就是这么实现 early media 的。要是你们双端都是自研最干脆的方法就是把next_seq在创建流时置为 0同时发送端强制把首个包的 RTP 序号清零一次。5.3 现象多路 SIP 呼叫同时开互相串流网关同时承载 4 路呼叫一路通话正常另一路看监控画面发现自己的视频流里偶尔穿插别人的画面。解密层没有报错payload 解出来的内容却是乱码。原因是多个呼叫复用了同一个srtp_t会话而没有为每个 SSRC 单独创建流。libsrtp 的会话可以包含多个数据流每个流由 SSRC 区分。当你在创建第一个会话后后续再接新呼叫必须用srtp_add_stream把新策略加进同一个会话。如果每次新呼叫都重新调用srtp_create旧会话里的流存在新会话里也有新增流两条流共享同一个 master key 的话RTP 序号和 SSRC 一旦被中间设备改写就分不清谁是谁了。解决方式每个呼叫独立密钥独立创建会话。不要为了省内存而共用 key。我后来干脆改成一对一映射一个srtp_t只服务一个方向的媒体逻辑清晰出问题也好定位。5.4 现象替换 openssl 后 libsrtp 编译不过把一台服务器的 OpenSSL 从 1.1.1 升级到 3.0 后重新编译 srtp.rar结果直接报错openssl/evp.h: No such file or directory或者链接时找不到EVP_aes_256_gcm。原因是 libsrtp 在编译时把 OpenSSL 的头文件路径和库路径写在了include/Makefile的全局 CFLAGS 里。升级系统后路径变了旧版本头文件残留导致编译器拿到了相互冲突的定义。解决方法是先把make clean跑一遍删掉 CMakeCache 或者 config.log再重新 configure。如果还是报错大概率是系统里同时存在/usr/include/openssl和/usr/local/include/openssl两套头文件。我在操作时会这样指定./configure CFLAGS-I/usr/include/openssl LDFLAGS-L/usr/lib/x86_64-linux-gnu -lssl -lcrypto这样让编译器只从系统库目录读一份 OpenSSL 头。另一个备选方案是干脆--disable-openssl用内置 crypto避免跟系统 OpenSSL 深度绑定。5.5 现象GCM 模式下包多出部分重复字节切到 AES_256_GCM 后每次加密出来的 SRTP 包末尾会跟着两段完全相同的字节。乍一看像是认证标签复制了一份但接收端为什么还能通过原因并不是复制而是我误把一个 44 字节的 blob 填到了policy.key里而 GCM 需要 3212 字节。库里把前 32 当作 key后 12 当作 salt多余的字节会被忽略但这不影响加密计算。真正多出来的“重复字节”是我背着库又在应用层手动附加了认证标签字段。也就是说虽然库本身没报错但代码里把policy.rtp.auth的算法配置成了 HMAC又在调用srtp_protect之后手写加了一次 HMAC形成了两层认证。解决方法是不要把crypto_policy_set_aes_cm_128_hmac_sha1_80和 GCM 混在一起。GCM profile 的policy.rtp里 auth 算法必须是NULL_AUTH因为 GCM 自带 tag。在 libsrtp2 的代码里直接区别就是// GCM 模式 policy.rtp.cipher_type AES_256_GCM; policy.rtp.auth_type NULL_AUTH;如果你像我一样从老代码迁移过来务必全局搜一下set_aes_cm和set_aes_gcm的调用点不要留下半套 HMAC 逻辑。6. 一个验证技巧用 Wireshark 解密自己的 SRTP 流6.1 小步跑通先让自带测试程序把键位对齐在写完业务代码之前先跑库自带的test_srtp程序它会用一套固定密钥加密已知向量然后比对输出。这是最快确认你手里的库和调用 API 是否匹配的途径。新版 libsrtp 的 test 程序名可能叫test_srtp库目录编译后在test/下跑一下能看到类似PASS的输出。如果 test 都过了再把你自己的发送代码和 Wireshark 配套验证。重点验证两件事第一srtp_protect输出的长度增量是否等于 profile 的标签长度第二Wireshark 里用固定密钥解密出来RTP payload 块的第一个字节是否还有你业务自定义的扩展头。这样就把整个链路从库到抓包全部打通了。6.2 从每次踩坑中提炼固定动作经过上面这几次折腾现在我接手任何 SRTP 资源都遵循一套固定动作先检查policy.key长度是不是对着当前 profile 填的再确认 SSRC 与next_seq是从实际 RTP 包头里读出来的不是文档里复制的然后把window_size显式设成 128 以上的值最后用 Wireshark 解密验证一个完整呼叫的语音或者视频流。我至今仍在反复提醒自己一个教训SRTP 调试不要凭直觉不要觉得“密钥一致就够了”必须把 SSRC、ROC、next_seq、认证标签长度这四个变量同时摆到桌面上对一遍。如果你现在拿到 srtp.rar我建议你先把crypto_policy_set_aes_cm_128_hmac_sha1_80这行写进测试代码跑通一个最细的流程再考虑引入 DTLS-SRTP 之类的协商逻辑。希望帮到你。本文还有配套的精品资源点击获取
返回列表