1# Arkguard 2Arkguard 是Javascript和Typescript的源码混淆工具。 3 4# 在DevEco Studio中的用法 5Arkguard已经被集成了到SDK中。可以在DevEco Studio中很方便地使用。Arkguard只能用于Stage模型 6(不支持FA模型)。目前Arkguard只提供名称混淆的能力(因为其它混淆能力会劣化性能)。 7使用Arkguard可以混淆以下名称: 8* 参数名和局部变量名 9* 顶层作用域的名称 10* 属性名称 11 12Arkguard默认使能对参数名和局部变量名的混淆。顶层作用域名称和属性名称的混淆是默认关闭的, 13因为默认打开可能会导致运行时错误。你可以通过[混淆选项](#混淆选项)来开启它们。 14 15创建一个新工程的时候,配置文件`build-profile.json5`中会自动生成以下内容: 16``` 17"arkOptions": { 18 "obfuscation": { 19 "ruleOptions": { 20 "enable": true, 21 "files": ["obfuscation-rules.txt"], 22 } 23 } 24} 25``` 26创建一个新的library的时候,还会额外生成`consumerFiles`属性: 27``` 28"arkOptions": { 29 "obfuscation": { 30 "ruleOptions": { 31 "enable": true, 32 "files": ["obfuscation-rules.txt"], 33 } 34 "consumerFiles": ["consumer-rules.txt"] 35 } 36} 37``` 38 39要想开启混淆,需要满足下面的条件: 40* 属性`ruleOptions.enable`的值为`true`,并且所有依赖的library的`ruleOptions.enable`属性是`true` 41* 在release模式构建 42 43属性`ruleOptions.files`中指定的混淆配置文件会在构建HAP或HAR的时候被应用。 44 45属性`consumerFiles`中指定的混淆配置文件会在构建依赖这个library的工程或library时被应用。 46这些混淆配置文件的内容还会被合并到HAR包中的`obfuscation.txt`文件。 47 48当构建HAP或者HAR的时候,最终的混淆规则是自身的`ruleOptions.files`属性,依赖的library的`consumerFiles`属性, 49以及依赖的HAR包中的`obfuscation.txt`文件的合并。如果构建的是HAR,`obfuscation.txt`是自身的`consumerFiles`属性, 50依赖的library的`consumerFiles`属性,以及依赖的HAR包中的`obfuscation.txt`文件的合并。 51构建HAP不会生成`obfuscation.txt`。详细合并的策略可以查看[混淆规则合并策略](#混淆规则合并策略)。 52 53## 配置混淆规则 54在创建工程或library的时候,DevEco Studio会自动生成`obfuscation-rules.txt`和`consumer-rules.txt`文件, 55但是它们默认不会包含任何混淆规则。你可以在这些文件中写混淆规则,或者也可以将规则写在其它文件, 56然后将文件路径放到`ruleOptions.files`和`consumerFiles`中,如下面的例子所示。 57``` 58"buildOption": { 59 "arkOptions": { 60 "obfuscation": { 61 "ruleOptions": { 62 "enable": true, 63 "files": ["obfuscation-rules.txt", "myrules.txt"], 64 } 65 "consumerFiles": ["consumer-rules.txt", "my-consumer-rules.txt"] 66 } 67 } 68} 69``` 70 71在混淆规则文件中,你可以写[混淆选项](#混淆选项)和[保留选项](#保留选项)。 72 73### 混淆选项 74 75`-disable-obfuscation` 76 77关闭所有混淆。如果你使用这个选项,那么构建出来的HAR或HAR将不会被混淆。默认情况下, 78Arkguard只混淆参数名和局部变量名(通过将它们重新命名为随机的短名字)。 79 80`-enable-property-obfuscation` 81 82开启属性混淆。 如果你使用这个选项,那么所有的属性名都会被混淆,除了下面场景: 83* 被`import/export`的类或对象的属性名不会被混淆。注意: 只有直接导出的类或对象的属性名不会被混淆。 84比如下面例子中的属性名`data`不会被混淆。 85 ``` 86 export class MyClass { 87 data: string; 88 } 89 ``` 90对于间接导出的场景,比如`export MyClass`和`let a = MyClass; export a;`,如果你不想混淆它们的属性名, 91那么你需要使用[保留选项](#保留选项)来保留这些属性名。另外,对于直接导出的类或对象的属性的属性名,比如下面例子中的`name`和`age`, 如果你不想混淆它们,那么你也需要使用[保留选项](#保留选项)来保留这些属性名。 92 ``` 93 export class MyClass { 94 person = {name: "123", age: 100}; 95 } 96 ``` 97* ArkUI组件中的属性名不会被混淆。比如下面例子中的`message`和`data`不会被混淆。 98 ``` 99 @Component struct MyExample { 100 @State message: string = "hello"; 101 data: number[] = []; 102 ... 103 } 104 ``` 105* 被[保留选项](#保留选项)指定的属性名不会被混淆。 106* 系统API列表中的属性名不会被混淆。系统API列表是构建时从SDK中自动提取出来的一个名称列表。 107* 字符串字面量属性名不会被混淆。比如下面例子中的`"name"`和`"age"`不会被混淆。 108 ``` 109 let person = {"name": "abc"}; 110 person["age"] = 22; 111 ``` 112 如果你想混淆字符串字面量属性名,你需要在该选项的基础上再使用`-enable-string-property-obfuscation`选项。比如 113 ``` 114 -enable-property-obfuscation 115 -enable-string-property-obfuscation 116 ``` 117 注意:如果你的代码里面有字符串属性名包含特殊字符(除了`a-z, A-Z, 0-9, _`之外的字符),比如`let obj = {"\n": 123, "": 4, " ": 5}`,我们建议不要开启`-enable-string-property-obfuscation`选项,因为当你不想混淆这些名字时,可能无法通过[保留选项](#保留选项)来指定保留这些名字。 118 119`-enable-toplevel-obfuscation` 120 121开启顶层作用域名称混淆。如果你使用这个选项,那么所有的顶层作用域的名称都会被混淆,除了下面场景: 122* 被`import/export`的名称不会被混淆。 123* 当前文件找不到声明的名称不会被混淆。 124* 被[保留选项](#保留选项)指定的顶层作用域名称不会被混淆。 125* 系统API列表中的顶层作用域名称不会被混淆。 126 127`-compact` 128 129去除不必要的空格符和所有的换行符。如果你使用这个选项,那么所有代码会被压缩到一行。 130 131`-remove-log` 132 133删除所有`console.*`语句。 134 135`-print-namecache` filepath 136 137将名称缓存保存到指定的文件路径。名称缓存包含名称混淆前后的映射。如果你使用了`-enable-property-obfuscation`或 138`-enable-toplevel-obfuscation`选项,并且你希望未来进行增量编译(比如热修复),那么你应该使用这个选项, 139并且将缓存文件保管好。 140 141`-apply-namecache` filepath 142 143复用指定的名称缓存文件。名字将会被混淆成缓存映射对应的名字,如果没有对应,将会被混淆成新的随机段名字。 144该选项应该在增量编译场景中被使用。 145 146默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,并且在增量编译场景中自动应用该缓存文件。 147 148### 保留选项 149 150保留选项只有在使用`enable-property-obfuscation`或`enable-toplevel-obfuscation`选项时发挥作用。 151 152`-keep-property-name` [,modifiers,...] 153 154指定你想保留的属性名。比如下面的例子: 155``` 156-keep-property-name 157age 158firstName 159lastName 160``` 161 162**哪些属性名应该被保留?** 163 164为了保障混淆的正确性,我们建议你保留所有不通过点语法访问的属性。 165 166例子: 167``` 168var obj = {x0: 0, x1: 0, x2: 0}; 169for (var i = 0; i <= 2; i++) { 170 console.log(obj['x' + i]); // x0, x1, x2 应该被保留 171} 172 173Object.defineProperty(obj, 'y', {}); // y 应该被保留 174console.log(obj.y); 175 176obj.s = 0; 177let key = 's'; 178console.log(obj[key]); // s 应该被保留 179 180obj.u = 0; 181console.log(obj.u); // u 可以被正确地混淆 182 183obj.t = 0; 184console.log(obj['t']); // 在开启字符串字面量属性名混淆时t和't'会被正确地混淆,但是我们建议保留 185 186obj['v'] = 0; 187console.log(obj['v']); // 在开启字符串字面量属性名混淆时'v'会被正确地混淆,但是我们建议保留 188``` 189 190`-keep-global-name` [,modifiers,...] 191 192指定要保留的顶层作用域的名称。比如, 193``` 194-keep-global-name 195Person 196printPersonName 197``` 198 199**哪些顶层作用域的名称应该被保留?** 200 201在Javascript中全局变量是`globalThis`的属性。如果在代码中使用`globalThis`去访问全局变量,那么该变量名应该被保留。 202 203例子: 204``` 205var a = 0; 206console.log(globalThis.a); // a 应该被保留 207 208function foo(){} 209globalThis.foo(); // foo 应该被保留 210 211var c = 0; 212console.log(c); // c 可以被正确地混淆 213 214function bar(){} 215bar(); // bar 可以被正确地混淆 216 217class MyClass {} 218let d = new MyClass(); // MyClass 可以被正确地混淆 219``` 220 221`-keep-dts` filepath 222 223保留指定路径的`.d.ts`文件中的名称。这里的文件路径可以是一个目录,这种情况下目录中所有`.d.ts`文件中的名称都会被保留。 224如果在构建HAR时使用了这个选项,那么文件中的名称会被合并到最后的`obfuscation.txt`文件中。 225 226### 注释 227 228可以使用`#`在混淆规则文件中进行注释。每行以`#`开头的文本会被当做是注释,比如下面的例子: 229``` 230# white list for MainAbility.ets 231-keep-global-name 232MyComponent 233GlobalFunction 234 235-keep-property-name # white list for dynamic property names 236firstName 237lastName 238age 239``` 240构建HAR时,注释不会被合并到最后的`obfuscation.txt`文件中。 241 242### 混淆规则合并策略 243一个工程中经常会有许多混淆规则文件,这些文件来自于: 244* 主工程的`ruleOptions.files` (这里主工程我们指的是正在构建的工程) 245* 本地依赖的library中的`consumerFiles`选项中指定的文件 246* 远程依赖的HAR包中的`obfuscate.txt`文件 247 248当构建主工程的时候,这些文件中的混淆规则会按照下面的合并策略(伪代码)进行合并: 249``` 250let `listRules` 表示上面提到的所有混淆规则文件的列表 251let finalRule = { 252 disableObfuscation: false, 253 enablePropertyObfuscation: false, 254 enableToplevelObfuscation: false, 255 compact: false, 256 removeLog: false, 257 keepPropertyName: [], 258 keepGlobalName: [], 259 keepDts: [], 260 printNamecache: string, 261 applyNamecache: string 262} 263for each file in `listRules`: 264 for each option in file: 265 switch(option) { 266 case -disable-obfuscation: 267 finalRule.disableObfuscation = true; 268 continue; 269 case -enable-property-obfuscation: 270 finalRule.enablePropertyObfuscation = true; 271 continue; 272 case -enable-toplevel-obfuscation: 273 finalRule.enableToplevelObfuscation = true; 274 continue; 275 case -compact: 276 finalRule.compact = true; 277 continue; 278 case -remove-log: 279 finalRule.removeLog = true; 280 continue; 281 case -print-namecache: 282 finalRule.printNamecache = #{指定的路径名}; 283 continue; 284 case -apply-namecache: 285 finalRule.applyNamecache = #{指定的路径名}; 286 continue; 287 case -keep-property-name: 288 finalRule.keepPropertyName.push(#{指定的名称}); 289 continue; 290 case -keep-global-name: 291 finalRule.keepGlobalName.push(#{指定的名称}); 292 continue; 293 case -keep-dts: 294 finalRule.keepDts.push(#{指定的路径}); 295 continue; 296 } 297 end-for 298end-for 299``` 300最后使用的混淆规则来自于对象`finalRule`。 301 302如果构建的是HAR,那么最终的`obfuscate.txt`文件内容来自于主工程和本地依赖的library的`consumerFiles`选项, 303以及依赖的HAR的`obfuscate.txt`文件的合并。合并策略和上面一样,除了以下的不同: 304* `-keep-dts`选项会被转换成`-keep-global-name`和`-keep-property-name`。 305* `-print-namecache`和`apply-namecache`选项会被忽略,不会出现在最后的`obfuscate.txt`文件中。 306