首页
/ Electron ASAR 完整性校验(ASAR Integrity)原理与接入指南

Electron ASAR 完整性校验(ASAR Integrity)原理与接入指南

2026-09-06 19:00:38作者:范靓好Udolf

ASAR Integrity 是 Electron 提供的运行时安全校验机制:开启后,应用会在运行时对自身 app.asar 的内容与打包期写入的哈希进行一致性校验,一旦发现文件被篡改便直接强制终止进程,从而防止应用代码被静态修改后重新分发。本文以本仓库官方教程 docs/tutorial/asar-integrity.md 为骨架,结合 Electron 源码、fuse 体系与集成测试,系统讲解 ASAR Integrity 的版本支持、工作原理、fuse 开关、macOS/Windows 双平台的打包期哈希注入方法,以及校验失败时的真实表现。

ASAR Integrity 是什么

ASAR(Atom Shell Archive)是 Electron 用来打包应用源码的归档格式(格式说明见 asar-archives 教程)。普通 ASAR 归档没有防篡改能力:攻击者拿到应用包后可以解包、修改其中任意 JS 再重新打包,由于可执行文件并未对归档内容做校验,被篡改的代码照样会被加载执行。

ASAR Integrity 正是针对这一缺口设计的安全特性。它的核心思想是**“打包期计算哈希,运行期验证哈希”**:

  • 在打包时,把整个 ASAR 头的哈希(header hash)嵌入到可执行文件的元数据里;
  • 在运行时,Electron 校验 ASAR 头的哈希是否一致;同时在按需读取文件内容时,对照归档内嵌的逐文件/逐块哈希做校验;
  • 若头哈希缺失或对不上、或读取的内容校验失败,应用会强制终止(forcefully terminate),而不是带着被篡改的数据继续运行。

按官方教程描述,该特性仍处于实验阶段(experimental),默认关闭,需要打包期显式开启。它针对的核心攻击面是对分发渠道上应用包的静态篡改,让篡改后的应用无法正常启动。

版本支持情况

平台 支持起始版本
macOS electron >= 16.0.0
Windows electron >= 30.0.0

除了 Electron 本身版本要满足要求之外,还需要打包 app.asar 时使用的 asar 工具支持生成完整性元数据

  • asar@3.1.0 首次引入了完整性支持;
  • 此后该 npm 包迁移更名为 @electron/asar所有版本的 @electron/asar 均支持 ASAR integrity(当前仓库的 devDependency 即为 @electron/asar@^4.3.0)。

关于 Mac App Store(MAS)构建

官方文档特别说明:ASAR integrity 在 MAS(Mac App Store)构建中完全支持,且被推荐作为最佳实践。需要理解的是:

  • 通过 MAS 安装的应用,其 Resources/ 目录已被系统保护(目录属主为 root,普通进程无法写入),系统层面已有只读防护;
  • 即便如此,ASAR integrity 仍能提供额外一层安全纵深
  • 如果你使用 Electron 的 MAS 构建产物、却通过非 App Store 渠道分发(例如官网直接下载),这类安装不具备系统级只读保护,此时 ASAR integrity 尤为重要。

工作原理:两级哈希体系

理解 ASAR Integrity 需要区分“归档内部”与“归档外部”两级哈希:

归档头 JSON 中的 integrity 元数据

ASAR 文件本身是一段 Pickle 化的头部 + 文件数据区。每个 ASAR 归档内含一段 JSON 字符串形式的 header,header 中会包含 integrity 对象,其结构如下(取自官方教程示例):

{
  "algorithm": "SHA256",
  "hash": "...",
  "blockSize": 1024,
  "blocks": ["...", "..."]
}

字段含义:

  • algorithm:哈希算法标识,当前仅支持 SHA256
  • hash:十六进制编码的完整内容哈希;
  • blockSize:分块大小(单位:字节),即每个块包含多少个字节;
  • blocks:十六进制编码的逐块哈希数组,数组中每个元素对应当前内容的一个 blockSize 大小的数据块。

需要说明的是:这个 JSON 示例是归档内“受保护数据段”的完整哈希 + 分块哈希结构。它的实际粒度由打包工具 @electron/asar 决定——从本仓库的解析代码看,archive 打开时会遍历 header 的 file table,逐节点读取其 integrity 字段(algorithm/hash/blockSize/blocks)并挂到对应条目的元数据上,见 shell/common/asar/archive.cc。因此 blockSize/blocks 的分块粒度通常是按归档内文件条目组织的:大文件可被切成多个块分别做哈希,从而支持“读到哪块验到哪块”的增量校验,避免一次性读取并哈希整个文件。仓库集成测试里构造的测试归档即采用 4MB(4 * 1024 * 1024)的块大小,并刻意在每块开头写入标记以便精准篡改某个块,参见 spec/asar-integrity-spec.ts

