加密不是一个开关,而是一串先后关系:文件先变成打不开的东西,打不开的东西先要过设备这一关,设备这一关要靠服务端点头。这条链上每一环都能被绕过,问题只在于绕过它需要多少成本。下面按代码里的真实顺序讲。
01一条视频只有三种被搬走的方式
讨论视频防盗,最容易犯的错是把三件不同的事混成一件。它们挡的人不一样,能被破的方式也不一样。
| 层 | 做什么 | 挡住谁 | 怎么被破 |
|---|---|---|---|
| 文件层 | 把视频装进一个只有专用播放器认识的加密容器 | 拿到文件直接双击、拖进网盘、当附件转发的人 | 拿到密钥,或拿到会算密钥的播放器 |
| 设备层 | 激活码只认一台机器,文件拷走也没用 | 把文件拷给别人、把激活码转手的人 | 整台电脑一起搬走,或远程控制别人的桌面 |
| 在线层 | 每次(或每七天)向服务端确认一次还能不能播 | 已被吊销、已过期、被判定异常使用的授权 | 断网使用缓存、伪造校验结果 |
行业里被高估的总是第一层。AES 密文当然解不开,但加解密的两端都在同一台机器上——播放器能解,这台机器就一定能解。所以真正决定方案强弱的不是算法,而是密钥在什么条件下才被算出来、算出来之后谁允许它被使用。
02文件层:一个 52 字节的文件头
我们的加密工具不产出 mp4,产出的是一个后缀为 .dat 的容器,内部格式代号 SLF1。整个容器由三段组成:一个固定 52 字节的头、一段加密的索引、一段接一段的文件数据。
| 区段 | 内容 | 为什么这样放 |
|---|---|---|
| 头 0–4 | 魔数 SLF1 |
一眼认出这是新版容器,不是随便改后缀的 mp4 |
| 头 8–40 | 32 字节 HMAC-SHA256 | 打开文件的第一关:算不出这个值,说明不是这台机器 |
| 头 40–52 | 文件数、索引长度、数据区长度 | 播放器不开整包也能定位到要播的那一段 |
| 索引区 | AES-GCM 加密的路径表 | 连"里面有几节课、每节叫什么"都不暴露 |
| 数据区 | 每个文件一段 nonce + 密文 + 认证标签 | 逐文件独立加密,播第 3 节不用解开第 1 节 |
几个具体的工程取舍值得说清:
- 用 GCM,不用 CBC。每个文件独立一个 12 字节 nonce,密文后面跟 16 字节认证标签。GCM 自带完整性校验:密文被改过一个字节,解密就直接失败,而不是"解出一段坏画面"。课程视频最怕的不是被读,是被悄悄替换或拼接,认证标签挡的是这一类。
- 索引也加密。很多"视频加密"只加密内容、文件名和目录结构裸着放。那等于把课程大纲免费送出去。这里索引区跟数据区用同一把密钥加密,播放器必须先过机器码校验才能解出"里面有什么"。
- 按偏移解密。播放第 N 个文件时,只把那一段读进内存解密,不整包常驻。一个 800MB 的课程包,播放时内存占用只跟"当前这一节"有关。这条设计是为了让客户在 8G 内存的办公机上也能播完。
- 支持的格式有边界。只处理 mp4、swf、avi、mkv、mov、wmv、flv、exe 八类,其他格式直接拒绝并提示。EXE 特殊一点:如果它是一个 Flash 打包程序,工具会把里面嵌的 SWF 抽出来,当作 SWF 加密——这样老的 Flash 课件也能进同一套授权。
03密钥是算出来的,不是发下来的
大多数加密方案的密钥由服务端生成、在授权通过后下发。我们没有这么做:密钥是客户端自己算的——把本机机器码做一次 SHA256,取前 32 字节,就是 AES-256 的密钥。
这么设计有两个好处。第一,服务端从头到尾不持有密钥,也就不存在"密钥库被拖库"这个风险;第二,链路上没有"下发密钥"这个环节,抓包抓不到密钥。但它同时也带来一个必须承认的代价:只要有人知道目标机器码,他就能自己算出这把密钥。加密强度在这里不解决任何问题,因为问题的答案根本不在密码学里,而在"机器码是秘密吗"。
这就是为什么整条链路的重心不在加密那一层,而在后面两道:机器码只属于那台机器,而服务端决定要不要放行。用一句更直白的话说——文件层负责让门外的人打不开,授权层负责让门里的人也不一定打得开。
如果只做"文件加密",把密钥直接写进文件头,那它就退化成一个改了后缀的压缩包:任何人拿到文件都能顺手解开。真正的分水岭是:密钥是不是由"某个只属于这台机器的信息"派生出来的。
这也是我们放弃"随机密钥 + 服务端下发"路线的原因04设备层:机器码是一个哈希,不是一个串号
客户在自己电脑上跑一次机器码工具,得到 32 位十六进制。这个值的生成过程只有两步:
- 读第一块磁盘的序列号。优先查 NVMe / SATA / SAS 接口的物理磁盘;查不到就退到"所有磁盘里的第一块";再查不到,最后退到 C 盘的卷序列号。三级回退,为的是虚拟机和某些品牌机也能取到一个值,而不是让客户卡在第一步。
- 做一次 MD5,输出 32 位小写十六进制。这里用 MD5 不是为了"安全"——它早就不该用于任何安全场景。用它只有一个目的:不把磁盘串号原样发出去。内容方和服务端拿到的是哈希值,不是客户的硬件编号。
这套做法有两个真实代价,我们选择把它写在明面上:
机器码这一层的能力与代价
- 换电脑、拷贝文件、重装播放器,都不影响"这个激活码只认原来那台机器"。
- 磁盘串号只以哈希形式出现在授权记录里,服务端数据库被看到也不等于客户硬件信息泄露。
- 换硬盘就等于换机器。机器码只看磁盘序列号,不看 CPU、主板、网卡,所以硬盘一换,原激活码立刻失效——这是客服最常见的一类问题。
- 重装系统在多数机器上不影响(物理磁盘序列号不变),但如果当初取的是 C 盘卷序列号这条回退路径,重装格式化就会变。
- 机器码本身是可以被"伪造环境"绕过的:虚拟机克隆、磁盘序列号可改的工具都真实存在。它挡的是普通用户,不是专门搞这个的人。
如果只从"防破解"的角度看,这几条都是缺点。但它们换来的是另一个更重要的东西:客户不需要理解加密。他只要打开一个工具、把一串字符发给内容方,剩下的事都在后台完成。任何需要客户"手动保护密钥"的方案,实际使用中都会失败。
05在线层:一次播放要过六道闸门
播放器每次决定"播不播"之前,会向服务端发一次请求,只带三个字段:激活码、机器码、播放器类型。服务端不查文件、不查密码,只查这条授权记录还能不能用。判定是一串有先后顺序的问题:
| 顺序 | 判定条件 | 不通过时返回 | 为什么放在这一位 |
|---|---|---|---|
| 1 | 激活码在库里存在吗 | 404「激活码无效或不存在」 | 先排除猜码,避免后面的信息被试探出来 |
| 2 | 授权状态是 active 吗 | 「激活码已停用」 | 管理端吊销后,这条必须先生效 |
| 3 | 机器码与记录一致吗 | 「激活码与当前设备不匹配」 | 设备不符时,讨论到期与否没有意义 |
| 4 | 到期时间还在将来吗 | 「激活码已过期」 | 时间比较以服务端为准,不看本机时钟 |
| 5 | 限量授权的次数还没用完吗 | 「激活码使用次数已用完(单次授权)」 | 次数是"用了就扣",只能放在放行前 |
| 6 | 授权类型匹配这个播放器吗 | 「激活码不适用于该播放器」 | 视频 / SWF / 加密工具三类是分开授权的 |
六道全过,返回的不只是"可以播",还带回到期时间、剩余次数这些字段,让播放器能提前提示"还剩 3 天到期",而不是等到某天打开突然黑屏。第 5 道要注意一个细节:限量授权在放行前就把次数扣掉了,不是"播完再扣"。这样即使客户端在播放中途被强杀,次数也已经用掉,不会出现"开一百次窗口却不扣次数"的口子。
还有一个我们踩过的坑:接口在"参数缺失"和"激活码不存在"这两种情况下,返回的HTTP 语义并不一样。前者是参数校验失败,后者是业务上明确"没有这张卡"。客户端必须两种都能正确翻译成中文提示——只处理一种,用户就会看到"服务器异常",然后打客服电话说"你们的服务器坏了"。
06断网也要能播:七天窗口与"单次卡不缓存"
在线校验有一个躲不开的副作用:客户的电脑可能没有网络,或者授权服务器在重启。如果每次播放都必须联网成功,那这套系统在客户的第一次断网时就会变成"废品"。所以播放器里有一套本地缓存,规则写得很死:
| 授权形态 | 是否会写本地缓存 | 结果 |
|---|---|---|
| 年卡 / 终身卡(不限次) | 会 | 校验成功后缓存 7 天,这 7 天内断网也能播;超过 7 天强制重新联网 |
| 单次卡 / 限量卡 | 不会 | 每次打开都必须联网校验,用一次扣一次,不给自己留离线空间 |
| 已被吊销 / 已过期 | 不写 | 下一次联网就会被拦;最长滞后 7 天(见下文) |
缓存文件放在用户配置目录下,播放器与加密工具各用各的文件,互不覆盖——否则"用加密工具校验过一次",就会让播放器的授权类型也被改写,出现"明明买的是视频卡,却打不开视频"这种诡异故障。缓存命中的条件有五个,缺一不可:同一个激活码、同一台机器、同一种播放器类型、授权还没到期、缓存写入时间在 7 天内。
七天这个数字是权衡出来的,不是拍脑袋。缓存越久,客户断网体验越好,但吊销生效越慢——你今天把一张卡作废,对方最晚还能再播七天。反过来,缓存太短,客户每次出差都要重新联网。我们选择七天,是因为课程场景里"一周内至少连一次网"是合理预期;换成考试系统或按次付费,这个数字必须改成 0。
还有一个很小的细节,但它是客服电话的主要来源之一:激活码在粘贴时经常带空格或换行。所以客户端在校验前会把所有空白字符剔掉再比对——不是"容错设计",而是真实场景:客户是从微信聊天框里复制那串码的。
07播放器为什么有 125 MB
客户第一次看到安装包体积,通常会问一句"怎么这么大"。这不是打包没优化,而是因为它自带了一个完整的播放内核。
| 程序 | 体积 | 大头在哪 |
|---|---|---|
| 视频播放器 | 131,350,528 B 约 125.3 MB | 内嵌 libmpv 解码内核 120,326,144 B(约 114.7 MB) |
| SWF 播放器 | 37,547,008 B 约 35.8 MB | Ruffle 的两份 WebAssembly 运行时,各约 14.3 MB |
| 加密工具 | 8,798,208 B 约 8.4 MB | 纯 Go 逻辑,没有解码器 |
| 机器码读取工具 | 8,636,928 B 约 8.2 MB | 同上,图形界面占了大部分 |
为什么不用系统自带的播放器、或者网页播放器?两个原因都是真实约束:
- 系统播放器不让"喂数据"。Windows 自带组件基本只接受文件路径,要播解密后的内容,就必须先落地成文件。而自带的 libmpv 支持受控加载,我们可以自己决定开哪个文件、什么时候关。这一条直接决定了"能不能在播放结束后立刻删掉临时文件"。
- 网页播放器会把明文交出去。HLS 或 MSE 方案需要把解密后的分片塞进浏览器的解码管线,而这个管线可以被浏览器插件、屏幕录制、开发者工具碰到。原生内核至少让明文停留在我们自己的进程里。
代价是安装包大、首次启动慢一点:内核 DLL 第一次运行时会从 exe 里释放到临时目录,之后再启动就直接复用,不再重复写盘。这一步用"先写临时文件再原子改名"的方式做,避免同时开两个播放器把 DLL 写坏。
08这套方案防不住的三件事
任何做内容保护的人都会被问到同一个问题:"能不能做到绝对防住?"答案是不能,而且原因很具体。下面三条不是理论推演,是我们自己代码里就存在的路径。
一、明文会短暂落在磁盘上
播放器解出某一节的内容后,要交给播放内核。内核接受的是文件路径,所以明文必须先写成一个临时文件,播完再删掉。这意味着:在播放的那段时间里,一节视频的完整明文就躺在系统的临时目录里,文件名是一串随机串。正常关闭播放器会立刻清除,启动时也会清扫上次崩溃留下的残留——但如果进程被强杀、或者有人守在那个目录里复制,明文就会被拿走。
想把这一段补上,只有两条路:改成内存流加载(需要改造播放内核的加载方式,代价是兼容性下降),或者用更底层的文件系统过滤驱动(代价是装驱动、要签名、和杀毒软件打架)。我们选的是"播完就删 + 启动清扫",而不是"假装不存在这个窗口"。这一点必须在售前讲清楚:它防的是外部分发,不是本机取证。
二、屏幕录制拦不住
播放器里确实有键盘钩子,但它的用途是"全屏时按 Esc 退出全屏",不是拦截图键。系统级的 PrintScreen、Win+Shift+S、第三方录屏软件、外接采集卡、手机对着屏幕拍——这些都不经过播放器的进程,技术上无法从应用层阻断。
行业里对这个问题的标准答案不是"加更多钩子",而是水印与追责:在画面上叠一层跟着播放器走的用户标识(机器码后四位、手机号尾号),让流出的画面本身带出处。这件事我们目前没有做进播放器,是明确的待补项,不是已具备的能力。
三、整台机器搬走,等于没换机器
设备绑定认的是"这台电脑",不是"这个人"。所以两种情形会绕过它:把整机(或者整机镜像)搬到别处继续用;通过远程桌面让别人在自己的机器码上播。前者等于物理层面的拷贝,后者在授权系统眼里完全是合法设备。
另外,前面说过密钥由机器码派生。这条链有一个直接推论:如果攻击者同时拿到密文文件和目标机器的机器码,他不需要联网、不需要服务端,就能在本地把内容解出来。我们能做的是让机器码不轻易外流(授权记录里只存哈希),以及让"播得动"这件事依赖服务端——但拦不住一个已经拿到这两样东西的人。
把能力边界一次说完
- 能做到:文件离开专用播放器打不开;激活码换机器不可用;过期/吊销/次数用尽能被拦;到期与剩余次数可提前提示。
- 能做到:内容方不用懂加密,拿到机器码就能出加密包;学员不用装环境,双击就能看。
- 做不到:阻止本机在播放期间取走明文;阻止屏幕录制与拍摄;阻止整机搬运与远程桌面;阻止已掌握机器码的人离线解密。
- 做不到:实时吊销。最坏情况有一周窗口(缓存机制决定)。
09我们走过的弯路:从异或到 AES-GCM
这套工具的容器头里有一个魔数 SLF1,它不是装饰——它标记的是"这一版换了加密算法"。第一代版本用的是循环密钥异或:把密钥按字节循环,跟数据逐字节 XOR。文件看起来是一片乱码,双击打不开,表面上"加密了"。但它在密码学上几乎等于没加密:
| 版本 | 算法 | 真实强度 |
|---|---|---|
| 第一代 | 循环密钥异或 | 已知明文即可还原密钥;视频这种高度可预测的数据,统计特征原样保留。属于"看起来乱码",不属于加密。 |
| 当前版 | AES-256-GCM | 每个文件独立 nonce,密文带认证标签。改一个字节就解不开,也无法通过已知片段反推密钥。 |
为什么第一代会用异或?因为它简单、快、不用引入加密库,而且在"防的是随手转发"的场景里,它确实骗过了第一波人。这是很多小工具的真实起点:先解决"双击打不开",再解决"专业工具打不开"。我们把旧算法保留在代码里,只用于读历史文件——已经发出去的旧包不能因为升级而突然打不开,这在客户那里是不可接受的。
这条弯路值得写出来,因为它解释了一个判断标准:问一个卖加密工具的人"你用的什么算法",如果对方答不上来,或者答"我们自己研发的混淆算法",那大概率还停在第一代。真正可验证的答案只有具体算法名、具体模式、具体密钥来源。
10什么情况下值得上这套东西
先把场景分清楚,比先看价格有用。下面五种情况,判断标准不是"内容值多少钱",而是"内容以什么形式离开你的手"。
需要加密的几种情形
- 课程以文件形式交付(U 盘、网盘链接、微信发送),学员可以把文件转给别人——这是最典型的适用场景。
- 内容单价较高,一份被转卖就抵掉好几个新客户。
- 需要"到期"这个能力:按年收费、按次收费、试用三天,都需要授权能在服务端被改。
- 视频只在平台内播放(公众号、视频号、第三方课堂)。这些平台自带观看权限,再加一层文件加密没有意义。
- 学员群体技术能力强、并且有明确的破解动机。加密能把成本抬高,但抬不到"拦得住"的程度。
价格按激活码计,一个激活码对应一条授权记录(一台设备)。官网现在挂的是这五档:
| 档位 | 价格 | 授权期 | 激活码 | 适合谁 |
|---|---|---|---|---|
| 单次加密授权卡 | ¥498 | 365 天 | 1 个 | 一次性使用,激活码用一次即作废;适合按次交付 |
| 基础版(年授权) | ¥998 | 365 天 | 100 个 | 小班课、几十人规模的固定学员 |
| 专业版(年授权) | ¥2,388 | 365 天 | 不限(支持 3 台) | 持续招生的课程方,学员增长不用再补卡 |
| 一次性加密方案 | ¥3,680 | 3,650 天 | 不限 | 一次买断、长期做内容的人 |
| OEM 定制 | ¥9,800 | 按需定制 | 按需定制 | 要换成自己品牌、走自己授权流程的机构 |
这些数字由服务端统一下发,页面上展示的和下单时计价的是同一份数据——不会出现"页面写 998、付款时变成别的数"的情况。价格与授权细则以产权保护页的实时展示为准,这篇只做工程说明。
本文事实来源
客户端实现:产权保护客户端工程(版本 2.0.1,2026-09-14)中的 crypto、machinecode、license、packer、mpvplayer 五个内部包,以及播放器、SWF 播放器、加密工具、机器码读取工具四个命令行程序。
容器格式:.dat 文件 52 字节头结构、索引区与数据区分段布局、支持的八种输入格式,均取自 packer 包源码。
授权判定:服务端授权模块的六条判定顺序与返回文案;五档价格取自授权模块的套餐定义(CRYBT_PLANS)。
体积数据:线上安装包目录实测字节数(播放器 131,350,528 B、SWF 播放器 37,547,008 B、加密工具 8,798,208 B、机器码读取工具 8,636,928 B);内嵌内核 libmpv-2.dll 为 120,326,144 B。
说明:本篇不包含播放器实机截图。文中两个示意图按源码结构绘制,不是产品界面。