灯光模拟HarmonyOS应用实战-76-选了release也不等于代码已混淆:从enable=false建立发布配置证据链
灯光模拟HarmonyOS应用实战-76-选了release也不等于代码已混淆:从enable=false建立发布配置证据链
工程能选择 release,规则文件里也写着属性名、顶层名、文件名和导出名混淆选项,这两件事很容易让人得出“发布包已经混淆”的结论。继续查看 entry/build-profile.json5 才会发现,真正控制规则是否启用的 ruleOptions.enable 当前为 false。规则文件存在并被列入 files,不代表这些规则已经生效。
发布配置最怕把“名称”“文件”“开关”“任务参数”和“产物”混成一层证据。更稳的做法是建立一条可以回读的链:声明发布意图,核对模块开关,确认构建确实使用 release,保存规则摘要,再从构建报告和目标产物验证效果。任何一层缺失,都只能报告到已经证明的那一层。

本文依据当前 The_kemusan 的根配置、entry/HSP/HAR 模块配置与规则文件展开,不公开签名敏感字段,也不宣称已经重新构建或检查某个发布包。
一、根配置只说明工程拥有release构建模式
根 build-profile.json5 的 buildModeSet 同时声明 debug 和 release。这证明构建系统认识这两个模式名称,但没有说明某次命令选择了哪一个,更没有说明某个模块的混淆选项已打开。
{
"app": {
"buildModeSet": [
{ "name": "debug" },
{ "name": "release" }
]
}
}
同一根配置还声明 entry、libraryHSP 和 libraryHAR 三个模块。每个模块都有自己的 build-profile.json5,所以根级存在 release 不会自动覆盖各模块的 ArkTS 选项。发布审计必须逐个查看实际被打入产品的模块。
| 证据 | 能证明 | 不能证明 |
|---|---|---|
buildModeSet 有 release | 工程支持该模式名 | 当前任务确实用了 release |
| IDE 下拉选择 release | 当前界面选择意图 | 命令、缓存和各模块最终输入一致 |
| 规则文件存在 | 有候选规则文本 | 规则主开关已启用 |
| release 包文件存在 | 曾产生某个文件 | 文件来自当前源码和当前配置 |
| 包可安装 | 基本打包、签名链可能可用 | 名称已经按规则变换 |
二、entry当前主开关明确是false
entry/build-profile.json5 的 release 配置在 arkOptions.obfuscation.ruleOptions 下设置了 enable: false,同时列出 ./obfuscation-rules.txt。
{
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
}
]
}
这里的决定性事实是 enable: false。files 表示规则文件路径已配置,主开关关闭时不能把它解释成规则已执行。至于 HarmonyOS 构建工具在当前 SDK 中如何记录具体选项,应以该版本构建报告和官方构建说明为准;本文只对源码配置做静态解读。
同文件的 resOptions.copyCodeResource.enable: false 控制的是代码资源复制选项,不是混淆主开关。两个字段都叫 enable,路径和职责不同,审计记录必须保存完整键路径。
三、规则文件内容不会越过主开关自行生效
entry 的 obfuscation-rules.txt 当前包含多个启用项:
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation
这些行说明团队准备了较强的候选规则,但它们的存在不是运行证据。可以把关系写成一个简单判定:只有构建模式匹配、模块主开关打开、规则文件成功加载,候选规则才进入后续处理;最后还要从报告和产物确认实际效果。

规则本身也需要风险审阅。属性名、导出名和文件名变化可能影响反射式访问、序列化字段、跨模块接口、动态加载或外部协议。不能为了得到“已混淆”状态直接把所有开关打开,再到运行阶段寻找崩溃。
四、三个模块目前都不能默认视为已启用
当前 libraryHSP/build-profile.json5 与 libraryHAR/build-profile.json5 的 release ruleOptions.enable 同样为 false。HSP 还声明了 consumerFiles;这仍不改变主开关状态。
interface ModuleObfuscationObservation {
moduleName: string;
buildMode: string;
ruleEnabled: boolean;
ruleFiles: string[];
consumerFiles: string[];
}
const currentObservations: ModuleObfuscationObservation[] = [
{
moduleName: 'entry',
buildMode: 'release',
ruleEnabled: false,
ruleFiles: ['./obfuscation-rules.txt'],
consumerFiles: []
},
{
moduleName: 'libraryHSP',
buildMode: 'release',
ruleEnabled: false,
ruleFiles: ['./obfuscation-rules.txt'],
consumerFiles: ['./consumer-rules.txt']
},
{
moduleName: 'libraryHAR',
buildMode: 'release',
ruleEnabled: false,
ruleFiles: ['./obfuscation-rules.txt'],
consumerFiles: ['./consumer-rules.txt']
}
];
这是根据当前三个模块配置整理的观察模型,不是工程已有代码。它明确阻止“entry 打开即可代表整个应用”这样的推断。实际产品是否把 HSP/HAR 打入 entry,还要结合模块依赖和本次构建图核对;模块在根配置中声明,也不等于一定进入目标 HAP。
五、用ReleaseProtectionManifest声明发布意图