打包期单独注入的“整个 ASAR 头”哈希

与上面的归档内元数据不同,你还必须在打包应用时另外定义一个对整个 ASAR header 的十六进制哈希(header hash),并把它写入应用的可执行文件(macOS 为 Info.plist,Windows 为 exe 资源,详见后文“提供 header hash”一节)。这个“外部”哈希是整个校验链路的第一道闸门:

  • 运行期打开可被校验的 asar 归档时,Electron 先取出这个外部头哈希;
  • 对归档实际读出的 header JSON 字节流计算 SHA256 并比对;
  • 如果外部没有提供哈希、或者哈希不匹配,应用会直接强制终止

对应的实现见 shell/common/asar/archive.cc:在 Archive::Init() 中,若归档需要校验(load_integrity 且外部提供了 integrity 配置),会先取 HeaderIntegrity(),拿不到就 LOG(FATAL) << "Failed to get integrity for validatable asar archive";随后对 header 字节调用 ValidateIntegrityOrDie,验证通过才把 header_validated_ 置真,此后 header 内嵌的逐文件哈希才被信任用于内容读取校验。

读取路径上的逐块校验

头哈希校验通过后,还有一层内容级校验:所有通过归档读出的数据都会对照条目自身哈希做验证。核心兜底函数是 shell/common/asar/asar_util.cc 中的 ValidateIntegrityOrDie

void ValidateIntegrityOrDie(base::span<const uint8_t> input,
                            const IntegrityPayload& integrity,
                            std::string_view what) {
  if (integrity.algorithm == HashAlgorithm::kSHA256) {
    const std::string hex_hash =
        base::ToLowerASCII(base::HexEncode(crypto::hash::Sha256(input)));
    if (integrity.hash != hex_hash) {
      LOG(FATAL) << "Integrity check failed for asar archive entry '" << what
                 << "' ...";
    }
  } else {
    LOG(FATAL) << "Unsupported hashing algorithm in ValidateIntegrityOrDie";
  }
}

这里的设计要点:比较是对整个条目内容哈希做全量比对,失败即 FATAL。而对于流式读取(如渲染进程经 network service 读取文件数据),则通过 Mojo 的 FilteredDataSource::Filter 机制按块增量验证——对应的 AsarFileValidator 记录当前块与已累计哈希字节数,在每次读块结束时核对当前块的哈希,见 shell/browser/net/asar/asar_file_validator.harchive.ccFillFileInfoWithNode 读取条目 integrity、并携带 header_validated_ 标记,就是为了保证“只有头哈希被验证过的归档,其内嵌分块哈希才可用于内容校验”,见 shell/common/asar/archive.cc

在可执行文件中开启:相关 Fuse

ASAR Integrity 校验默认关闭,需要在打包构建期通过切换 Electron fuse 来开启。fuse 是写在 Electron 二进制中的“魔法位”,打包期翻转、之后随应用一起代码签名,被签名保护后便无法在分发后被改回(系统层由 macOS Gatekeeper / Windows AppLocker 之类的签名校验机制兜底)。关于 fuse 的整体机制与全部开关,参见 fuses 文档

涉及本特性的两个 fuse:

EnableEmbeddedAsarIntegrityValidation

默认:关闭

  • @electron/fuses 选项: FuseV1Options.EnableEmbeddedAsarIntegrityValidation
  • 作用:在 macOS 与 Windows 上,开启对 app.asar 内容加载时的完整性校验。
  • 性能影响:官方描述该特性“设计上追求最小性能影响”,但可能让从 app.asar 内部读取文件的速度略有下降,因为读取多了一重哈希计算。

OnlyLoadAppFromAsar(强烈建议同时开启)

默认:关闭

  • @electron/fuses 选项: FuseV1Options.OnlyLoadAppFromAsar
  • 作用:默认情况下 Electron 按 app.asarappdefault_app.asar 的顺序搜索应用代码;开启该 fuse 后,只允许从 app.asar 加载应用代码

官方教程明确警告:开启完整性校验 fuse 时,通常应同时开启 onlyLoadAppFromAsar。否则,完整性校验可以通过“Electron 应用代码搜索路径”(即让它从未受校验的 app 目录加载代码)被绕过。两者结合才能保证“不可能加载未经校验的代码”。

开启示例(使用 @electron/fuses):

const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')

