Unity AssetBundle 热更新资源保护排查笔记

这段时间排查了几个使用 AssetBundle 更新资源的 Unity 项目,发现一个容易被忽略的问题:安装包里的代码和资源已经做过保护,但热更新下载的新 Bundle 仍然保持标准格式。首包和更新资源走了不同链路,安全措施只覆盖了前者。

AssetBundle 负责资源打包和加载,版本检查、下载、缓存、回滚通常由自研框架或 Addressables 处理。下面按实际排查顺序记录几个需要注意的点。

1. 检查版本清单和下载地址

自研框架常见的是 AssetBundleManifest、JSON 或二进制版本表,Addressables 则会检查远程 content catalog。清单中一般包含 Bundle 名称、版本、依赖关系和下载位置。

https://cdn.example.com/hotfix/{channel}/{version}/{bundleName}.ab

如果路径规则固定,URL 长期有效,拿到清单后就可能批量下载资源。下载文件仍以 UnityFS 开头时,可以直接交给常见 Unity 资源工具解析。

hash 文件名只能隐藏语义,签名 URL 主要限制链接有效期和滥用频率。合法客户端仍要取得文件,所以最终还得处理 Bundle 本体。

2. 当前版本、历史目录和本地缓存要一起看

当前版本加密后,还要检查 CDN 历史目录。灰度、兼容和回滚会保留旧资源,如果早期 Bundle 没补做加密,旧版本仍然可能成为明文入口。

本地缓存也要单独确认。网络下载的是密文,客户端解密后再把标准 Bundle 写入 Application.persistentDataPath,保护范围就只剩传输阶段。比较稳妥的做法是缓存密文,加载时再解密。

密钥不宜全部 Bundle 共用一个固定值,可以按 Bundle 和版本派生工作密钥:

// 伪代码
byte[] context = UTF8($"{bundleName}:{version}");
byte[] salt = SecureRandomBytes(16);
byte[] resourceKey = HKDF(masterKey, salt, info: context);

byte[] nonce = SecureRandomBytes(12);
byte[] encrypted = AESGCMEncrypt(
    resourceKey,
    nonce,
    rawBundleBytes,
    associatedData: context
);

saltnonce 可以随密文保存,但 nonce 不能在同一密钥下重复。工作密钥按资源隔离,可以缩小单个密钥泄露后的影响范围;主密钥仍要避免以明显常量出现在托管代码中。

3. 加密文件和版本清单分别校验

只验证 Bundle 的 SHA-256 并不完整。如果清单没有签名,文件和 hash 可以一起被替换。比较合理的校验顺序如下:

if (!VerifySignature(manifestBytes, manifestSignature, embeddedPublicKey))
    RejectUpdate();

BundleEntry entry = signedManifest[bundleName];

if (SHA256Hex(cachedEncryptedBundle) != entry.sha256)
    RejectAndRedownload(bundleName);

if (entry.version < signedManifest.minSupportedVersion)
    ForceRedownload(bundleName);

清单签名确认版本信息来自可信发布端,文件 hash 检查缓存内容,AES-GCM 认证标签检查密文是否被改动,最低支持版本用于阻止危险回退。

Unity CRC 和 AssetBundle hash 也有各自用途。CRC 可以发现内容损坏或变化,AssetBundle hash 可作为缓存版本值,但二者不能替代发布端签名。

4. 加载方式决定性能成本

LZ4 Bundle 原本支持按块读取。如果在外层做整文件加密,可能必须先解完整文件才能加载。大型 Bundle 通过 AssetBundle.LoadFromMemory 整包载入时,还要注意密文、明文和 Unity 对象同时存在造成的峰值内存。

实际接入时,我一般按资源价值分级:新角色、付费皮肤、活动配置优先保护,普通通用资源采用较轻方案;大 Bundle 评估分块解密、流式读取或自定义 Provider,并在中低端设备上测试加载耗时和峰值内存。

5. 发布前检查项

  • 首包和热更 Bundle 是否使用同一套保护规则;
  • CDN 历史版本是否仍保留明文资源;
  • 本地缓存是否重新落成标准 Bundle;
  • 密钥是否按 Bundle 或版本隔离;
  • 清单是否签名并设置最低支持版本;
  • 加密是否影响 LZ4 分块读取和加载内存。

资源加密接入热更新,需要改动构建脚本、加载层、缓存和密钥管理。前期工作量不小,但如果不把它纳入发布流程,以后每次增量更新都要重新检查有没有明文资源。首包保护是一次性的,更新之后仍然成立,才算真正覆盖了 AssetBundle 分发链路。

posted @ 2026-09-23 11:44  字节暗面  阅读(12)  评论(0)    收藏  举报