1# ArkGuard源码混淆工具 2 3## 代码混淆简介 4 5针对工程源码的混淆可以降低工程被破解攻击的风险,缩短代码的类与成员的名称,减小应用的大小。 6DevEco Studio原先默认开启代码混淆功能,会对API 10及以上版本的Stage模型、[编译模式为release](#说明)时自动进行代码混淆,其仅对参数名和局部变量名进行混淆。 7**从DevEco Studio 5.0.3.600开始,新建工程默认关闭代码混淆功能,如果在模块配置文件build-profile.json5开启代码混淆,混淆规则配置文件obfuscation-rules.txt中默认开启-enable-property-obfuscation、-enable-toplevel-obfuscation、-enable-filename-obfuscation、-enable-export-obfuscation四项推荐选项,开发者可进一步在obfuscation-rules.txt文件中更改配置。** 8 9### 使用约束 10 11* 仅支持Stage工程 12* 编译模式为release 13* 模块及模块依赖的HAR均未配置关闭混淆的规则`-disable-obfuscation` 14 15### 混淆范围 16 17在应用工程中,代码混淆支持以下格式文件混淆,混淆后的缓存文件保存在模块目录下的build/[...]/release目录下。 18 19* ArkTS文件 20* TS文件 21* JS文件 22 23## 开启代码混淆 24 25代码混淆能力已在系统中集成,可通过以下方式在DevEco Studio开启使用。 26 27代码混淆目前只提供名称混淆的能力(因为其它混淆能力会劣化性能)。 开启代码混淆可以混淆以下名称: 28 29* 参数名和局部变量名 30* 顶层作用域的名称 31* 属性名称 32* 导出名称 33* 文件名称 34 35混淆开启后,默认使能对参数名和局部变量名的混淆,无需选项配置。顶层作用域名称混淆、属性名称的混淆、导出名称混淆、文件名混淆打开可能会导致运行时错误,这些混淆功能通过混淆配置选项来开启/关闭它们。 36 37创建一个新工程的时候,配置文件build-profile.json5中会自动生成以下内容: 38 39``` 40"arkOptions": { 41 "obfuscation": { 42 "ruleOptions": { 43 "enable": true, 44 "files": ["./obfuscation-rules.txt"], 45 } 46 } 47} 48``` 49 50创建一个新的library的时候,还会额外生成consumerFiles属性: 51 52``` 53"arkOptions": { 54 "obfuscation": { 55 "ruleOptions": { 56 "enable": true, 57 "files": ["./obfuscation-rules.txt"], 58 } 59 "consumerFiles": ["./consumer-rules.txt"] 60 } 61} 62``` 63 64混淆功能被关闭希望重新开启混淆需要满足条件: 属性ruleOptions.enable的值为true。 65 66属性ruleOptions.files中指定的混淆配置文件会在构建HAP、HSP或HAR的时候生效。 67属性consumerFiles中指定的混淆配置文件会在构建依赖这个library的模块时生效。这些混淆配置文件的内容还会被合并到HAR包中的obfuscation.txt文件。 68 69当构建HAP、HSP和HAR的时候,最终的混淆规则是当前构建模块的ruleOptions.files属性,依赖library的consumerFiles属性,以及依赖HAR包中的obfuscation.txt文件的合并。 70如果构建的是HAR,HAR包中的obfuscation.txt是自身的consumerFiles属性, 依赖library的consumerFiles属性,以及依赖HAR包中的obfuscation.txt文件的合并。构建HAP、HSP不会生成obfuscation.txt。详细合并的策略可以查看[混淆规则合并策略](#混淆规则合并策略)。 71 72**规则变更提醒** 73 74从DevEco Studio 5.0.3.600开始,新建工程默认关闭代码混淆功能,即`"enable": false`;混淆规则配置文件obfuscation-rules.txt中默认开启enable-property-obfuscation、-enable-toplevel-obfuscation、-enable-filename-obfuscation、-enable-export-obfuscation四项推荐的选项。这四项规则开启可能会引发应用运行时崩溃,建议阅读[排查指南](#如何排查功能异常)来修正应用功能。 75 76### 混淆规则配置文件 77 78在创建工程或library的时候,DevEco Studio会自动生成`obfuscation-rules.txt`和`consumer-rules.txt`文件。混淆规则可以写到这些文件中,或者其它自定义文件,然后将文件路径放到`ruleOptions.files`和`consumerFiles`中,如下面的例子所示。 79 80``` 81"buildOption": { 82 "arkOptions": { 83 "obfuscation": { 84 "ruleOptions": { 85 "enable": true, 86 "files": ["./obfuscation-rules.txt", "./myrules.txt"], //myrules.txt放入配置文件build-profile.json5同级目录下 87 } 88 "consumerFiles": ["./consumer-rules.txt", "./my-consumer-rules.txt"] 89 } 90 } 91} 92``` 93 94## 配置混淆规则 95 96混淆规则分为两种类型,一种是[混淆选项](#混淆选项),一种是[保留选项](#保留选项);前者是提供顶层作用域名称、属性名称、文件名称等多种混淆功能配置开关,后者是提供各种混淆功能的白名单配置能力。 97 98**注意** 99 100若修改应用混淆配置,新配置需要重新全量编译应用才能生效。 101 102### 混淆选项 103 104#### -disable-obfuscation 105 106关闭所有混淆。如果使用这个选项,那么构建出来的HAP、HSP或HAR将不会被混淆。 107 108#### -enable-property-obfuscation 109 110开启属性混淆。 如果使用这个选项,那么所有的属性名都会被混淆,除了下面场景: 111 112* 被`import/export`直接导入或导出的类、对象的属性名不会被混淆。例如下面例子中的属性名`data`不会被混淆。 113 114 ``` 115 export class MyClass { 116 data: string; 117 } 118 ``` 119 120* ArkUI组件中的属性名不会被混淆。例如下面例子中的`message`和`data`不会被混淆。 121 122 ``` 123 @Component struct MyExample { 124 @State message: string = "hello"; 125 data: number[] = []; 126 ... 127 } 128 ``` 129 130* 被[保留选项](#保留选项)指定的属性名不会被混淆。 131* SDK API列表中的属性名不会被混淆。SDK API列表是构建时从SDK中自动提取出来的一个名称列表,其缓存文件为systemApiCache.json,路径为工程目录下build/default/cache/{...}/release/obfuscation中 132* 字符串字面量属性名不会被混淆。例如下面例子中的`"name"`和`"age"`不会被混淆。 133 134 ``` 135 let person = {"name": "abc"}; 136 person["age"] = 22; 137 ``` 138 139 如果想混淆字符串字面量属性名,需要在该选项的基础上再使用`-enable-string-property-obfuscation`选项。例如 140 141 ``` 142 -enable-property-obfuscation 143 -enable-string-property-obfuscation 144 ``` 145 146 **注意**: 147 148 **1.** 如果代码里面有字符串属性名包含特殊字符(除了`a-z, A-Z, 0-9, _`之外的字符),例如`let obj = {"\n": 123, "": 4, " ": 5}`,建议不要开启`-enable-string-property-obfuscation`选项,因为可能无法通过[保留选项](#保留选项)来指定保留这些名字。 149 **2.** SDK API的属性白名单中不包含声明文件中使用的字符串常量值,例如示例中的字符串'ohos.want.action.home'未包含在属性白名单中 150 151 ``` 152 // SDK API文件@ohos.app.ability.wantConstant片段: 153 export enum Params { 154 ACTION_HOME = 'ohos.want.action.home' 155 } 156 // 开发者源码示例: 157 let params = obj['ohos.want.action.home']; 158 ``` 159 160 因此在开启了`-enable-string-property-obfuscation`选项时,如果想保留代码中使用的SDK API字符串常量的属性不被混淆,例如obj['ohos.want.action.home'], 那么需要使用keep选项保留。 161 162#### -enable-toplevel-obfuscation 163 164开启顶层作用域名称混淆。如果使用这个选项,那么所有的顶层作用域的名称都会被混淆,除了下面场景: 165 166* 被`import/export`的名称不会被混淆。 167* 当前文件找不到声明的名称不会被混淆。 168* 被[保留选项](#保留选项)指定的顶层作用域名称不会被混淆。 169* SDK API列表中的顶层作用域名称不会被混淆。 170 171#### -enable-filename-obfuscation 172 173开启文件/文件夹名称混淆。如果使用这个选项,那么所有的文件/文件夹名称都会被混淆,例如: 174 175``` 176// directory和filename都会混淆 177import func from '../directory/filename'; 178import { foo } from '../directory/filename'; 179const module = import('../directory/filename'); 180``` 181 182除了下面场景: 183 184* oh-package.json5文件中'main'、'types'字段配置的文件/文件夹名称不会被混淆。 185* 模块内module.json5文件中'srcEntry'字段配置的文件/文件夹名称不会被混淆。 186* 被[-keep-file-name](#保留选项)指定的文件/文件夹名称不会被混淆。 187* 非ECMAScript模块引用方式(ECMAScript模块示例:`import {foo} from './filename'`) 188* 非路径引用方式,例如例子中的json5不会被混淆 `import module from 'json5'` 189 190**注意**: 191 192由于系统会在应用运行时加载某些指定的文件,针对这类文件,开发者需要手动在[-keep-file-name](#保留选项)选项中配置相应的白名单,防止指定文件被混淆,导致运行失败。 193上述需要手动配置白名单的情况,包括但不限于以下场景: 194 195* 当模块中包含Ability组件时。用户需要将`src/main/module.json5`中,'abilities'字段下所有'srcEntry'对应的路径配置到白名单中。 196* 当模块中包含Worker多线程服务时,用户需要将`build-profiles.json5`中,'buildOption'-'sourceOption'-'workers'字段下所有的路径配置到白名单中。 197 198**提醒**: 199 200编译入口、Ability组件、Worker多线程,这三种不能混淆的文件名在DevEco Studio 5.0.3.500版本已被自动收集进白名单中,无需再手动配置,其它不能混淆文件名的场景仍需开发者手动配置 201 202#### -enable-export-obfuscation 203 204开启直接导入或导出的类或对象的名称和属性名混淆。如果使用这个选项,那么模块中的直接导入或导出的名称都会被混淆,除了下面场景: 205 206* 远程HAR(真实路径在oh_modules中的包)中导出的类或对象的名称和属性名不会被混淆。 207* 被[保留选项](#保留选项)指定的名称与属性名不会被混淆。 208* SDK API列表中的名称不会被混淆。 209 210**注意**: 211 2121. 混淆导入或导出的类中属性名称需要同时开启`-enable-property-obfuscation`与`-enable-export-obfuscation`选项。 2132. 编译HSP时,如果开启`-enable-export-obfuscation`选项,需要在模块中的混淆配置文件`obfuscation-rules.txt`中保留对外暴露的接口。 2143. HAP/HSP/HAR依赖HSP场景下,编译时如果开启`-enable-export-obfuscation`选项,需要在模块中的混淆配置文件`obfuscation-rules.txt`中保留HSP导入的接口。 215 216 ``` 217 // 代码示例(HSP中入口文件Index.ets): 218 export { add, customApiName } from './src/main/ets/utils/Calc' 219 220 // 保留接口名称配置示例: 221 // HSP以及依赖此HSP的模块中obfuscation-rules.txt文件配置: 222 -keep-global-name 223 add 224 customApiName 225 ``` 226 227#### -compact 228 229去除不必要的空格符和所有的换行符。如果使用这个选项,那么所有代码会被压缩到一行。 230 231**注意**: 232 233release模式构建的应用栈信息仅包含代码行号,不包含列号,因此compact功能开启后无法依据报错栈中的行号定位到源码具体位置。 234 235#### -remove-log 236 237删除以下场景中对 console.*语句的调用,要求console.*语句返回值未被调用。 238 2391. 文件顶层的调用 2402. 代码块Block中的调用 2413. 模块Module中的调用 2424. switch语句中的调用 243 244#### -print-namecache *filepath* 245 246将名称缓存保存到指定的文件路径。名称缓存包含名称混淆前后的映射。 247 248**注意**: 249 250每次全量构建工程时都会生成新的namecache.json文件,因此您每次发布新版本时都要注意保存一个该文件的副本。 251 252#### -apply-namecache *filepath* 253 254复用指定的名称缓存文件。名字将会被混淆成缓存映射对应的名字,如果没有对应,将会被混淆成新的随机段名字。 255该选项应该在增量编译场景中被使用。 256 257默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,并且在增量编译场景中自动应用该缓存文件。 258缓存目录:build/default/cache/{...}/release/obfuscation 259 260#### -remove-comments 261 262删除编译生成的声明文件中的JsDoc注释。 263 264**注意**: 265 266编译生成的源码文件中的注释默认会被全部删除,不支持配置保留。 267可通过`keep-comments`配置来保留编译生成的声明文件中的JsDoc注释。 268 269#### -print-kept-names *filepath* 270 271该选项支持输出未混淆名单和全量白名单。其中,*filepath*为可选参数。 272 273当*filepath*参数缺省时,未混淆名单(keptNames.json)和全量白名单(whitelist.json)默认输出到缓存路径`build/default/cache/{...}/release/obfuscation`中。 274 275当*filepath*配置参数时,未混淆名单还会输出到该参数指定的路径中。其中,*filepath*仅支持相对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。 276 277全量白名单(whitelist.json)包含本次模块编译流程中收集到的全部白名单,分为以下七种: 278 279(1)'sdk':表示系统api。 280 281(2)'lang':表示语言中的关键字。 282 283(3)'conf':表示用户配置的保留选项中的白名单。 284 285(4)'struct':表示ArkUI的struct中的属性。 286 287(5)'exported':表示被导出的名称及其属性。 288 289(6)'strProp': 表示字符串属性。 290 291(7)'enum':表示enum中的成员。 292 293未混淆名单(keptNames.json)中包含未混淆的名称及未混淆的原因。其中,未混淆原因有以下七种:与sdk白名单重名、与语言白名单重名、与用户配置白名单重名、与struct白名单重名、与导出白名单重名、与字符串属性白名单重名(未开启字符串属性混淆的情况下)以及与enum白名单重名。 294 295**注意**: 296 2971.在编译har模块且开启属性混淆的情况下,'enum'白名单将收集enum中的成员名称。 298 299例如: 300 301``` 302enum Test { 303 member1, 304 member2 305} 306``` 307 308enum白名单内容为['member1', 'member2']。这是由于历史版本的har模块的编译中间产物为js文件,在js文件中enum类型会转换为一个立即执行函数,而enum成员会被转化为一个字符串属性和一个字符串常量。因此,为了保证开启属性混淆的情况下功能正常,需要将enum成员名称收集为白名单。在编译新版字节码har模块时,此特性仍然被保留。 309 3102.在编译hap/hsp/字节码har模块且开启属性混淆的情况下,当enum的成员被初始化时,'enum'白名单收集初始化表达式中包含的变量名称。 311 312例如: 313 314``` 315let outdoor = 1; 316enum Test { 317 member1, 318 member2 = outdoor + member1 + 2 319} 320``` 321 322其中,编译hap/hsp模块的情况下,enum白名单内容为['outdoor', 'member1'];编译字节码har模块的情况下,enum白名单内容为['outdoor', 'member1', 'member2']。 323 324### 保留选项 325 326#### -keep-property-name *[,identifiers,...]* 327 328指定想保留的属性名,支持使用名称类通配符。例如下面的例子: 329 330``` 331-keep-property-name 332age 333firstName 334lastName 335``` 336 337**注意**: 338 339该选项在开启`-enable-property-obfuscation`时生效 340 341**哪些属性名应该被保留?** 342 343为了保障混淆的正确性,建议保留所有不通过点语法访问的属性。 344 345例子: 346 347``` 348var obj = {x0: 0, x1: 0, x2: 0}; 349for (var i = 0; i <= 2; i++) { 350 console.info(obj['x' + i]); // x0, x1, x2 应该被保留 351} 352 353Object.defineProperty(obj, 'y', {}); // y 应该被保留 354console.info(obj.y); 355 356obj.s = 0; 357let key = 's'; 358console.info(obj[key]); // s 应该被保留 359 360obj.u = 0; 361console.info(obj.u); // u 可以被正确地混淆 362 363obj.t = 0; 364console.info(obj['t']); // 在开启字符串字面量属性名混淆时t和't'会被正确地混淆,但是建议保留 365 366obj['v'] = 0; 367console.info(obj['v']); // 在开启字符串字面量属性名混淆时'v'会被正确地混淆,但是建议保留 368``` 369 370对于间接导出的场景,例如`export MyClass`和`let a = MyClass; export {a};`,如果不想混淆它们的属性名,那么需要使用[保留选项](#保留选项)来保留这些属性名。另外,对于直接导出的类或对象的属性的属性名,例如下面例子中的`name`和`age`, 如果不想混淆它们,那么也需要使用[保留选项](#保留选项)来保留这些属性名。 371 372``` 373export class MyClass { 374 person = {name: "123", age: 100}; 375} 376``` 377 378so库的API(例如示例中的foo),如果要在ArkTS/TS/JS文件中使用需手动保留API名称。 379 380``` 381import testNapi from 'library.so' 382testNapi.foo() // foo需要保留,示例如:-keep-property-name foo 383``` 384 385使用到的json文件中的字段,需要手动保留。 386 387``` 388const jsonData = ('./1.json') 389let jsonStr = JSON.parse(jsonData) 390let jsonObj = jsonStr.jsonProperty // jsonProperty 需要被保留 391``` 392 393使用到的数据库相关的字段,需要手动保留。 394 395``` 396const dataToInsert = { 397 value1: 'example1', // value1 需要被保留 398}; 399``` 400 401源码中自定义装饰器修饰了成员变量、成员方法、参数,同时其源码编译的中间产物为js文件时(如编译release源码HAR或者源码包含@ts-ignore、@ts-nocheck),这些装饰器所在的成员变量/成员方法名称需要被保留。这是由于ts高级语法特性转换为js标准语法时,将上述装饰器所在的成员变量/成员方法名称硬编码为字符串常量。 402 403示例: 404 405``` 406class A { 407 // 1.成员变量装饰器 408 @CustomDecoarter 409 propetyName: string = "" // propetyName 需要被保留 410 // 2.成员方法装饰器 411 @MethodDecoarter 412 methodName1(){} // methodName1 需要被保留 413 // 3.方法参数装饰器 414 methodName2(@ParamDecorator param: string): void { // methodName2 需要被保留 415 } 416} 417``` 418 419#### -keep-global-name *[,identifiers,...]* 420 421指定要保留的顶层作用域的名称,支持使用名称类通配符。例如, 422 423``` 424-keep-global-name 425Person 426printPersonName 427``` 428 429namespace中导出的名称也可以通过`-keep-global-name`保留。 430 431``` 432export namespace Ns { 433 export const age = 18; // -keep-global-name age 保留变量age 434 export function myFunc () {}; // -keep-global-name myFunc 保留函数myFunc 435} 436``` 437 438**哪些顶层作用域的名称应该被保留?** 439 440在JavaScript中全局变量是`globalThis`的属性。如果在代码中使用`globalThis`去访问全局变量,那么该变量名应该被保留。 441 442示例: 443 444``` 445var a = 0; 446console.info(globalThis.a); // a 应该被保留 447 448function foo(){} 449globalThis.foo(); // foo 应该被保留 450 451var c = 0; 452console.info(c); // c 可以被正确地混淆 453 454function bar(){} 455bar(); // bar 可以被正确地混淆 456 457class MyClass {} 458let d = new MyClass(); // MyClass 可以被正确地混淆 459``` 460 461当以命名导入的方式导入 so 库的 API时,若同时开启`-enable-toplevel-obfuscation`和`-enable-export-obfuscation`选项,需要手动保留 API 的名称。 462 463``` 464import { testNapi, testNapi1 as myNapi } from 'library.so' // testNapi 和 testNapi1 应该被保留 465``` 466 467#### -keep-file-name *[,identifiers,...]* 468 469指定要保留的文件/文件夹的名称(不需要写文件后缀),支持使用名称类通配符。例如, 470 471``` 472-keep-file-name 473index 474entry 475``` 476 477**哪些文件名应该被保留?** 478 479``` 480const module1 = require('./file1') // ArkTS不支持CommonJS语法,这种路径引用应该被保留 481const moduleName = './file2' 482const module2 = import(moduleName) // 动态引用方式无法识别moduleName是否是路径,应该被保留 483``` 484 485#### -keep-comments *[,identifiers,...]* 486 487保留编译生成的声明文件中class, function, namespace, enum, struct, interface, module, type及属性上方的JsDoc注释,支持使用名称类通配符。例如想保留声明文件中Human类上方的JsDoc注释,可进行以下配置: 488 489``` 490-keep-comments 491Human 492``` 493 494**注意**: 495 4961. 该选项在开启`-remove-comments`时生效 4972. 当编译生成的声明文件中class, function, namespace, enum, struct, interface, module, type及属性的名称被混淆时,该元素上方的JsDoc注释无法通过`-keep-comments`保留。例如当在`-keep-comments`中配置了exportClass时,如果exportClass类名被混淆,其JsDoc注释无法被保留: 498 499``` 500/* 501 * @class exportClass 502 */ 503export class exportClass {} 504``` 505 506#### -keep-dts *filepath* 507 508保留指定绝对路径的`.d.ts`文件中的名称。这里的文件路径也可以是一个目录,这种情况下目录中所有`.d.ts`文件中的名称都会被保留。 509 510#### -keep *filepath* 511 512保留指定相对路径中的所有名称(例如变量名、类名、属性名等)不被混淆。这个路径可以是文件与文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。 513路径仅支持相对路径,`./`与`../`为相对于混淆配置文件所在目录,支持使用路径类通配符。 514 515``` 516-keep 517./src/main/ets/fileName.ts // fileName.ts中的名称不混淆 518../folder // folder目录下文件及子文件夹中的名称都不混淆 519../oh_modules/json5 // 引用的三方库json5里所有文件中的名称都不混淆 520``` 521 522**注意**: 523 5241. 被`-keep filepath`所保留的文件,其依赖链路上的文件中导出名称及其属性都会被保留。 5252. 该功能不影响文件名混淆`-enable-filename-obfuscation`的功能。 526 527#### 保留选项支持的通配符 528 529##### 名称类通配符 530 531名称类通配符使用方式如下: 532 533| 通配符 | 含义 | 示例 | 534| ------ | ---------------------- | ------------------------------------------ | 535| ? | 匹配任意单个字符 | "AB?"能匹配"ABC"等,但不能匹配"AB" | 536| \* | 匹配任意数量的任意字符 | "\*AB\*"能匹配"AB"、"aABb"、"cAB"、"ABc"等 | 537 538**使用示例**: 539 540保留所有以a开头的属性名称: 541 542``` 543-keep-property-name 544a* 545``` 546 547保留所有单个字符的属性名称: 548 549``` 550-keep-property-name 551? 552``` 553 554保留所有属性名称: 555 556``` 557-keep-property-name 558* 559``` 560 561##### 路径类通配符 562 563路径类通配符使用方式如下: 564 565| 通配符 | 含义 | 示例 | 566| ------ | ------------------------------------------------------------------------ | ------------------------------------------------- | 567| ? | 匹配任意单个字符,除了路径分隔符`/` | "../a?"能匹配"../ab"等,但不能匹配"../a/" | 568| \* | 匹配任意数量的任意字符,除了路径分隔符`/` | "../a*/c"能匹配"../ab/c",但不能匹配"../ab/d/s/c" | 569| \*\* | 匹配任意数量的任意字符 | "../a**/c"能匹配"../ab/c",也能匹配"../ab/d/s/c" | 570| ! | 表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况 | "!../a/b/c.ets"表示除"../a/b/c.ets"以外 | 571 572**使用示例**: 573 574表示路径../a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆: 575 576``` 577-keep 578../a/b/*/c.ets 579``` 580 581表示路径../a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆: 582 583``` 584-keep 585../a/b/**/c.ets 586``` 587 588表示路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,`!`不可单独使用,只能用来排除白名单中已有的情况: 589 590``` 591-keep 592../a/b/ 593!../a/b/c.ets 594``` 595 596表示路径../a/中的所有文件(不包含子文件夹)不会被混淆: 597 598``` 599-keep 600../a/* 601``` 602 603表示路径../a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆: 604 605``` 606-keep 607../a/** 608``` 609 610表示模块内的所有文件不会被混淆: 611 612``` 613-keep 614./** 615``` 616 617**注意**: 618 619(1)以上选项,不支持配置通配符`*`、`?`、`!`作其它含义使用。 620例如: 621 622``` 623class A { 624 '*'= 1 625} 626 627-keep-property-name 628* 629``` 630 631此时`*`表示匹配任意数量的任意字符,配置效果为所有属性名称都不混淆,而不是只有`*`属性不被混淆。 632 633(2)-keep选项中只允许使用`/`路径格式,不支持`\`或`\\`。 634 635### 注释 636 637可以使用`#`在混淆规则文件中进行注释。每行以`#`开头的文本会被当做是注释,例如下面的例子: 638 639``` 640# white list for MainAbility.ets 641-keep-global-name 642MyComponent 643GlobalFunction 644 645-keep-property-name # white list for dynamic property names 646firstName 647lastName 648age 649``` 650 651构建HAR时,注释不会被合并到最后的`obfuscation.txt`文件中。 652 653### 混淆规则合并策略 654 655一个工程中经常会有许多混淆规则文件,这些文件来自于: 656 657* 主工程的`ruleOptions.files` (这里主工程指的是正在构建的工程) 658* 本地依赖的library中的`consumerFiles`选项中指定的文件 659* 远程依赖的HAR包中的`obfuscation.txt`文件 660 661当构建主工程的时候,这些文件中的混淆规则会按照下面的合并策略(伪代码)进行合并: 662 663``` 664let `listRules` 表示上面提到的所有混淆规则文件的列表 665let finalRule = { 666 disableObfuscation: false, 667 enablePropertyObfuscation: false, 668 enableToplevelObfuscation: false, 669 compact: false, 670 removeLog: false, 671 keepPropertyName: [], 672 keepGlobalName: [], 673 keepDts: [], 674 printNamecache: string, 675 applyNamecache: string 676} 677for each file in `listRules`: 678 for each option in file: 679 switch(option) { 680 case -disable-obfuscation: 681 finalRule.disableObfuscation = true; 682 continue; 683 case -enable-property-obfuscation: 684 finalRule.enablePropertyObfuscation = true; 685 continue; 686 case -enable-toplevel-obfuscation: 687 finalRule.enableToplevelObfuscation = true; 688 continue; 689 case -compact: 690 finalRule.compact = true; 691 continue; 692 case -remove-log: 693 finalRule.removeLog = true; 694 continue; 695 case -print-namecache: 696 finalRule.printNamecache = #{指定的路径名}; 697 continue; 698 case -apply-namecache: 699 finalRule.applyNamecache = #{指定的路径名}; 700 continue; 701 case -keep-property-name: 702 finalRule.keepPropertyName.push(#{指定的名称}); 703 continue; 704 case -keep-global-name: 705 finalRule.keepGlobalName.push(#{指定的名称}); 706 continue; 707 case -keep-dts: 708 finalRule.keepDts.push(#{指定的路径}); 709 continue; 710 } 711 end-for 712end-for 713``` 714 715最后使用的混淆规则来自于对象`finalRule`。 716 717如果构建的是HAR,那么最终的`obfuscation.txt`文件内容来自于自身和本地依赖的library的`consumerFiles`选项,以及依赖的HAR的`obfuscation.txt`文件的合并。 718 719当`consumerFiles`指定的混淆配置文件中包含以下混淆规则时,这些混淆规则会被合并到HAR包的`obfuscation.txt`文件中,而其他混淆规则不会。 720 721``` 722// 混淆选项 723-enable-property-obfuscation 724-enable-string-property-obfuscation 725-enable-toplevel-obfuscation 726-compact 727-remove-log 728 729// 保留选项 730-keep-property-name 731-keep-global-name 732``` 733 734**library中混淆注意事项** 735 7361. 如果`consumerFiles`指定的混淆配置文件中包含上述混淆选项,当其他模块依赖该HAR包时,这些混淆选项会与主模块的混淆规则合并,从而影响主模块。因此不建议开发者在`consumer-rules.txt`文件中配置混淆选项,建议仅配置保留选项。 737 7382. 如果在`consumerFiles`指定的混淆配置文件中添加`-keep-dts`选项,会被转换成`-keep-global-name`和`-keep-property-name`。 739 740## 报错栈还原 741 742经过混淆的应用程序中代码名称会发生更改,crash时打印的报错栈更难以理解,因为报错栈与源码不完全一致。开发人员可使用DevEco Studio命令工具Command Line Tools中的hstack插件来还原源码堆栈,进而分析问题。反混淆工具需要使用应用编译过程中生成的sourceMaps.map文件以及混淆名称映射文件nameCache.json文件,因此请本地备份它们。 743 744 745 746## 说明 747 748* 目前不支持在hvigor构建流程中插入自定义混淆插件 749* 混淆的HAR包被模块依赖,若模块开启混淆,则HAR包会被二次混淆 750* DevEco Studio右上角Product选项,将其中Build Mode选择release,可开启release编译模式 751 752 753## FAQ 754 755### 混淆各功能上线SDK版本 756 757| 混淆选项 | 功能描述 | 最低版本号 | 758| ------- | --------- | ------ | 759| -disable-obfuscation | 关闭混淆 | 4.0.9.2 | 760| -enable-property-obfuscation | 属性混淆 | 4.0.9.2 | 761| -enable-string-property-obfuscation | 字符串字面量属性名混淆 | 4.0.9.2 | 762| -enable-toplevel-obfuscation | 顶层作用域名称混淆 | 4.0.9.2 | 763| -enable-filename-obfuscation | HAR包文件/文件夹名称混淆 <br> HAP/HSP文件/文件夹名称混淆 | 4.1.5.3 <br> 5.0.0.19 | 764| -enable-export-obfuscation | 向外导入或导出的名称混淆 | 4.1.5.3 | 765| -compact | 去除不必要的空格符和所有的换行符 | 4.0.9.2 | 766| -remove-log | 删除特定场景中的console.* | 4.0.9.2 | 767| -print-namecache | 将名称缓存保存到指定的文件路径 | 4.0.9.2 | 768| -apply-namecache | 复用指定的名称缓存文件 | 4.0.9.2 | 769| -remove-comments | 删除文件中所有注释 | 4.1.5.3 | 770| -keep-property-name | 保留属性名 | 4.0.9.2 | 771| -keep-global-name | 保留顶层作用域的名称 | 4.0.9.2 | 772| -keep-file-name | 保留HAR包的文件/文件夹的名称 <br> 保留HAP/HSP包的文件/文件夹的名称 | 4.1.5.3 <br> 5.0.0.19 | 773| -keep-dts | 保留指定路径的.d.ts文件中的名称 | 4.0.9.2 | 774| -keep-comments | 保留编译生成的声明文件中class, function, namespace, enum, struct, interface, module, type及属性上方的JsDoc注释 | 4.1.5.3 | 775| -keep | 保留指定路径中的所有名称 | 5.0.0.18 | 776| 通配符 | 名称类和路径类的保留选项支持通配符 | 5.0.0.24 | 777 778### 如何查看混淆效果 779 780开发人员可以在编译产物build目录中找到混淆后的文件,以及混淆生成的名称映射表及系统API白名单文件。 781 782* 混淆后的文件目录:`build/default/[...]/release/模块名` 783* 混淆名称映射表及系统API白名单目录:`build/default/[...]/release/obfuscation` 784 * 名称映射表文件:nameCache.json,该文件记录了源码名称混淆的映射关系。 785 * 系统API白名单文件:systemApiCache.json,该文件记录了SDK中的接口与属性名称,与其重名的源码不会被混淆。 786 787  788 789### 如何排查功能异常 790 7911. 先在obfuscation-rules.txt配置-disable-obfuscation选项关闭混淆,确认问题是否由混淆引起。 7922. 若确认是开启混淆后功能出现异常,请先阅读文档了解[-enable-property-obfuscation](#混淆选项)、[-enable-toplevel-obfuscation](#混淆选项)、[-enable-filename-obfuscation](#混淆选项)、[-enable-export-obfuscation](#混淆选项)等混淆规则的能力以及哪些语法场景需要配置白名单来保证应用功能正常。下文简要介绍默认开启的四项选项功能,细节还请阅读对应选项的完整描述。 793 1.[-enable-toplevel-obfuscation](#混淆选项)为顶层作用域名称混淆开关。 794 2.[-enable-property-obfuscation](#混淆选项)为属性混淆开关,配置白名单的主要场景为网络数据访问、json字段访问、动态属性访问、调用so库接口等不能混淆场景,需要使用[-keep-property-name](#保留选项)来保留指定的属性名称。 795 3.[-enable-export-obfuscation](#混淆选项)为导出名称混淆,一般与1、2选项配合使用;配置白名单的主要场景为模块对外接口不能混淆,需要使用[-keep-global-name](#保留选项)来指定保留导出/导入名称。 796 4.[-enable-filename-obfuscation](#混淆选项)为文件名混淆,配置白名单的主要场景为动态import或运行时直接加载的文件路径,需要使用[-keep-file-name](#保留选项)来保留这些文件路径及名称。 7973. 参考FAQ中的[常见报错案例](#常见报错案例),若是相似场景可参考对应的解决方法快速解决。 7984. 若常见案例中未找到相似案例,建议依据各项配置功能正向定位(若不需要相应功能,可删除对应配置项)。 7995. 应用运行时崩溃分析方法: 800 1.打开应用运行日志或者点击DevEco Studio中出现的Crash弹窗,找到运行时崩溃栈。 801 2.应用运行时崩溃栈中的行号为[编译产物](#如何查看混淆效果)的行号,方法名也可能为混淆后名称;因此排查时建议直接根据崩溃栈查看编译产物,进而分析哪些名称不能被混淆,然后将其配置进白名单中。 8026. 应用在运行时未崩溃但出现功能异常的分析方法(比如白屏): 803 1.打开应用运行日志:选择HiLog,检索与功能异常直接相关的日志,定位问题发生的上下文。 804 2.定位异常代码段:通过分析日志,找到导致功能异常的具体代码块。 805 3.增强日志输出:在疑似异常的功能代码中,对处理的数据字段增加日志记录。 806 4.分析并确定关键字段:通过对新增日志输出的分析,识别是否由于混淆导致该字段的数据异常。 807 5.配置白名单保护关键字段:将确认在混淆后对应用功能产生直接影响的关键字段添加到白名单中。 808 809### 常见报错案例 810 811#### 开启-enable-property-obfuscation选项可能出现的问题 812 813**案例一:报错内容为 Cannot read property 'xxx' of undefined** 814 815``` 816// 混淆前 817const jsonData = ('./1.json') 818let jsonStr = JSON.parse(jsonData) 819let jsonObj = jsonStr.jsonProperty 820 821// 混淆后 822const jsonData = ('./1.json') 823let jsonStr = JSON.parse(jsonData) 824let jsonObj = jsonStr.i 825``` 826 827开启属性混淆后,"jsonProperty" 被混淆成随机字符 "i",但json文件中为原始名称,从而导致值为undefined。 828 829**解决方案:** 使用`-keep-property-name`选项将json文件里的字段配置到白名单。 830 831**案例二:使用了数据库相关的字段,开启属性混淆后,出现报错** 832 833报错内容为 `table Account has no column named a23 in 'INSET INTO Account(a23)'` 834 835代码里使用了数据库字段,混淆时该SQL语句中字段名称被混淆,但数据库中字段为原始名称,从而导致报错。 836 837**解决方案:** 使用`-keep-property-name`选项将使用到的数据库字段配置到白名单。 838 839#### 开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项可能出现的问题 840 841**当开启这两个选项时,主模块调用其他模块方法时涉及的方法名称混淆情况如下:** 842 843| 主模块 | 依赖模块 | 导入与导出的名称混淆情况 | 844| ------- | ------- | ----------------------------| 845| HAP/HSP | HSP | HSP和主模块是独立编译的,混淆后名称会不一致,因此都需要配置白名单 | 846| HAP/HSP | 本地HAR | 本地HAR与主模块一起编译,混淆后名称一致 | 847| HAP/HSP | 三方库 | 三方库中导出的名称及其属性会被收集到白名单,因此导入和导出时都不会被混淆 | 848 849HSP需要将给其他模块用的方法配置到白名单中。因为主模块里也需要配置相同的白名单,所以推荐将HSP配置了白名单的混淆文件(假设名称为hsp-white-list.txt)添加到依赖它的模块的混淆配置项里,即下图files字段里。 850 851 852 853**案例一:动态导入某个类,类定义的地方被混淆,导入类名时却没有混淆,导致报错** 854 855``` 856// 混淆前 857export class Test1 {} 858 859let mytest = (await import('./file')).Test1 860 861// 混淆后 862export class w1 {} 863 864let mytest = (await import('./file')).Test1 865``` 866 867导出的类 "Test1" 是一个顶层作用域名,当 "Test1" 被动态使用时,它是一个属性。因为没有开启`-enable-property-obfuscation`选项,所以名称混淆了,但属性没有混淆。 868 869**解决方案:** 使用`-keep-global-name`选项将 "Test1" 配置到白名单。 870 871**案例二:在使用namespace中的方法时,该方法定义的地方被混淆了,但使用的地方却没有被混淆,导致报错** 872 873``` 874// 混淆前 875export namespace ns1 { 876 export class person1 {} 877} 878 879import {ns1} from './file1' 880let person1 = new ns1.person1() 881 882// 混淆后 883export namespace a3 { 884 export class b2 {} 885} 886 887import {a3} from './file1' 888let person1 = new a3.person1() 889``` 890 891namespace里的 "person1" 属于顶层作用域的class名称,通过 "ns1.person1" 来调用时,它是属于一个属性,由于未开启属性混淆,所以在使用它时没有被混淆。 892 893**解决方案:** 894 8951. 开启`-enable-property-obfuscation`选项。 8962. 将namespace里导出的方法使用`-keep-global-name`选项添加到白名单。 897 898**案例三:使用了`declare global`,混淆后报语法错误** 899 900``` 901// 混淆前 902declare global { 903 var age : string 904} 905 906// 混淆后 907declare a2 { 908 var b2 : string 909} 910``` 911 912报错内容为 `SyntaxError: Unexpected token` 913 914**解决方案:** 使用`-keep-global-name`选项将`__global`配置到白名单中。 915 916#### 未开启-enable-string-property-obfuscation混淆选项,字符串字面量属性名却被混淆,导致字符串字面量属性名的值为undefined 917 918``` 919person["age"] = 22; // 混淆前 920 921person["b"] = 22; // 混淆后 922``` 923 924**解决方案:** 925 9261. 确认是否有依赖的HAR包开启了字符串属性名混淆,若开启了,则会影响主工程,需将其关闭。 9272. 若不能关闭`-enable-string-property-obfuscation`选项,将属性名配置到白名单中。 9283. 若依赖HAR包未开启字符串属性名混淆,同时SDK版本小于4.1.5.3,请更新SDK。 929 930#### 开启-enable-filename-obfuscation选项后,可能会出现的问题 931 932**案例一:报错为 Error Failed to get a resolved OhmUrl for 'D:code/MyApplication/f12/library1/pages/d.ets' imported by 'undefined'** 933 934工程的目录结构如下图所示,模块library1的外层还有目录 "directory",开启文件名混淆后,"directory" 被混淆为f12,导致路径找不到。 935 936 937 938**解决方案:** 939 9401. 如果工程的目录结构和报错内容都相似,请将SDK更新至最低5.0.0.26版本。 9412. 使用`-keep-file-name`将模块外层的目录名 "directory" 配置到白名单中。 942 943**案例二:报错为 Cannot find module 'ets/appability/AppAbility' which is application Entry Point** 944 945由于系统会在应用运行时加载ability文件,用户需要手动配置相应的白名单,防止指定文件被混淆,导致运行失败。 946 947**解决方案:** 使用`-keep-file-name`选项,将`src/main/module.json5`文件中,'srcEntry'字段所对应的路径配置到白名单中。 948 949``` 950-keep-file-name 951appability 952AppAbility 953``` 954 955#### 使用-keep-global-name选项配置白名单时,可能会出现的问题 956 957报错内容为 `Cannot read properties of undefined (reading 'has')` 958 959**解决方案:** 将SDK更新至最低4.1.6.3版本。 960