flipFuses(
  // E.g. /a/b/Foo.app(macOS 上指向 .app 包,Windows 上指向 exe)
  pathToPackagedApp,
  {
    version: FuseVersion.V1,
    [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
    [FuseV1Options.OnlyLoadAppFromAsar]: true
  }
)

需要留意:flipFuses 会改写二进制,因此翻转 fuse 之后、正式发布之前必须重新做(代码)签名,否则 macOS 的 ad-hoc/正式签名失效;仓库集成测试在翻转前显式设置 resetAdHocDarwinSignature: true 以重新生成 ad-hoc 签名,见 spec/asar-integrity-spec.ts。最终由操作系统签名校验保证 fuse 位在分发后不可被反转。

提示:若使用 Electron Forge,可通过 @electron-forge/plugin-fuses 插件在 Forge 配置文件中完成 fuse 配置(见 Forge fuses 插件说明)。

提供 header hash:打包期注入

完整性校验比对的对象,是打包时你提供的那个归档头哈希。提供方式因平台而异,且不同打包工具链的处理方式完全不同。

使用 Electron 官方工具链(推荐)

如果你使用 Electron Forge 或 Electron Packager,只要启用了 asar,它们会自动完成上述 macOS/Windows 的哈希注入,无需额外配置。使用本特性所需的最低版本为:

  • @electron/packager@18.3.1
  • @electron/forge@7.4.0

使用其他构建系统:macOS

macOS 打包时,必须在产物应用的 Info.plist 中填充一个合法的 ElectronAsarIntegrity 字典块。示例:

<key>ElectronAsarIntegrity</key>
<dict>
  <key>Resources/app.asar</key>
  <dict>
    <key>algorithm</key>
    <string>SHA256</string>
    <key>hash</key>
    <string>9d1f61ea03c4bb62b4416387a521101b81151da0cfbe18c9f8c8b818c5cebfac</string>
  </dict>
</dict>

说明:

  • 合法的 algorithm 取值目前仅有 SHA256
  • 外层 key(如 Resources/app.asar)是归档相对 Contents/ 的相对路径;
  • hash 是用指定算法对该归档的 ASAR header 计算得到的哈希;
  • 生成方法:@electron/asar 包暴露了 getRawHeader 方法,对其返回结果的 headerString 做哈希即可。

对应到 Electron 源码侧:macOS 下 Archive::HeaderIntegrity() 从主 bundle 的 Info.plist 读取 ElectronAsarIntegrity 字典,用归档相对 Contents 的路径(RelativePath())查表,并校验 algorithm 必须为 SHA256 后才返回哈希载荷,见 shell/common/asar/archive_mac.mm

借助 node:crypto 计算头哈希的参考脚本(与本仓库集成测试中的 headerHash 辅助函数一致,见 spec/asar-integrity-spec.ts):

const { createHash } = require('node:crypto')
const { getRawHeader } = require('@electron/asar')

const hash = createHash('sha256')
  .update(getRawHeader('/path/to/app.asar').headerString)
  .digest('hex')
// 将 hash 写入 Info.plist 的 ElectronAsarIntegrity 字典

使用其他构建系统:Windows

Windows 打包时,必须在可执行文件中填充一个合法的 资源(resource)条目

  • 资源类型(type): Integrity
  • 资源名称(name): ElectronAsar
  • 资源值: 一个 JSON 编码的字典数组,形式如下:
[
  {
    "file": "resources\\app.asar",
    "alg": "sha256",
    "value": "9d1f61ea03c4bb62b4416387a521101b81151da0cfbe18c9f8c8b818c5cebfac"
  }
]

其中 file 为归档相对资源目录(assets 目录)的路径,alg 为小写算法名,value 为十六进制哈希值。

这一格式并非随意约定,它必须与 Electron 侧解析逻辑严格一致。Windows 端实现见 shell/common/asar/archive_win.cc

  • 通过 Win32 API FindResource/LoadResource 从当前模块(exe)中查找类型为 Integrity、名称为 ElectronAsar 的资源;
  • 将资源内容作为 JSON 解析,要求顶层必须是数组,逐项校验字段 file(非空字符串)、alg(小写后必须等于 sha256)、value(非空);
  • 校验通过后以“小写化后的文件路径”为 key 建缓存表,供 Archive::HeaderIntegrity() 查询。若归档路径在表中找不到对应项,会直接 LOG(FATAL)

macOS 与 Windows 两条读取路径虽然平台 API 不同,但都归结为返回一个 IntegrityPayloadalgorithm = HashAlgorithm::kSHA256 + 十六进制 hash),供 Archive::Init() 做头哈希比对。

