
简介实时音视频通信中数据加密是保障内容安全的关键环节。SRTP安全实时传输协议在RTP基础上提供机密性、完整性与抗重放保护广泛用于WebRTC、监控流加密、直播连麦等场景。libsrtp作为成熟的开源实现是集成SRTP能力的主流选择。然而许多开发者从网络获取libsrtp源码包如srtp.rar后常因版本不匹配、策略配置错误、ROC同步问题而导致编译或运行失败。本文从解压与版本识别入手系统讲解libsrtp的编译、最小示例、policy细节以及真实工程中的常见坑帮助读者快速跑通SRTP加密链路并避免典型集成错误。 很多做音视频通信的人第一次接触 SRTP 开源库都是从一个疑似的摸不着头脑的压缩包开始的你搜“srtp.rar”也好srtp open、srtp r 也好最终目标其实很明确拿到一套能直接编译、能集成进项目的 SRTP 实现。而真正把 srtp.rar 解压、编译、跑通第一个 demo再把它接进真实的音视频工程里中间有不少信息是文档里不会明说的。这篇文章就围绕我最近的一次完整实战来写从压缩包解压讲到最后集成排错把我踩过的坑和验证过的方法都摊开。这套开源库解决的是实时音视频流的加密传输问题也就是大家常问的“音视频 srtp 加密”。不管你是做 WebRTC 网关、监控流加密、直播连麦还是自己写 RTP 传输栈SRTP 几乎都是绕不开的那一层。希望这篇内容能帮你少走点弯路尤其是那些刚把源码包拿到手、还不知道从哪下手的读者。1. srtp.rar 背后是什么先把这个压缩包拆明白先说结论你在网上找到的 srtp.rar 这类资源包绝大多数是 libsrtp 的源码快照或者在源码基础上打了补丁、重新打包过的衍生版本。libsrtp 是 Cisco 维护的开源 SRTP 实现C 语言编写也是很多商业产品、开源项目包括 WebRTC的底层依赖。1.1 这个资源包里通常装了什么正常的 libsrtp 源码包一般会包含这几部分srtp/目录最核心的加解密逻辑、会话管理、策略管理比如srtp.c、srtp.h。crypto/目录底层密码学实现包括 AES、HMAC、SHA-1 等以及随机数生成。include/目录对外暴露的头文件很多版本里srtp.h会在这里或者在srtp/include下。test/目录官方测试程序包含用于验证实现正确性的测试向量非常关键。configure.ac、CMakeLists.txt、Makefile.in等构建脚本。README.md、CHANGES、LICENSE等说明文档。我拿到的这个 srtp.rar 解压之后没有独立的一级目录所有文件散落在压缩包根目录下这种情况下更要留意版本信息和文件完整性。我建议先把所有文件解压到一个独立的目录里不要直接在当前目录原地解压否则各种.o文件、Makefile和你的项目文件混在一起后续清理很痛苦。版本识别很简单优先看configure.ac或CMakeLists.txt里的VERSION定义。如果压缩包里没有这两个文件那就看srtp.h里的SRTP_VERSION宏。不同版本的 API 差异很大尤其是 1.x 和 2.x 之间的srtp_create、srtp_protect等函数签名都改过。网上很多教程用的还是 1.4.x 的老接口如果你拿到的是 2.x 源码照抄旧博客的代码会直接编译失败。1.2 解压不是双击就行rar 工具和包完整性检查你可能觉得解压一个 rar 有什么好说的但这类在网络上传播的资源包恰恰是最容易出问题的地方。首先是解压工具。Windows 上我建议直接用 7-Zip能解 rar、zip、7z免费且没有广告。WinRAR 也可以但要注意官网之外的“破解版”“烈火版”很多捆绑了推广软件安全风险很大。我自己遇到过下载所谓“绿色版 WinRAR”结果被注入广告程序的情况后来统一换成 7-Zip省心很多。Linux 上通常用unrar-free或unrar但有些老格式的 rar 包unrar-free解不了这时可以安装官方 unrar 或者用 7z 命令7z x srtp.rar7z x会保留压缩包内的目录结构7z e则是把所有文件解压到当前目录、丢弃路径。如果你不确定包内部结构用x更安全。解压之后第一件事是检查完整性。很多资源包是从网盘下载的传输过程可能损坏或者发布者打包时就不完整。你可以用哈希校验来判断sha256sum srtp.rar如果发布者在下载页面提供了 SHA-256那就比对一下。如果没有提供至少看一下压缩包能不能完整解压解压过程中有没有 CRC 错误。如果解压时提示某个文件校验失败千万不要硬着头皮往下用建议重新下载或找其他来源否则后面编译期会出现各种诡异的“缺文件”报错排查起来特别浪费时间。还有一个绕不开的话题压缩包有密码怎么办网上有不少人搜“rar 密码移除”但我的实际建议是如果这个包设了密码最可靠的途径是联系发布者获取正确密码。现代 rar 加密用的是 AES强密码下暴力破解完全不现实市面上的“密码移除工具”基本是骗下载量或者捆绑流氓软件的。为了一个开源库源码去冒这个风险完全不值得。1.3 源码目录结构与版本识别解压完成后第一眼要确认目录结构和版本。一个比较典型的 2.x 版本 libsrtp 源码包解压出来的样子srtp/ ├── crypto/ │ ├── include/ │ ├── aes/ │ ├── hash/ │ ├── math/ │ ├── rng/ │ └── kernel/ ├── include/ ├── srtp/ ├── test/ ├── CMakeLists.txt ├── configure.ac ├── Makefile.in └── VERSION这里有一个容易忽略的点有人拿到的 srtp.rar 是 WebRTC 源码里的某个third_party/libsrtp目录这种目录结构会略微不同因为 WebRTC 会把 libsrtp 嵌入到自己的构建系统里。如果你是要单独集成建议从上游 GitHub 下载官方发布版本而不是从某个大工程里抠出来。2. SRTP 不是“RTP 加个密”那么简单我见过很多朋友对 SRTP 的理解就是“把 RTP 包加密一下”。这个理解方向没错但实际做起来会发现SRTP 要解决的问题远不止“不让别人偷看”这么简单。它涉及数据机密性、完整性、防重放、密钥管理等多个层面如果只盯着加密算法很容易在集成时翻车。2.1 RTP 与 SRTP加密不是全部先简单回顾一下 RTP。RTP 是实时传输协议用于在 IP 网络上传输音频、视频等实时数据。它的头部包含序号、时间戳、SSRC同步源标识等字段。问题是RTP 本身是明文传输的任何人都可以在链路上抓包还原音频和视频内容甚至篡改 RTP 头、注入恶意媒体包。SRTPSecure Real-time Transport Protocol就是在 RTP 基础上增加安全能力定义在 RFC 3711 中。它做的事包括对 RTP 负载进行加密保证机密性。对整个 RTP 包头部和负载计算认证标签保证完整性。通过序号检查和重放窗口抵御重放攻击。也就是说SRTP 保护的不仅是“内容不能被看”还包括“内容没有被改”和“包是新鲜的、不是重放的”。这三者缺一不可。如果只加密不认证攻击者虽然看不懂载荷但可以把一串乱码灌进接收端造成解码器崩溃如果只认证不加密类似监听的用户还是能看到你的画面和声音内容。2.2 机密性、完整性、抗重放三个需求缺一不可libsrtp 默认使用的是 AES 加密常见的是 AES-CMAES 计数器模式CTR 模式将计数器值加密后与明文异或得到密文。计数器由主密钥、SSRC、包索引和 IV 生成。AES-CM 性能很好支持随机访问解密很适合实时流场景。完整性方面常见的有 HMAC-SHA1输出 10 字节或 20 字节的认证标签。有些新版本也支持 AES-CMAC。认证标签会追加在 RTP 包末尾这也是为什么同样一个 RTP 包在走 SRTP 之后长度会比原来大网络层 MTU 预算必须提前留足这个我后面会专门讲。抗重放是 SRTP 里很容易被忽略的点。SRTP 包头里保留 RTP 的 16 位序号但远远不够用所以内部维护了一个 48 位的扩展序号高 32 位叫 ROCRollover Counter低 16 位就是原始 RTP 序号。每当 RTP 序号从 0xFFFF 回绕到 0x0000 时ROC 就加 1。接收端通过维护一个滑动窗口判断收到的包是否“太旧”如果序号落在窗口之外、已经重放过直接拒绝。这个机制看似简单但实际工程中如果发送端重置了 ROC接收端没有同步就会出现大量replay_old报错表现为偶尔能听到声音、画面卡顿、日志刷错误。这个问题我见过不止一次后面会细说。2.3 密钥从哪来DTLS-SRTP 的角色SRTP 解决了传输安全问题但有一个关键问题它不负责密钥怎么协商。主密钥、会话密钥、盐值这些必须通过可靠的带外机制分发。常见的有三种DTLS-SRTP在 DTLS 握手过程中协商 SRTP 密钥材料WebRTC 里用得最多。MIKEY多媒体互联网密钥管理用于传统 SIP 环境。ZRTP在媒体流内进行密钥协商。很多人会问DTLS 和 SRTP 是什么关系简单说DTLS-SRTP 运行在媒体通道的传输层之上先通过 DTLS 握手验证对端身份然后导出 SRTP 需要的主密钥和盐值。握手完成后媒体数据走 SRTP。如果只是自己在局域网里做测试也可以手动指定固定的主密钥libsrtp 的测试用例就是这么干的但正式环境千万不要用固定密钥。3. libsrtp 编译和最小可用示例从 srtp.rar 解压出来的源码我建议先按官方方式编译一遍跑通自带的测试再去集成到自己的项目。这一步能帮你确认压缩包本身是否完整、工具链是否正常也为后面调试打好基础。3.1 编译前置条件libsrtp 的依赖很少核心只需要 C 编译器和标准库。在 Linux 上需要确认有build-essential、make。如果你想跑测试向量还需要 Python用于部分测试脚本。先看我这边 Linux 环境下的编译过程cd srtp ./configure make make runtestconfigure脚本如果没有生成可能需要先跑autogen.sh它会根据configure.ac生成configure。如果源码包是从网上来的缺少autogen.sh或configure那就用 CMake 构建mkdir build cd build cmake .. makeCMake 方式在 Windows 上也能用Visual Studio 可以直接打开生成的.sln。make runtest这一步强烈建议跑一下它会执行大量测试向量验证包括 RFC 3711 里的官方向量。如果跑出来一堆 FAIL那说明这套源码被改坏了或者编译选项有兼容性问题建议直接换上游源码。我拿到的那包代码第一次make runtest就有一个 SHA-1 相关的错误最后发现是发布者用老代码强行适配新编译器导致的换成官方最新版本后一切正常。3.2 一个最小加密会话跑起来编译通过后写一个最小测试程序来走通“加密一个 RTP 包再解密回来”的流程。我先给出一段典型的 libsrtp 2.x 代码#include stdio.h #include string.h #include srtp.h int main(void) { srtp_t session; srtp_policy_t policy; srtp_err_status_t status; uint8_t key[30] {0}; // 30字节: 16字节主密钥 14字节盐值 uint8_t rtp_packet[128]; int pkt_len 12 8; // RTP头12字节 8字节测试负载 memset(rtp_packet, 0x11, sizeof(rtp_packet)); rtp_packet[0] 0x80; // RTP版本号 rtp_packet[1] 0x7a; // PT 122 rtp_packet[2] 0x00; rtp_packet[3] 0x01; // 序号 1 rtp_packet[4] 0x00; rtp_packet[5] 0x00; rtp_packet[6] 0x00; rtp_packet[7] 0x00; // 时间戳 rtp_packet[8] 0x12; rtp_packet[9] 0x34; rtp_packet[10] 0x56; rtp_packet[11] 0x78; // SSRC 0x12345678 // 初始化库 status srtp_init(); if (status ! srtp_err_status_ok) { return -1; } // 策略设置 memset(policy, 0, sizeof(policy)); crypto_policy_set_aes_cm_128_hmac_sha1_80(policy.rtp); crypto_policy_set_aes_cm_128_hmac_sha1_80(policy.rtcp); policy.ssrc.type ssrc_specific; policy.ssrc.value 0x12345678; policy.key key; policy.allow_repeat_tx 0; policy.window_size 128; policy.next_seq 0; status srtp_create(session, policy); if (status ! srtp_err_status_ok) { return -1; } // 加密srtp_protect 会把认证标签追加到包末尾 int protected_len pkt_len; status srtp_protect(session, rtp_packet, protected_len); printf(protected len: %d - %d\n, pkt_len, protected_len); // 解密 int decrypted_len protected_len; status srtp_unprotect(session, rtp_packet, decrypted_len); printf(decrypted len: %d\n, decrypted_len); srtp_dealloc(session); srtp_shutdown(); return 0; }这段代码的关键点在于crypto_policy_set_aes_cm_128_hmac_sha1_80设定的是加密算法 AES-128-CM、认证算法 HMAC-SHA1、认证标签 10 字节。policy.key的前 16 字节是主密钥后 14 字节是盐值总共 30 字节。如果你用crypto_policy_set_aes_cm_128_hmac_sha1_32认证标签就是 4 字节但安全性弱很多除了兼容老旧设备之外不建议用。next_seq这个字段也很重要。发送端第一次调用srtp_protect时libsrtp 会以next_seq作为初始 RTP 序号。设置成 0 意味着第一个包序号从 0 开始如果你的 RTP 栈从 1 开始管理序号这里设置 1 更合理。如果发送端每次只发一个包且接收端允许一定偏移乱序容错主要看window_size。3.3 理解 policy 的细节policy 是 libsrtp 里最核心的结构体很多 API 报错都源于对它理解不够。我整理了一个表格列出我经常检查的字段字段作用我的建议ssrc.type确定策略匹配方式明确知道 SSRC 时用ssrc_specific未知时用ssrc_any但后者会降低接收端排查问题的便利性ssrc.value具体的 SSRC 值每路媒体流应使用独立 session 和独立 SSRCkey主密钥 盐值生产环境必须通过 DTLS-SRTP 协商测试可用固定值rtp/rtcpRTP 和 RTCP 的安全策略两者都要设置不能只设 RTPwindow_size抗重放窗口大小一般 64 或 128 足够窗口越大越耐乱序但内存开销增加allow_repeat_tx是否允许发送相同序号的新包发送端如果是覆盖式重传RTP 序号不变需要设成 1next_seq发送端初始序号和上层 RTP 栈对齐auth_tag_len认证标签长度默认 10 字节一般不用动实践中有个常见误区把window_size设得超大认为海量乱序包需要很大窗口。但实际上 SRTP 窗口是基于“低位序号差”计算的窗口大小代表相对于最大接收序号的偏移普通音视频场景 128 已经绰绰有余。设太大反而会让接收端对陈旧包更宽容重放攻击的窗口也变大。4. 在真实音视频工程里集成 SRTP 的坑跑通最小示例只是第一步真正把 SRTP 接入到业务代码里问题才会浮出来。下面这些坑都是我实际踩过或帮别人排查过的每一条都对应真实崩溃现场。4.1 每包多出来的字节数预算要留够很多人的首次翻车不是加密失败而是加密之后包太大、UDP 发不出去。srtp_protect会在 RTP 包末尾追加认证标签默认 10 字节如果 RTCP 包也走 SRTP同样有认证标签开销。此外如果启用了 MKIMaster Key Index还会多出 MKI 字段和可变长度认证标签但通常用不到。实际发送时UDP 载荷的 MTU 要考虑这个增量。比如你的原始 RTP 包设计为 1200 字节加密后可能是 1210 字节。如果网络准备用 1200 字节的 MTU 发送加密后就会出现 IP 分片。分片会导致重传、乱序、丢包率上升这对实时音视频是致命的。我的建议是在分配发送缓冲区时直接用srtp_get_protect_trailer_len(session, len)查询认证标签开销把缓冲区预留出来。不要写死某个数值因为不同算法、不同版本可能返回不同的 trailer 长度。接收端分配解密缓冲区时同样要留足余量srtp_unprotect会原地解密如果缓冲区刚好是原包大小认证标签部分可能写到越界内存属于典型的内存破坏隐患。4.2 SSRC 通配符和流状态libsrtp 支持两种 SSRC 策略指定值ssrc_specific和通配符ssrc_any。通配符在实现网关或混流器时看起来很诱人一个 session 处理所有 SSRC 的包省去每个流都创建一次 session 的麻烦。但坑在于通配符模式下libsrtp 只维护一组序号状态和重放窗口。如果有多路媒体流共用同一个 session各流的 RTP 序号是独立的这会导致接收端把 A 流的包序号放进对 B 流的预期里造成大量replay_old甚至把合法包直接丢掉。我实测下来的结论是即使在网关场景也尽量按流创建 session每路流用独立的 SSRC、独立的策略、独立的状态。如果流的数量很少个位数多 session 的内存开销完全可以接受。通配符策略只适合单路流的单向传输场景比如某些安全监控设备上报时只存在一个媒体源。4.3 与 WebRTC 源码的版本匹配如果你不是直接调用 libsrtp而是集成 WebRTC那同样会遇到版本问题。WebRTC 的third_party/libsrtp是定制过的API 可能和上游略有差异编译宏也不同。比如 WebRTC 倾向于用 BoringSSL 而不是 OpenSSL如果你的代码同时链接了其他 OpenSSL 依赖符号冲突和 ABI 不匹配的问题会让你怀疑人生。我经历过的典型报错是undefined reference to srtp_protect查完发现是头文件和库版本不匹配头文件是 2.x库文件却是 1.x。头文件里srtp_protect只接受 3 个参数老库链接时找不到符号。解决方法是让头文件和库文件严格来自同一份源码并且构建系统里只保留一条 libsrtp 路径。如果是在 WebRTC 工程里优先用 WebRTC 自带的版本和构建脚本不要自己单独编一份再混进去。另外libsrtp 需要全局随机数生成器。编译时如果没启用适当的随机数后端可能默认使用/dev/urandom或平台相关实现。在嵌入式环境或沙箱环境里随机数不足会导致 key 生成失败。用固定 key 在测试环境没问题但上生产前一定要检查目标平台的随机数能力。4.4 序号回绕和 ROC 同步这是最隐蔽的一个坑。RTP 序号只有 16 位最大 65535。发送端长时间运行后序号从 65535 回绕到 0ROC 增加。正常情况下接收端能自动处理回绕。但如果发送端进程重启重置了 ROC而接收端还保留着旧的高水位序号那么新包的序号虽然变了但 ROC 没跟上接收端会认为这是重放过期的包。具体表现是你重启了发送端加密参数、密钥都没变但接收端日志持续输出srtp_err_status_replay_old。排查方向往往是“密钥是不是变了”“网络是不是有问题”实际上问题出在 ROC 没有同步。libsrtp 提供了srtp_set_stream_roc和srtp_get_stream_roc可以用来手动同步 ROC。如果发送端和接收端能协商一个基准序号重启后双方都设置到同样的 ROC就能避免误判。如果没法同步最简单的办法是更换 SRTP 会话重新握手、重新生成密钥这样序号状态全部重置。生产环境里SRTP 会话的生命周期和媒体流的生命周期应该绑定流结束就销毁 session下一次通话重新协商会话不要复用跨通话的 session。5. 实测中的排错经验与学习建议这一部分我整理一下自己实践中的排查方法和学习路径希望能帮你快速定位问题也顺便聊聊怎么系统掌握 SRTP 这块知识。5.1 最常见的三个报错和解决报错或现象可能原因排查与解决srtp_err_status_replay_old接收窗口过期、ROC 不同步、同一包重复到达先看发送端是否重复发送相同序号再检查会话是否跨重启复用必要时手动srtp_set_stream_rocsrtp_err_status_auth_fail密钥不一致、包头被篡改、认证标签计算方式不匹配对比两端 key 和盐值确认算法策略一致认证标签长度等抓包看接收到的包尾长度是否符合预期srtp_err_status_fail内部状态异常、未初始化、证书相关资源失败查看是否有遗漏srtp_init()确认 policy 字段完整性检查随机数生成器是否可用auth_fail是最常见的集成错误。它往往不是算法问题而是两端的 key 或盐值不完全一致。我曾经在调试时发现一端把 key 的 30 字节都当作主密钥用另一端把后 14 字节当作盐值结果数据完全对不上。这种问题靠读代码看不出最好用两端互相打印 key 的十六进制值来比对。5.2 用测试向量验证实现如果你不是直接使用 libsrtp而是基于它做二次开发或者照着 RFC 自己写实现那一定要用官方的测试向量做验证。RFC 3711 的附录 A 提供了若干 SRTP 测试向量包括密钥、盐值、明文 RTP 包、预期密文和认证标签。libsrtp 源码中的test/目录下有许多基于这些向量的测试用例make runtest都会跑到。我建议自己封装一层测试固定密钥和盐值输入固定 RTP 包断言srtp_protect后的输出与预期一致。这样如果将来升级库版本运行一遍回归测试就能发现有没有兼容性变化。抓包对比也是一个有效的验证手段。发送端启用 SRTP 后用 Wireshark 抓包如果能看到 UDP 载荷比原始 RTP 多出的字节数等于认证标签长度说明链路方向没问题。接收端解密后可以在应用层打印srtp_unprotect返回的载荷长度和关键 RTP 头字段确定解密链路是否贯通。5.3 几条实在的学习建议第一不要自己从零实现 SRTP。这个领域密码学和协议细节非常多自己造的轮子在正确性、性能侧写、安全性上都很难和高成熟度的开源实现竞争。最理想的路径是基于 libsrtp 这类成熟库做集成如果确实有定制需求先吃透源码再动手改。第二读源码比读博客更靠谱。libsrtp 的代码注释很详细尤其是srtp.c里 policy 的处理流程、crypto_policy_set_*系列函数直接告诉你每个策略具体启用的算法和参数。遇到网上说法和源码冲突时以源码为准。第三重点弄懂会话生命周期。SRTP 会话的创建、使用、销毁是集成中出错率最高的部分。把srtp_create、srtp_protect、srtp_unprotect、srtp_dealloc这几个函数的前置条件和后置条件弄清楚能避免绝大多数崩溃和内存问题。第四在阅读 RFC 方面我建议优先看 RFC 3711SRTP 协议本身和 RFC 7714WebRTC 中 SRTP 的使用前者帮你理解协议字段和算法要求后者帮你看到实际工程里的使用姿势。就我这次的经历而言最花时间的不是加密逻辑本身而是把密钥管理和会话生命周期搞明白。很多人拿到 srtp.rar 之后急着写代码一跑不通就开始怀疑算法其实大部分问题都出在策略配置、密钥格式和状态同步上。建议你拿到源码后先完整跑一遍官方测试再写最小示例最后再对接业务这条路最顺畅。本文还有配套的精品资源点击获取