先把期望写成清单,能让配置差异在构建前暴露。清单不存口令、私钥、证书内容或本机绝对敏感路径。
interface ReleaseProtectionModule {
moduleName: string;
required: boolean;
expectedRuleEnabled: boolean;
expectedRuleFile: string;
protectedExportsReviewed: boolean;
}
interface ReleaseProtectionManifest {
manifestVersion: number;
product: string;
buildMode: 'release';
modules: ReleaseProtectionModule[];
approvedRuleDigest: string;
}
expectedRuleEnabled 表达产品意图,当前配置观察表达实现状态。二者不同就阻止发布准备继续。protectedExportsReviewed 要求跨模块 API、序列化字段和外部调用面已经审阅;它不能由脚本自动替业务负责人勾选。
approvedRuleDigest 保存规则文件内容摘要,避免审阅后规则又发生变化。摘要只能证明文件内容相同,不能证明工具已加载它,因此仍需后续任务和报告证据。
六、构建前静态门禁核对完整键路径
可以在构建前读取各模块 JSON5,输出一份不含敏感信息的配置报告。下面用已解析对象展示核心校验,正式脚本要使用能够正确处理 JSON5 的解析器,不能先粗暴删除注释和尾逗号。
interface ProtectionIssue {
moduleName: string;
code: string;
configPath: string;
}
function compareProtectionIntent(
expected: ReleaseProtectionModule,
actual: ModuleObfuscationObservation
): ProtectionIssue[] {
const issues: ProtectionIssue[] = [];
if (actual.buildMode !== 'release') {
issues.push({
moduleName: actual.moduleName,
code: 'WRONG_BUILD_MODE',
configPath: 'buildOptionSet.name'
});
}
if (actual.ruleEnabled !== expected.expectedRuleEnabled) {
issues.push({
moduleName: actual.moduleName,
code: 'RULE_ENABLE_MISMATCH',
configPath:
'arkOptions.obfuscation.ruleOptions.enable'
});
}
if (actual.ruleFiles.indexOf(
expected.expectedRuleFile) < 0) {
issues.push({
moduleName: actual.moduleName,
code: 'RULE_FILE_MISSING',
configPath:
'arkOptions.obfuscation.ruleOptions.files'
});
}
return issues;
}
报告应该写 entry: RULE_ENABLE_MISMATCH,而不是笼统写“release 配置失败”。完整路径能防止把 copyCodeResource.enable 与 ruleOptions.enable 看混。若目标策略暂时明确要求关闭混淆,expectedRuleEnabled 可以为 false,报告就忠实记录“关闭符合意图”;不要把安全审计写成永远要求开启的单向脚本。
七、启用前先梳理必须保留的契约
混淆不是一个孤立布尔开关。属性名或导出名变化前,需要列出框架、跨模块、序列化和动态访问依赖,制定保留规则。
interface KeepContract {
owner: string;
symbolOrPattern: string;
reason: string;
evidence: string;
}
const keepContracts: KeepContract[] = [
{
owner: 'entry',
symbolOrPattern: 'serialized-history-fields',
reason: '持久化字段需要协议兼容',
evidence: 'history-schema-review'
},
{
owner: 'libraryHSP',
symbolOrPattern: 'public-cross-module-api',
reason: '跨模块调用面需与消费规则一致',
evidence: 'export-consumer-review'
}
];
示例只表达审阅形态,不声称当前工程已经存在这些正式清单。尤其不能直接把所有历史记录字段名称写进公共文章;真实项目应在受控仓库中保存精确协议和保留规则。
开启强规则后,应先做最小试验包,覆盖启动、页面加载、Preferences 读写、备份恢复、HSP/HAR 调用与错误日志。任何运行失败都要回到具体规则和契约,不要用“全部关闭”掩盖根因,也不要只增加宽泛 keep 让保护失去意义。
八、构建任务必须记录实际模式和输入摘要
IDE 下拉框属于操作意图,自动发布需要机器可读任务记录。每次构建生成一个 ReleaseBuildAttestation,写明源码修订、实际 buildMode、模块开关、规则摘要和输出摘要。
interface ReleaseBuildAttestation {
sourceRevision: string;
requestedBuildMode: string;
resolvedBuildMode: string;
moduleRuleDigests: string[];
moduleRuleEnabled: boolean[];
buildSucceeded: boolean;
artifactDigest: string;
generatedAt: string;
}
function canInspectArtifact(
record: ReleaseBuildAttestation
): boolean {
return record.buildSucceeded &&
record.requestedBuildMode === 'release' &&
record.resolvedBuildMode === 'release' &&
record.artifactDigest.length > 0;
}
buildSucceeded 只证明构建任务成功结束;只有开关、规则摘要和产物进一步核对后,才能报告保护状态。产物摘要用于避免拿旧文件做后续分析。若工作区没有 Git,也应使用可复核的源码快照摘要,不能写一个虚构修订号。
本文没有运行任何构建命令。实际命令参数要以工程当前 hvigor 版本、项目脚本和官方文档为准,执行时建议禁用长期守护进程并保存完整退出码与报告路径。
九、产物核对关注差异而不是“文件能打开”
产物层可以比较一个受控样例在关闭与开启配置下的符号、文件名和体积变化,并执行相同功能回归。不要把体积变小单独当作混淆证据,压缩、资源裁剪和构建缓存都可能影响大小。
interface ArtifactProtectionEvidence {
artifactDigest: string;
ruleDigest: string;
symbolSampleChanged: boolean;
fileNameSampleChanged: boolean;
protectedApiStillCallable: boolean;
persistenceRoundTripPassed: boolean;
smokeSuitePassed: boolean;
}
function hasProtectionEvidence(
evidence: ArtifactProtectionEvidence
): boolean {
return evidence.artifactDigest.length > 0 &&
evidence.ruleDigest.length > 0 &&
evidence.symbolSampleChanged &&
evidence.protectedApiStillCallable &&
evidence.persistenceRoundTripPassed &&
evidence.smokeSuitePassed;
}
是否要求文件名样例变化取决于批准规则;未启用文件名选项时不应作为必选项。更可靠的方法是让证据结构根据 manifest 中实际启用的规则生成对应断言。
发布证据还要区分未签名中间产物、签名 HAP、安装包和商店提交版本。对其中一个文件的符号抽样,不能自动代表另一个文件。签名信息本身也要脱敏,报告只保留证书指纹或配置 ID,不记录口令。
十、排障表、验证清单与当前结论
| 现象 | 优先核对 | 常见原因 | 处理方向 |
|---|---|---|---|
| 选了 release,符号仍可读 | ruleOptions.enable 完整路径 | 主开关仍是 false | 先比对 manifest 与模块配置 |
| 规则文件写了四个启用项却无效果 | 主开关和构建报告 | 只配置 files,未启用规则 | 不把文件存在当执行证据 |
| entry 有变化,HSP/HAR 没变化 | 各模块开关 | 只修改了 entry | 按实际构建图逐模块核对 |
| 开启后跨模块调用失败 | consumer/keep 契约 | 导出名或属性名被改写 | 收窄规则并补精确保留项 |
| 历史数据读取异常 | 序列化字段协议 | 属性名变化影响持久化 | 固定 DTO 字段或配置保留规则 |
| 包体变小就被报告为已保护 | 产物证据定义 | 把体积变化当唯一依据 | 核对规则、符号样例与功能回归 |
| 报告显示开关开启,拿到的却是旧包 | 产物摘要和时间 | 输出目录残留旧文件 | 每次记录摘要并隔离输出 |
| 审计报告泄露签名信息 | 输出白名单 | 直接复制根配置 | 只保留配置 ID 与非敏感摘要 |
- 根配置只用于确认 release 模式存在,不直接作为混淆结论。
- entry、HSP、HAR 的
ruleOptions.enable分别读取。 -
copyCodeResource.enable没有被误认为混淆开关。 - 规则文件摘要与批准版本一致。
- manifest 明确每个实际参与构建模块的期望状态。
- 属性名、顶层名、文件名和导出名规则逐项审阅风险。
- 序列化字段、跨模块 API 和动态访问有精确保留契约。
- 构建记录包含请求模式、解析模式、源码与产物摘要。
- 构建报告能回读实际加载的模块选项。
- 产物核对与启动、持久化、备份恢复和模块调用回归配套。
- 未签名、签名和提交产物分别留存证据。
- 报告不会写入口令、私钥或完整签名材料。
当前源码能够确认:根配置声明了 debug 与 release;entry 的 release 混淆 ruleOptions.enable 是 false;规则文件列出了四项候选启用规则;libraryHSP 与 libraryHAR 的同一主开关也为 false。因此不能根据“选了 release”或“规则文件存在”报告当前代码已经混淆。
ReleaseProtectionManifest、配置门禁、保留契约、构建证明和产物证据结构都是建议方案,尚未写入 The_kemusan。本文没有执行构建,没有生成或反编译新的 HAP,也没有在模拟器、真机或应用市场环境验证启用后的行为。
更多推荐

所有评论(0)