ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

一机一码+视频加密:Python构建桌面软件离线授权与防复制体系

一机一码+视频加密:Python构建桌面软件离线授权与防复制体系 简介一套面向软件开发者的“一机一码”注册机生成与EXE、视频文件加密解决方案专注解决软件授权绑定机器码、防止文件被破解复制等问题适合需要给程序增加license保护或加密视频资源的开发者参考。压缩包共111个文件容量仅4.54MB类型覆盖C、Delphi、VB、汇编等多种语言源码cpp/pas/bas/asm配合工程文件vcproj/dpr/vbp/sln、已编译的exe/dll/lib、资源定义文件、chm帮助文档及批处理脚本结构清晰便于检索。包含FASM汇编示例、Potato等多语言示例和自动编译脚本可对照源码理解注册机生成、机器码采集绑定以及视频文件加密的完整流程既可直接运行体验效果也能修改复用其核心代码快速搭建自己的授权体系。已有3101人学习下载适合对软件加密保护、防逆向和授权机制感兴趣的中高级开发者。 上个月我在闲鱼刷到自己的付费课程被二道贩子打包转卖附带一个“破解版播放器”评论区还有人问“老板能不能离线看”。那个播放器是我两年前写的第一版工具当时只加了个密码校验被扒下来也就是几分钟的事。那晚我没急着去吵架而是在笔记本上列了一份需求清单一机一码绑定硬件、视频文件加密、授权码自动生成这次要做就做一整套还都得自己掌控。这篇文章会按我落地这套方案的顺序来讲包括机器码怎么取最靠谱、注册机该用什么签名算法才不容易被逆向、Python 脚本到底怎么打包成 EXE 才少踩坑以及视频加密和授权验证怎么在一个播放器里联动。适合被盗版问题困扰的独立开发者、准备给客户做授权交付的桌面软件作者也适合想入门软件授权体系设计的同学。1. 一机一码的授权链路先搞清楚软件是被谁“抄走”的先说一个重要判断一机一码防的主要不是逆向高手而是“复制粘贴”。大部分普通用户不会反编译你的 EXE他们只是觉得“这软件挺好传给同事一起用”“这课程不错发给闺蜜看看”。一对多的复制靠一个密码框根本拦不住。一机一码要做的事情是把每一个合法的授权绑定到一台具体的设备上让复制行为在逻辑上失效。1.1 授权链路里的三个角色一套典型的一机一码体系里有三个角色客户端程序跑在用户电脑上负责生成机器码、接收注册码、验证授权状态。注册机跑在软件作者自己电脑上的专用工具输入机器码后输出合法的注册码。用户把客户端生成的机器码发给你再从你这里拿到注册码输入回软件。关键点在于注册机永远不能出现在用户手里也不能混进客户端安装包。它是作者私有的“发牌器”一发一个准并且发的每一张牌都只在对应的那台机器上有效。1.2 完整的激活时序在动手写代码之前我先把流程画得非常具体用户首次运行客户端程序采集硬件指纹经过哈希处理后生成一串机器码显示在“关于/激活”窗口里。用户把这串机器码通过微信、邮件或表单发给你。你在自己电脑的注册机界面粘贴机器码再选一个授权等级和过期时间点击生成得到注册码。注册码是一段经过签名的长字符串用户复制到客户端客户端用内置公钥验证签名。验证通过后客户端在本机保存一个授权文件记录机器码、有效期、等级。后续每次启动都校验一次。这个链路里有三个技术点必须想清楚机器码会不会变、注册码能不能被伪造、授权文件能不能被复制到别的电脑。后面三节我就是逐个解决这三个问题的。2. 机器码采集每台电脑的“身份证”怎么取才不翻车机器码是整个体系的地基。如果两台不同电脑生成了同样的机器码那你的注册码就可以全国通用如果同一台电脑每次生成的结果都不一样用户会被你气死。稳是第一原则。2.1 哪些硬件信息适合做指纹我试过很多组合最后稳定使用的候选信息有这么几类CPU 序列号ProcessorId主板序列号BaseBoard SerialNumber第一块物理磁盘的序列号DiskDrive SerialNumberMAC 地址另外一个值得谨慎使用的是 Windows 安装时生成的 MachineGuid 注册表项它位于HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography。这个值在系统未重装的情况下是稳定的而且每台机器几乎必然不同坏处是一旦重装系统就会变。我的处理方式是把 MachineGuid 作为辅助字段加入哈希而不是唯一依赖。注意网卡 MAC 在休眠、插拔 USB 网卡、启用虚拟网卡时都可能变化所以不能只用它一个。CPU 和主板序列号在生产环境里确实有极少数返回空值的情况我补了空值默认策略。2.2 机器码生成的 Python 实现我最终用的是 wmic 加保底方案。Python 环境是 3.10 pywin32核心代码如下import hashlib import subprocess import uuid def _query_wmic(args): try: result subprocess.check_output( wmic args, shellTrue, stderrsubprocess.DEVNULL ).decode(utf-8, errorsignore).strip() lines [line.strip() for line in result.splitlines() if line.strip()] return lines[1].strip() if len(lines) 1 else UNKNOWN except Exception: return UNKNOWN def get_cpu_id(): return _query_wmic(cpu get processorid) def get_board_id(): return _query_wmic(baseboard get serialnumber) def get_disk_id(): return _query_wmic(diskdrive get serialnumber) def get_machine_guid(): try: import winreg key winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, rSOFTWARE\Microsoft\Cryptography) value, _ winreg.QueryValueEx(key, MachineGuid) return str(value).strip() except Exception: return UNKNOWN def generate_machine_code(): info [ get_cpu_id(), get_board_id(), get_disk_id(), get_machine_guid(), str(uuid.getnode()) ] raw |.join(info) return hashlib.sha256(raw.encode(utf-8)).hexdigest().upper()为什么不直接展示原始硬件信息而是做 SHA256原因很简单客户端机器码如果直接等于“CPU序列号-主板序列号”一旦软件被反编译这些硬件信息就暴露了会加剧隐私风险。哈希之后就只是 64 位十六进制串用户也更容易复制不容易漏字符。2.3 机器码稳定性的实测结果我在三台机器做了跨周测试两台 Windows 10一台 Windows 11。结果如下测试项结果重启后重新生成全部一致拔掉外接 USB 网卡全部一致关闭/开启虚拟机网卡全部一致插入新的 U 盘全部一致重装系统后MachineGuid 变化若该项目进入哈希则结果变化这个结果让我决定MachineGuid 不起主决定作用但参与哈希有助于区分“两台配置完全一致的品牌机”。它们虽然 CPU/主板序列号不同但这个差异本应已经足够遇到极端情况MachineGuid 能兜底。3. 注册机核心设计RSA 签名让注册码不可伪造如果只把机器码存到本地文件里那人家直接复制整个授权文件到另一台电脑是不是就绕过了一机一码所以授权内容必须和机器码绑定而且不能让用户手工拼接篡改。3.1 为什么我不选择 HMAC 对称方案常见的错误做法是把机器码和一个固定密钥做 HMAC生成一段“签名”作为注册码客户端再用同一个密钥验证。这样做验证端和生成端拥有同一个密钥只要有人反编译客户端提取出密钥他就能写一个和你的注册机等效的生成脚本。这是对称方案在客户端分发场景里的致命伤。所以我选择了 RSA 非对称签名注册机持有私钥只在你自己的电脑上客户端只放公钥即使公钥被提取出来也做不了任何签名。这是整个体系防伪造的根基。3.2 注册码的数据结构一条注册码在我这里被组织成这样的结构base64( 机器码 | 过期日期 | 授权等级 | RSA签名 )签名前的内容是明文的签名是对内容哈希之后做的。为什么要带过期日期和授权等级因为我可以把“卖一年授权”和“永久授权”区分开并且把“标准版”和“专业版”都塞进这条码里。用户改注册码里的日期验签直接失败。3.3 注册机端的签名代码注册机不发布只在你自己的 Windows 电脑上跑可以用纯 Python 写个带界面的小工具。核心逻辑import base64 from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 # 生成密钥对只在初始化时执行一次 # key RSA.generate(2048) # private_pem key.export_key().decode() # public_pem key.publickey().export_key().decode() private_key RSA.import_key(open(private.pem).read()) def create_license(machine_code, expire_date, level): payload f{machine_code}|{expire_date}|{level} digest SHA256.new(payload.encode(utf-8)) signature pkcs1_15.new(private_key).sign(digest) token base64.b64encode(payload.encode(utf-8) b| signature) return token.decode(utf-8)这里有个容易踩的坑签名是二进制数据直接拼到字符串里会乱码。我先把 payload 编码成 UTF-8 bytes再拼上分隔符|和二进制签名最后整体做 base64。这样注册码就是一段可复制、可传输的 ASCII 文本。3.4 客户端验签代码客户端里放的是公钥验证流程是对注册码先解码再从右侧最后一个|处拆出签名和载荷import base64 from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 public_key RSA.import_key(open(public.pem).read()) def verify_license(token): try: raw base64.b64decode(token.encode(utf-8)) payload, signature raw.rsplit(b|, 1) digest SHA256.new(payload) pkcs1_15.new(public_key).verify(digest, signature) machine_code, expire_date, level payload.decode(utf-8).split(|) return True, machine_code, expire_date, level except Exception: return False, , , 拿到验签后的机器码客户端还会和本机当前generate_machine_code()的结果做一次比对二者一致才放行。这一步很关键用户如果把注册码发给别人对方就算粘贴到自己软件里也会因为机器码不匹配而激活失败。4. 客户端 EXE 打包与防反编译的取舍技术上打通后下一步是让用户拿到一个能双击运行的 EXE。我最初用 PyInstaller 打包过程比想象中顺利但也踩了几个不大不小的坑。4.1 PyInstaller 打包命令我的客户端依赖 pycryptodome、wmi、subprocess 等模块打包命令如下pyinstaller --noconfirm --onefile --windowed --name VideoGuard client.py如果你像我一样在代码里动态读取了公钥文件public.pem记得把这个文件一起打进去。最简单的方式是修改生成的.spec文件里的datas例如a.datas [(public.pem, public.pem, DATA)]然后执行pyinstaller VideoGuard.spec这样公钥会随 EXE 一起被释放到临时目录软件运行时就能读取到。4.2 反编译风险明白极限在哪里Python 写的 EXE 确实能被反编译出字节码再还原出接近源码的代码。这意味着公钥、授权验证逻辑都可能被逆向者看到。但我上面说了客户端只有公钥它本身做不了签名。反编译对“注册码伪造”的危害远没有想象中那么大。为了增加一点逆向成本我在发布前用了 Nuitka 做二次编译把关键逻辑转成 C 再编回二进制。Nuitka 的编译时间比较久第一次全量编译花了二十多分钟但生成物确实比单纯 PyInstaller 的 pyc 要难分析得多。编译命令nuitka --onefile --enable-plugintk-inter --include-data-filepublic.pempublic.pem client.py如果你是做技术验证的 DemoPyInstaller 就够了如果产品要正式售卖建议至少走一遍 Nuitka性价比比外部加壳高得多。4.3 UPX 加壳与杀毒误报的拉锯为了减小体积我一度给 PyInstaller 开了 UPX 压缩结果 Windows Defender 直接报毒而且不止在一台机器上。后来查到原因UPX 解压特征和部分加壳恶意软件高度重叠误报率极高。最终我放弃了 UPX靠 Nuitka 的产物本身已经足够用。这里也提醒一句打包工具越“冷门”误报率往往越低但代价是你自己维护起来也要多花时间。5. 视频文件加密离线播放器怎么守住内容软件授权解决的是“谁能用”视频加密解决的是“内容怎么才能不被直接拿走”。视频文件如果只是个普通 mp4用户激活后顺手把文件拷贝出去授权体系就白做了。我的思路是把视频数据加密播放时必须由客户端实时解密并且解密动作依赖授权验证结果。5.1 分块加密方案AES-CTR 模式视频文件通常很大一次性整体解密既不现实也不利于拖动进度条。我用 AES-256-CTR 模式对视频做分块加密。CTR 模式是把一个计数器和密钥一起输入块密码生成密钥流再和明文异或解密时使用完全相同的计数器和密钥就能还原明文。它的最大优点是支持随机访问我想解密视频的第 5 个分块只需要把计数器调整到第 5 块的位置不需要从头解密到第 5 块。加密工具代码from Crypto.Cipher import AES from Crypto.Util import Counter import os def encrypt_video(src_path, dst_path, key): iv os.urandom(16) initial_counter int.from_bytes(iv, big) ctr Counter.new(128, initial_valueinitial_counter) cipher AES.new(key, AES.MODE_CTR, counterctr) with open(src_path, rb) as fin, open(dst_path, wb) as fout: fout.write(iv) while True: chunk fin.read(1024 * 1024) if not chunk: break fout.write(cipher.encrypt(chunk))解密端读取前 16 字节得到 IV然后构建同样的 Counter边读边解密。如果要做进度条 seek可以先计算目标字节位置对应的分块计数偏移再重新构造 Counter。5.2 密钥不落地加密视频的密钥不能是硬编码在播放器里的常量否则反编译后谁都可以解密。我的做法是密钥单独保存为一个密文密钥文件里面是用公钥加密后的“内容加密密钥”。播放器在启动时先验证注册码、比对机器码全部通过后用客户端内置私钥去解出内容密钥再流式解密视频。这样即使有人拿走了视频文件和密钥文件没有授权和对应私钥也无法解密出明文。说明客户端内置私钥还是可以被最强的逆向手段提取这是所有 DRM 类方案都绕不开的物理极限。但对 99% 的复制传播场景这套防护已经足够让普通用户知难而退。5.3 如果不想自己写播放器做一个完整的视频播放器工作量不小。如果你只是想把加密视频放到现有播放器里放我建议直接用 FFmpeg 的 HLS AES-128 方案先对视频切片再用密钥文件对每一个.ts切片加密生成m3u8播放列表。播放器端只有拿到正确密钥才能播放。你的客户端要做的事情就是验证授权后把 key 交给播放器明文的 key 始终只在内存中。FFmpeg 生成加密 HLS 的关键参数示意ffmpeg -i input.mp4 -hls_time 10 -hls_playlist_type vod -hls_key_info_file key_info.txt output.m3u8其中key_info.txt里配置了密钥文件路径、加密后的 key 的 URL 和 IV。HLS 方案兼顾了流媒体兼容性和切片加密比整文件加密更符合当前播放生态。6. 整套系统上线后的实测与三个印象最深的坑方案在代码里走通是一回事真正部署到各种用户电脑上又是另一回事。我自己在测试群里发过 20 个试用授权收到的反馈让我改了好几版。6.1 机器码采集的兼容性问题最集中的问题是老旧的 Windows 10 精简版系统上wmic 命令可能被精简掉。第一次测试时我就翻车了一个用户反馈生成机器码时程序闪退。排查发现是子进程调用返回异常而我的异常处理没有涵盖stdout为空的情况。后来我把_query_wmic里所有可能的返回值都加了兜底并且启动时先做一次自检如果 CPU、主板、磁盘全部为 UNKNOWN就直接提示“当前系统不支持硬件指纹采集”并给出人工通道。绝不能放任程序崩溃。6.2 注册码长度与人工发送的体验RSA 2048 位签名再加上机器码、日期、等级信息最终注册码长度在 400 个字符左右。用户从微信复制这种字符串时很容易漏掉尾部字符。我后来做了两个优化注册码全部大写客户端激活框里加入自动去除空格和换行、逐段校验的提示。另外在注册机里我加了二维码输出用户扫码直接复制到剪贴板这个改动极大降低了人工输入错误率。6.3 时间回拨和重装系统的处理注册码里有过期时间客户端每次启动都要校验。有用户为了续期把系统时间拨回去结果授权确实重新有效了。这个问题的通用解法是引入一个可信时间源服务但让单机工具每次联网校验又会招致离线用户的反对。我采用的折中方案是客户端把上次成功校验的系统时间和当前系统时间的差值记录下来如果差值超过 24 小时就强制要求重新激活。重装系统的情况更复杂我选择允许用户向作者临时申请一个“重装码”会根据硬件哈希匹配情况决定是否通过避免正版用户被误伤。6.4 授权文件被整体复制的问题最后再讲一个非常容易被忽略的漏洞用户把激活过的整个软件目录压缩包发给朋友里面包括授权文件。如果授权文件里只存“已激活”标志对方解压后就能直接用了。所以我在授权文件里不仅存验签后的机器码还在每次启动时重新计算当前机器的机器码做比对。只有授权文件里的机器码和当前机器一致软件才继续运行。这样即使授权文件整包被复制走也会因为硬件指纹不匹配而失效。7. 一些更省事的后续扩展思路整套体系跑通之后我又做了几个小扩展纯粹是顺手但效果不错。注册机我后来加了一个“批量发码”功能从 Excel 粘贴一串机器码和对应的到期日一键生成全部注册码列表再自动复制到剪贴板。要发的授权多了以后这个功能能省下不少时间。客户端激活界面也做了升级先显示机器码和复制按钮用户发给你的时候不用手敲激活成功后显示到期日到期前 7 天每次启动弹一次提醒配合续费流程体验比较顺。另外我还给注册码加了一个更直观的“授权人姓名”字段这样授权可追溯也能防止别人拿你的注册码到处秀。最后想说的是这套方案不是银弹。最核心的价值在于把“复制就能用”变成“复制了也用不了”把盗版成本抬高到一个普通人懒得跨过去的水平。我在实际运营中看到的主要变化是转卖资源的人还在但转卖以后买家装不上、打不开反而会回去骂卖家这本身就让盗版链条的转化率跌了一大截。对独立开发者来说这已经是一个相当值得投入的成本收益平衡点了。本文还有配套的精品资源点击获取
返回列表