官方文档给出的 Windows 实现参考是 Electron Packager 源码中的 src/resedit.ts(通过 resedit 等库改写 PE 资源)。仓库集成测试中也用 reseditNtExecutableResource 以相同形状向 exe 写入 Integrity/ElectronAsar 资源,见 spec/asar-integrity-spec.ts

校验失败时的真实行为:源码与测试佐证

官方教程的核心行为承诺是:若没有任何哈希、或哈希不匹配,应用将被强制终止。仓库的集成测试 spec/asar-integrity-spec.ts 对这一行为做了非常系统的验证,可归纳为以下几个层面:

1. fuse 开关本身决定是否生效

  • EnableEmbeddedAsarIntegrityValidation 开启时:正常未篡改的归档可正常启动退出(退出码 0);
  • 篡改归档头后启动,进程崩溃(macOS 上表现为 SIGABRT/SIGTRAP,Windows 上为非零退出码),输出中包含 Integrity check failed for asar archive
  • 同一篡改在 fuse 关闭时完全不做任何校验,应用照常运行(退出码 0)。

这组对照实验直接证明了“fuse 是总开关”以及“运行期校验确实会以终止进程为代价”。

2. 不同加载路径上的篡改检测

测试分别篡改了不同语义的内容,并断言应用终止:

  • 篡改主进程加载的 JS:进程以状态码 1 退出并打印 ASAR Integrity Violation: got a hash mismatch
  • 篡改渲染进程读取的内容(如 require-trusted-types-for 这类 renderer 资源):进程崩溃,输出包含 Failed to validate block while ending ASAR file stream
  • 篡改 fs.open 一族的文件读取路径(测试覆盖 fdstreamhandlecopy 四种模式):全部无法读到原内容,以状态码 1 退出并报 hash mismatch,且输出中不会出现 *-read-ok 标记。

3. 多块条目的增量校验语义

对于包含多个 4MB 块的归档条目,测试进一步验证了块级校验的精确语义:

  • 篡改第 N 块后流式读取:到达被篡改块之前的数据可以正常流出,一旦流推进到被篡改的块便终止,且被篡改块的任何一个字节都不会被交付
  • 当只有较靠后的块被篡改时,只读取完好块的区间请求(range read)依然成功——说明校验是按被实际读取的块粒度发生的,不影响与篡改块无关的读取;
  • 整文件读取(readFile)遇到含篡改块的文件则必然失败;
  • 甚至在“某块先被完整读取过、随后又被就地篡改、再次读取”的场景下,依然能检测到哈希不匹配——说明读取不会无条件信任此前的结果。

这些用例一方面印证了“读到哪块验到哪块”的增量设计(配合 asar_file_validator.h 的块级 Mojo Filter),另一方面也说明从主进程 fs API 到渲染进程网络读取的整条数据通路都被纳入了校验范围。

使用前提与注意点小结

在项目里落地 ASAR Integrity,建议按如下顺序检查:

  1. 确认平台与版本:macOS 需要 Electron ≥ 16,Windows 需要 Electron ≥ 30;若使用 MAS 构建并走非 App Store 分发,务必开启。
  2. 确认 asar 工具:使用支持 integrity 的 @electron/asarasar@3.1.0 起,其后所有版本均可)。
  3. 打包期开启 fuse:翻转 EnableEmbeddedAsarIntegrityValidation,并同时翻转 OnlyLoadAppFromAsar 以防绕过;翻转后重新签名。
  4. 注入头哈希:优先使用 Electron Packager(≥ 18.3.1)/ Electron Forge(≥ 7.4.0)自动完成;自定义构建系统时,macOS 写 Info.plistElectronAsarIntegrity 字典(算法仅支持 SHA256,哈希用 getRawHeader(...).headerStringnode:crypto 生成),Windows 写入类型 Integrity、名称 ElectronAsar 的 JSON 数组资源。
  5. 理解性能与失败模型:开启后从 app.asar 内部读文件可能略有变慢;一旦头哈希缺失/不匹配或读取内容哈希不一致,Electron 会以 Fatal 方式终止应用,因此要保证打包流水线生成的哈希与最终归档严格对应,任何“打包后改 asar”的流程都会导致应用无法启动。

综上,ASAR Integrity 提供了一条从“明文归档可被任意篡改”到“归档头 + 文件内容双层哈希、篡改即终止”的加固路径,其全部机制都围绕 asar 归档格式fuse 打包期开关 以及 archive 运行时校验 三块代码实现展开,适合作为 Electron 应用发布安全加固的首选方案之一。

登录后查看全文
热门项目推荐
相关项目推荐