ARTICLE DETAIL

资讯详情

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

SRTP开源库libsrtp实战:从srtp.rar解压到API调用全解析

SRTP开源库libsrtp实战:从srtp.rar解压到API调用全解析 简介音视频通信中RTP流裸奔在公网上极易被窃听和篡改。SRTP安全实时传输协议作为RTP的安全扩展通过AES加密与消息认证保障实时媒体流的机密性和完整性。在众多实现中libsrtp凭借其稳定性、完整API和WebRTC生态的广泛采用成为SRTP开源库的事实标准。无论是处理srtp.rar压缩包中的r_srtp版本还是对接DTLS-SRTP密钥协商工程师都需要掌握从源码编译、Policy配置到srtp_protect/unprotect调用链路的完整流程。本文面向VoIP、WebRTC网关及安防流媒体开发者解析libsrtp的选型要点、编译参数、核心API握手逻辑及常见auth_fail排查技巧帮助你在RTP层快速构建安全传输能力。 做音视频通话、WebRTC网关或者流媒体加密传输的同学应该都见过一个叫srtp.rar的东西。文件名里经常还混着srtp open、r_srtp、开源库这些关键词看着挺懵但点破了就一句话这是一份SRTP开源库的源码包。SRTP是Secure Real-Time Transport Protocol解决的就是RTP音视频流在网络上裸奔的问题。这篇文章我就围绕这份srtp.rar把SRTP是什么、开源库怎么选、拿到压缩包后怎么解压编译、核心API怎么调用、踩过的坑怎么排查从头到尾走一遍。适合正在做WebRTC、VoIP、视频会议、安防流媒体或者想在RTP层加一层加密但还没搞清楚库怎么用的朋友。没有那么多协议术语轰炸尽量用干活的时候最常见的情形来讲。1. SRTP是什么为什么音视频项目绕不开它1.1 从RTP到SRTP协议栈里的那一层加密传统RTP协议本身没有任何安全机制。你抓包抓到一段RTP流直接用Wireshark就能看到载荷内容如果传的是G.711语音或者未加密的H.264别人把包存下来就能还原成声音和画面。这在公网链路、跨运营商传输、SIP外呼接运营商网关这些场景下是很要命的事。SRTP就是在RTP头部后面做了一层加密处理同时对RTP头部的关键字段和载荷做消息认证防篡改、防重放。它的封装格式和RTP基本兼容原来的RTP头、扩展头、SSRC、序列号这些字段都还在只是载荷被加密了尾部追加了认证标签。所以SRTP不是替换RTP而是在RTP外面套了一层安全壳。真正干活的加密算法有两类AES-CMCounter Mode计数器模式和AES-GCMGalois/Counter Mode带认证的计数器模式。AES-CM是传统方案后面跟HMAC-SHA1做认证WebRTC里最常见的AES_CM_128_HMAC_SHA1_80就是它AES-GCM是后起之秀单算法同时完成加密和认证性能更好AEAD_AES_128_GCM、AEAD_AES_256_GCM都在这条线上。选库的时候如果支持GCM优先用GCM能少一层HMAC计算延迟也更低。1.2 DTLS-SRTP和密钥协商的现实关系SRTP本身只解决“数据加密”的问题不解决“密钥从哪来”的问题。实际项目里最常见的密钥协商方式是DTLS-SRTP。DTLS是UDP版的TLS先用DTLS握手建立起信任关系然后通过RFC 5764定义的方式从DTLS密钥材料里导出SRTP的主密钥和盐值。WebRTC里面SRTP的密钥基本都来自DTLS-SRTP协商。你会看到dtls这个热词和srtp高频绑在一起就是因为这两个本来就是一体的DTLS负责“我们俩都可信”SRTP负责“后续音视频包不可偷看”。如果不走DTLS也有MIKEY、ZRTP或者静态预置密钥但WebRTC生态和大多数开源网关都默认DTLS-SRTP。1.3 srtp.rar文件名到底透露了什么再看srtp.rar_srtp_srtp open_srtp r_srtp开源库这串词拆开看就清晰了。srtp.rar说明这是一份打包好、需要解压的源码压缩包。srtp open大概率来自两处一是老版libsrtp里创建会话上下文的函数叫srtp_open()二是“启用SRTP”这个动作在很多程序里被叫成“open srtp”。r_srtp这种命名一般是发布版release或者某个内部改造分支的代号不是官方标准也可能是别人在GitHub上fork后仓促命名的仓库。开源库说明这份压缩包最终对应的是某个开源的SRTP实现最主流的就是libsrtp。如果你是从某个网盘或者同事那里拿到的srtp.rar不要被文件名里的花里胡哨带偏第一件事永远是搞清楚里面是哪个SRTP实现、哪个版本。这决定了后面的编译选项和API写法。2. 开源SRTP库怎么选libsrtp为什么是默认答案2.1 主流SRTP实现盘点我见过有人在这个阶段纠结很久其实不用。目前能落到工程里用的就这几个实现维护方特点适用场景libsrtpCisco维护WebRTC参考实现纯CAPI稳定文档全绝大多数音视频、WebRTC网关OpenSSL内置SRTP支持OpenSSL不直接提供SRTP封装只提供DTLS-SRTP密钥导出和加密原语配合libsrtp或其他SRTP栈使用PJSIP的SRTP模块Teluu集成在PJSIP栈里用底层libsrtp或内部实现PJSUA/PJSIP业务GStreamer的srtp插件GStreamer基于libsrtp封装成GStreamer元素管道式流媒体处理除非你的项目本身就是PJSIP或者GStreamer体系否则从零接入SRTP直接选libsrtp。WebRTC里的SRTP栈基本就是libsrtpChrome、Firefox、各种网关都能对上。它提供完整的加密算法、认证算法、重放保护、密钥导出工具而且API分成两层一层是srtp_*会话层一层是crypto_*加密内核层日常用会话层就够了。2.2 libsrtp版本演进srtp_open到srtp_create网上很多老文章的代码片段里写的是srtp_open()现在的API文档里你几乎找不到这个函数名因为它在新版本里改名了。libsrtp 1.x时代创建会话上下文用的是srtp_create()和srtp_open()并存老接口叫srtp_open()。到libsrtp 2.x以后官方统一成两套srtp_create(srtp_ctx, policy)创建并初始化SRTP会话srtp_dealloc(srtp_ctx)释放会话srtp_protect(srtp_ctx, rtp_buffer, len)加密RTP包srtp_unprotect(srtp_ctx, rtp_buffer, len)解密并校验RTP包所以你在老代码里看到srtp_open特别正常但新项目一律用srtp_create。这也是srtp open这个词迷惑性最大的地方——它不一定是“打开SRTP功能”的意思可能只是API函数名。2.3 看到r_srtp这类命名时怎么判断收到r_srtp这种目录名时不要直接编译完就拿来集成。我的习惯是先做三件事看版本号打开源码根目录的configure.ac或CMakeLists.txt看SRTP_MAJOR_VERSION和SRTP_MINOR_VERSION。如果是2.x直接按新API写如果是1.x注意接口区别。看README和ChangeLog确认是不是官方标签打出来的release包还是被别人改过内部加密逻辑。搞过安防网关的人应该懂很多厂家在libsrtp上改过算法轮数或者密钥派生逻辑这种定制包一定要留着对应文档。看测试用例跑一下官方的test程序如果自带的rtpw_test能通过说明这包基本是完整的如果失败先说包有问题别急着怪自己代码。3. 拿到srtp.rar之后解压、认目录、编译一套走3.1 用rar解压软件解开压缩包文件名是srtp.rar第一步肯定是解压。这套在Linux开发机、Windows上都能处理。Windows下用WinRAR或者7-Zip就能解右键直接解开。Linux下如果没装rar工具先去装个unrarsudo apt install unrar # Debian/Ubuntu sudo yum install unrar # CentOS/RHEL 需要先开EPEL解压命令unrar x srtp.rar或者用7-Zip7z x srtp.rar解压后如果看到的是根目录下直接一堆.c、.h文件那说明打出来的包没包好建议统一建一个工作目录再放进去如果看到的是一个以libsrtp-2.x.x或者r_srtp命名的目录那直接进目录。这里要说一个热词里提到的“rar密码移除”。如果有人给你的srtp.rar是带密码的标准做法是找打包人索取密码或者让对方重新打成不带密码的包。网上那些所谓的“rar密码移除”工具我在实际项目里不建议碰——绝大多数是捆绑垃圾软件甚至木马而且能破解的密码强度本身就说明这个分发包来源不可信。安全第一宁可重新下载一份官方源码包。3.2 源码目录结构和核心文件解压完不要急着编译先花五分钟把目录结构看一遍。以libsrtp 2.x为例子libsrtp/ ├── include/ │ ├── srtp.h # 主头文件所有API都在这里 │ └── crypto_math.h # 加密数学工具函数 ├── crypto/ │ ├── cipher/ # AES等加密算法实现 │ ├── hash/ # HMAC-SHA1等认证算法 │ └── kernel/ # 加密内核配置 ├── src/ │ ├── srtp.c # 核心SRTP逻辑 │ ├── rtp.c # RTP包解析封装 │ └── dtls_srtp.c # DTLS-SRTP密钥导出实现 ├── test/ │ ├── rtpw_test.c # 官方测试程序 │ └── srtp_driver.c # 驱动级测试 ├── configure.ac ├── CMakeLists.txt └── Makefile.in对业务代码最关键的只有两个include/srtp.h和src/srtp.c。前者是头文件后者是核心实现。dtls_srtp.c在做WebRTC对接时很有用密钥导出那一截就在这。3.3 configure编译参数与常见选项libsrtp的构建有两种方式传统的是autotools新一点的也支持CMake。我日常用autotools多一点编译命令非常稳定./configure --enable-openssl --prefix/usr/local make -j$(nproc) sudo make install--enable-openssl这个参数很重要。它让libsrtp使用OpenSSL的加密函数库而不是自带的那个纯C实现。自带实现在AES-CM场景下还能用但性能一般而且在GCM模式下没优势。实际生产环境链接系统OpenSSL是更稳的选择还能借助硬件加速。如果要用AES-GCM系列算法老版本需要显式开./configure --enable-openssl --enable-gcm --prefix/usr/local新版默认就把GCM编进来了但我还是习惯显式加上防止版本行为不一致。编译完以后检查一下生成的目标文件确认有libsrtp2.so或.a同时看一下pkg-config能不能正常找到pkg-config --libs --cflags libsrtp2如果你的项目用CMake也可以这样cmake -B build -DENABLE_OPENSSLON cmake --build build cmake --install build3.4 交叉编译到嵌入式平台做音视频终端、IPC、边缘网关时免不了交叉编译。libsrtp作为纯C库交叉编译难度不大核心就是给configure传对工具链参数。./configure \ --hostaarch64-linux-gnu \ --enable-openssl \ --with-openssl-dir/your/sdk/usr \ --prefix/usr/local \ CCaarch64-linux-gnu-gcc \ CXXaarch64-linux-gnu-g make -j$(nproc) make install DESTDIR/your/sysroot注意点只有两个--with-openssl-dir一定要指到交叉编译环境里的OpenSSL目录不能指到宿主机。不然链接阶段会出现“架构不匹配”的报错。如果最终产物要打包成静态库给第三方加一个--with-pic位置无关代码对后续打进so还是可执行文件都有好处。4. 核心API实操从srtp_init到加解密完整链路4.1 初始化与Policy配置几乎所有使用libsrtp的代码第一步都是#include srtp.h srtp_init();SRTP_init在libsrtp 2.x里是个宏或实际函数用于初始化库内部状态、加密内核、随机数源。如果你没有调用它就直接srtp_create会返回srtp_err_status_init这是新手最常见的错误。接下来配置policy。policy是整个SRTP最核心的数据结构它决定了用什么加密算法、用什么认证算法、密钥是什么、SSRC行为、重放窗口大小等。一个典型的RTP加密配置长这样srtp_policy_t policy; srtp_t srtp_ctx; srtp_err_status_t err; /* 1. 清零避免脏数据 */ memset(policy, 0, sizeof(policy)); /* 2. 选择加密和认证策略这里用最常见的AES-CM-128 HMAC-SHA1-80 */ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(policy.rtp); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(policy.rtcp); /* 3. 设置随机派生的密钥 */ uint8_t key[30]; // 16字节key 14字节salt rand_bytes(key, sizeof(key)); // 真实项目里这个key来自DTLS-SRTP导出 /* 4. 设置SSRC行为 */ policy.ssrc.type ssrc_any_outbound; // 出向使用具体SSRC或者inbound用any policy.ssrc.value 0; // ssrc_any_outbound时可以为0 /* 5. 重放窗口和重复发送开关 */ policy.window_size 128; policy.allow_repeat_tx 1; // 允许重传相同包比如DTMF重复包 /* 6. key和链表指针 */ policy.key key; policy.next NULL; /* 7. 创建会话 */ err srtp_create(srtp_ctx, policy); if (err ! srtp_err_status_ok) { printf(srtp_create failed: %d\n, err); }如果选择GCM把第二步换成srtp_crypto_policy_set_aes_gcm_128_16_auth(policy.rtp); srtp_crypto_policy_set_aes_gcm_128_16_auth(policy.rtcp);GCM模式下的密钥长度也会变化AES-128-GCM是16字节key 12字节salt所以key缓冲区大小是28字节。4.2 srtp_protect和srtp_unprotect实战密钥协商完成后发包前调用srtp_protect收包后调用srtp_unprotect这是SRTP最核心的两个动作。看一个完整的发送端例子uint8_t rtp_packet[2048]; size_t len; /* 构造一个RTP包在rtp_packet里填好RTP头然后拷贝音视频载荷 */ /* ... 省略RTP头填充过程 ... */ len rtp_payload_len rtp_header_len; err srtp_protect(srtp_ctx, rtp_packet, len); if (err ! srtp_err_status_ok) { printf(srtp_protect failed, err%d\n, err); return; } /* 注意len在srtp_protect之后会变成SRTP包的完整长度包含认证标签 */ sendto(sockfd, rtp_packet, len, 0, (struct sockaddr*)dest, sizeof(dest));接收端的逻辑基本镜像uint8_t recv_buffer[2048]; size_t recv_len recvfrom(sockfd, recv_buffer, sizeof(recv_buffer), 0, ...); err srtp_unprotect(srtp_ctx, recv_buffer, recv_len); if (err ! srtp_err_status_ok) { /* auth_fail表示密钥不匹配或数据被篡改 */ printf(srtp_unprotect failed, err%d\n, err); return; } /* recv_len在unprotect后是原始RTP包长度继续按RTP解析 */这里有两个实际工程里容易踩的坑第一len在srtp_protect之后会变大。HMAC-SHA1-80会追加10字节认证标签GCM-128-16会追加16字节认证标签。如果缓冲区大小不够尾部追加就会溢出。所以我喊一句发送缓冲区和接收缓冲区都至少比最大RTP包多预留32字节。第二srtp_protect是在原地把RTP头、扩展头里的内容做了处理SSRC、序列号这些字段可能被改写所以不要在调用后还拿原始RTP包当参照。正确做法是先把RTP包留着真正要发的时候再拷贝到发送缓冲区里调srtp_protect。4.3 从DTLS握手导出SRTP密钥真实WebRTC或SIP over DTLS场景里SRTP密钥不是自己随机生成的而是从DTLS握手结果里导出的。RFC 5764规定了一个术语“EXTRACTOR-dtls_srtp”标签握手完成后从TLS主密钥中提取出密钥材料。如果用libsrtp提供的API#include srtp.h #define DTLS_SRTP_KEYING_MATERIAL_LEN 60 // AES_CM_128_HMAC_SHA1_802*(1614)60 uint8_t keying_material[60]; srtp_dtls_keying_material_t dtls_keys; srtp_err_status_t err; /* 经过DTLS握手后从TLS栈获得keying_material例如在OpenSSL里通过 SSL_export_keying_material(material, 60, EXTRACTOR-dtls_srtp, ...) */ /* ... */ err srtp_dtls_keying_material_export( keying_material, NULL, /* salt传NULL表示不额外加盐 */ dtls_keys); if (err ! srtp_err_status_ok) { printf(srtp_dtls_keying_material_export failed\n); return; } /* dtls_keys.master_key现在指向16字节主密钥 dtls_keys.master_salt指向14字节salt */ memcpy(key_buffer, dtls_keys.master_key, 16); memcpy(key_buffer 16, dtls_keys.master_salt, 14);如果你不想依赖libsrtp这个导出函数也可以自己拆。规则很简单客户端取keying_material的前30字节服务端取后30字节前16字节是加密key后14字节是salt组合起来填入policy.key。很多auth_fail问题都是这段取反了后面排查部分会再提。4.4 多流和RTCP的扩展玩法一个会话里处理多个媒体流是WebRTC的常态。音频一个SSRC视频一个SSRC甚至屏幕共享再来一个SSRC。libsrtp的policy链表机制支持多流一个流一个policy节点通过policy.next串起来一次srtp_create就能创建多个加密上下文。代码上就是把上面那段policy配置复制成多份各自设置不同的ssrc.value然后用next连起来。RTCP的加密走的是另一套policy字段policy.rtcp。虽然RTCP包我们一般不加密很多场景只做认证但标准要求必须设置。如果不想加密RTCP可以用srtp_crypto_policy_set_null_cipher_hmac_sha1_80()这种null cipher策略只认证不加密这样可以省一点CPU同时保证控制报文可信。5. 常见问题与排查技巧实录5.1 压缩包层面的问题rar解压损坏下载过程中文件损坏很常见尤其是一个几十MB的srtp.rar。用unrar t srtp.rar测试压缩包完整性如果提示损坏直接重新下载不要尝试修复强行修复出来的源码可能在编译时出现诡异报错。带密码的压缩包优先找来源方要密码。用破解工具不靠谱而且你无法验证破解后的代码有没有被替换过。安全项目里这是铁律。打包方式混乱有的包解开后不是标准目录而是直接把一堆.c.h文件平铺在根目录。这种多半是Windows上右键压缩的产物不影响编译但建议自己重建目录结构再动手。5.2 srtp_create/open失败这类init问题错误码场景处理方式srtp_err_status_init没有先调srtp_init()程序最开始调用一次srtp_init()srtp_err_status_failpolicy参数非法、key为空检查policy.key、policy.cipher_type、policy.auth_type是否都已经设置srtp_err_status_no_ctx传了NULL的srtp_t*检查srtp_create第一个参数是否指向有效的srtp_t变量srtp_err_status_alloc_fail内存不足或者栈碎片严重嵌入式环境尤其常见检查堆空间policy没清零是非常隐蔽的错误。结构体里有不少保留字段和内部标志如果不memset清零里面有随机值srtp_create在解析时就会出现莫名其妙的问题报错都不是固定的。这就是我写policy配置时永远先memset的原因。5.3 加解密失败与auth_failauth_fail是SRTP排查里最高频的错误。它明确说明认证标签不对也就是对端拿到的数据和我方发的数据不一致。大部分原因按照出现频率排序密钥材料分裂错误DTLS导出的60字节客户端和服务端各用一半。两边如果都取了前30字节必然auth_fail。代码里用dtls_keys.master_key直接建policy的一般没事手工拆的人最容易错。SSRC没有对齐SRTP的加密IV依赖SSRC。发送端用ssrc_specific接收端用了ssrc_any_outbound且实际流的SSRC对不上解密出来就是乱码或者auth_fail。调试时可以把两端的SSRC打印出来比对。丢包顺序问题SRTP重放保护默认窗口是128如果网络丢包超过窗口范围老包会被当重放包丢弃然后大量auth_fail。这种情况把policy.window_size调大比如1024同时检查UDP链路为什么要丢这么多。RTP扩展头没有协商好如果你的RTP包带扩展头SRTP处理扩展头时两边对扩展头的理解不一致认证标签就会对不上。这个在WebRTC接入SIP网关时容易遇到先关掉RTP扩展头试一下。我自己的排查习惯是先打印错误码再有条件的话在srtp_unprotect失败时同时打印SSRC、序列号、包长和认证标签的前4字节四个值一对比80%的auth_fail都能定位。5.4 重放保护、丢包与调优建议SRTP的重放保护看似简单实际对音视频实时性影响很大。默认窗口大小是128意思是SRTP允许序列号回退最多128个包再丢。对于正常网络这够用但在WiFi切换、弱网传输、FEC重传的时候包可能延后比较久才到超过窗口就会被丢弃。我的建议是局域网内或者强调实时性的场景窗口不用太大128够用。跨公网、弱网、有FEC重传机制的场景窗口调到512或者1024配合对端的重发策略。千万不要为了排查方便把窗口设成0那等于完全关闭重放保护生产环境裸奔认证和重放防御全失效。另外GCM模式下包长度和CPU占用都比CBC/HMAC模式低如果两端都是libsrtp 2.x且启用了OpenSSL尽量用AEAD_AES_128_GCM。我用同样的机器对比过同样10000包AES-CMHMAC和AES-128-GCM的加解密耗时大概能差出25%~35%在移动端上这个差距更明显。再补充一个和MTU相关的小经验。SRTP会给每个RTP包增加认证标签长度HMAC-SHA1-80是10字节GCM是16字节。如果你原来每个RTP包是1400字节UDP载荷加密后变成1410/1416字节。在运营商或者路由器MTU比较紧的网络里超一点就会分片或者被丢。所以做媒体层时给RTP载荷留一点裕量宁可少放几个字节的音频帧也要保证加密后UDP包不超过MTU。我在实际项目里折腾libsrtp最深的体会是这库本身很简洁但它的坑全在周边密钥材料从哪来、policy配没配对、缓冲区留没留够。很多人卡在auth_fail卡两天最后发现就是客户端服务端取反了half材料或者RTP扩展头没对齐。如果你也是刚拿到一份srtp.rar准备集成建议按这个顺序走先解压确认版本再跑通官方test把基础环境验证好接着用自己的DTLS栈把密钥导出到libsrtp最后再接入你的RTP收发循环。每一步单独验证会比自己一口气写完再调省非常多时间。最后分享一个调试小技巧在把srtp_protect接进正式媒体链路之前先用一个简单的UDP socket把音频包发到对端看看能不能正常srtp_unprotect还原。这一步能隔离出问题是出在RTP构造、DTLS协商还是SRTP库本身别把三件事混在一起查不然一个auth_fail能把人查崩溃。本文还有配套的精品资源点击获取
返回列表