├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── megatronking │ │ └── stringfog │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── megatronking │ │ │ └── stringfog │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── github │ └── megatronking │ └── stringfog │ └── ExampleUnitTest.java ├── assets └── flow.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── maven_local.gradle ├── maven_nexus.gradle ├── settings.gradle ├── stringfog-core ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ └── java │ └── com │ └── github │ └── megatronking │ └── stringfog │ └── plugin │ ├── ClassStringField.java │ ├── ClassVisitorFactory.java │ ├── StringFogClassGenerator.java │ ├── StringFogClassVisitor.java │ ├── StringFogMappingPrinter.java │ ├── StringFogMode.java │ ├── WhiteLists.java │ ├── kg │ ├── HardCodeKeyGenerator.java │ └── RandomKeyGenerator.java │ └── utils │ ├── Log.java │ ├── MD5.java │ ├── SetUtils.java │ └── TextUtils.java ├── stringfog-ext └── xor │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ └── src │ └── main │ └── java │ └── com │ └── github │ └── megatronking │ └── stringfog │ └── xor │ └── StringFogImpl.java ├── stringfog-gradle-plugin ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── megatronking │ │ └── stringfog │ │ └── plugin │ │ ├── SourceGeneratingTask.kt │ │ ├── StringFogExtension.kt │ │ ├── StringFogInstrumentationParams.kt │ │ ├── StringFogPlugin.kt │ │ └── StringFogTransform.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── stringfog.properties └── stringfog-interface ├── .gitignore ├── build.gradle ├── gradle.properties └── src └── main └── java └── com └── github └── megatronking └── stringfog ├── Base64.java ├── IKeyGenerator.java ├── IStringFog.java ├── StringFogWrapper.java └── annotation └── StringFogIgnore.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | /maven 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016, Megatron King 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | Contact GitHub API Training Shop Blog About 204 | © 2016 GitHub, Inc. Terms Privacy Security Stat 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # StringFog 3 | 一款自动对dex/aar/jar文件中的字符串进行加密Android插件工具,正如名字所言,给字符串加上一层雾霭,使人难以窥视其真面目。 4 | 5 | - 支持java/kotlin。 6 | - 支持app打包生成的apk加密。 7 | - 支持aar和jar等库文件加密。 8 | - 支持加解密算法的自主扩展。 9 | - 支持配置可选代码加密。 10 | - 完全Gradle自动化集成。 11 | - 不支持InstantRun。 12 | 13 | **一些提示** 14 | > 由于我目前主要精力在[Reqable](https://reqable.com)创业项目上,StringFog的Issue处理没那么及时,非常抱歉! 15 | 虽然我已经很久不从事Android项目的开发,但是还是会尽力将StringFog一直维护下去,如果您发现了一些可以修复的问题,欢迎提交PR。 16 | 17 | ### 原理 18 | 19 | ![](https://github.com/MegatronKing/StringFog/blob/master/assets/flow.png)
20 | 21 | - 加密前: 22 | ```java 23 | String a = "This is a string!"; 24 | ``` 25 | 26 | - 加密后: 27 | ```java 28 | String a = StringFog.decrypt(new byte[]{-113, 71...}, new byte[]{-23, 53}); 29 | 30 | ``` 31 | 32 | - 运行时: 33 | ```java 34 | decrypt: new byte[]{-113, 71...} => "This is a string!" 35 | ``` 36 | 37 | ### 混淆 38 | StringFog和混淆完全不冲突,也不需要配置反混淆,实际上StringFog配上混淆效果会更好! 39 | 40 | ### 使用 41 | 由于开发了gradle插件,所以在集成时非常简单,不会影响到打包的配置。插件已经上传到MavenCentral,直接引用依赖就可以。 42 | **jcenter已经废弃,3.0+版本取消发布** 43 | 44 | ##### 1、在根目录build.gradle中引入插件依赖。 45 | ```groovy 46 | buildscript { 47 | repositories { 48 | mavenCentral() 49 | } 50 | dependencies { 51 | ... 52 | classpath 'com.github.megatronking.stringfog:gradle-plugin:5.2.0' 53 | // 选用加解密算法库,默认实现了xor算法,也可以使用自己的加解密库。 54 | classpath 'com.github.megatronking.stringfog:xor:5.0.0' 55 | } 56 | } 57 | ``` 58 | 59 | ##### 2、在app或lib的build.gradle中配置插件。 60 | ```groovy 61 | apply plugin: 'stringfog' 62 | 63 | // 导入RandomKeyGenerator类,如果使用HardCodeKeyGenerator,更换下类名 64 | import com.github.megatronking.stringfog.plugin.kg.RandomKeyGenerator 65 | import com.github.megatronking.stringfog.plugin.StringFogMode 66 | 67 | stringfog { 68 | // 必要:加解密库的实现类路径,需和上面配置的加解密算法库一致。 69 | implementation 'com.github.megatronking.stringfog.xor.StringFogImpl' 70 | // 可选:StringFog会自动尝试获取packageName,如果遇到获取失败的情况,可以显式地指定。 71 | packageName 'com.github.megatronking.stringfog.app' 72 | // 可选:加密开关,默认开启。 73 | enable true 74 | // 可选:指定需加密的代码包路径,可配置多个,未指定将默认全部加密。 75 | fogPackages = ['com.xxx.xxx'] 76 | // 可选(3.0版本新增):指定密钥生成器,默认使用长度8的随机密钥(每个字符串均有不同随机密钥), 77 | // 也可以指定一个固定的密钥:HardCodeKeyGenerator("This is a key") 78 | kg new RandomKeyGenerator() 79 | // 可选(4.0版本新增):用于控制字符串加密后在字节码中的存在形式, 默认为base64, 80 | // 也可以使用text或者bytes 81 | mode StringFogMode.base64 82 | } 83 | ``` 84 | 85 | kts中配置参考 86 | ```kotlin 87 | plugins { 88 | //...lib or application 89 | id("stringfog") 90 | } 91 | apply(plugin = "stringfog") 92 | 93 | configure { 94 | // 必要:加解密库的实现类路径,需和上面配置的加解密算法库一致。 95 | implementation = "com.github.megatronking.stringfog.xor.StringFogImpl" 96 | // 可选:加密开关,默认开启。 97 | enable = true 98 | // 可选:指定需加密的代码包路径,可配置多个,未指定将默认全部加密。 99 | // fogPackages = arrayOf("com.xxx.xxx") 100 | kg = com.github.megatronking.stringfog.plugin.kg.RandomKeyGenerator() 101 | // base64或者bytes 102 | mode = com.github.megatronking.stringfog.plugin.StringFogMode.bytes 103 | } 104 | ``` 105 | 106 | ##### 3、在app或lib的build.gradle中引入加解密库依赖。 107 | 108 | ```groovy 109 | dependencies { 110 | ... 111 | // 这里要和上面选用的加解密算法库一致,用于运行时解密。 112 | compile 'com.github.megatronking.stringfog:xor:5.0.0' 113 | } 114 | ``` 115 | 116 | ##### 注意事项 117 | 从AGP 8.0开始,默认不生成BuildConfig,但是StringFog依赖此配置,请注意加上下面的配置。 118 | ```kotlin 119 | android { 120 | // 注意请加上此配置 121 | buildFeatures { 122 | buildConfig = true 123 | } 124 | ... 125 | } 126 | ``` 127 | 128 | ### 扩展 129 | 130 | #### 注解反加密 131 | 如果开发者有不需要自动加密的类,可以使用注解StringFogIgnore来忽略: 132 | ```java 133 | @StringFogIgnore 134 | public class Test { 135 | ... 136 | } 137 | ``` 138 | #### 自定义加解密算法实现 139 | 实现IStringFog接口,参考stringfog-ext目录下面的xor算法实现。 140 | 注意某些算法在不同平台上会有差异,可能出现在运行时无法正确解密的问题。如何集成请参考下方范例! 141 | ```java 142 | public final class StringFogImpl implements IStringFog { 143 | 144 | @Override 145 | public byte[] encrypt(String data, byte[] key) { 146 | // 自定义加密 147 | } 148 | 149 | @Override 150 | public String decrypt(byte[] data, byte[] key) { 151 | // 自定义解密 152 | } 153 | 154 | @Override 155 | public boolean shouldFog(String data) { 156 | // 控制指定字符串是否加密 157 | // 建议过滤掉不重要或者过长的字符串 158 | return true; 159 | } 160 | 161 | } 162 | 163 | ``` 164 | 165 | #### 自定义密钥生成器 166 | 实现IKeyGenerator接口,参考RandomKeyGenerator的实现。 167 | 168 | #### Mapping文件 169 | **注意⚠️:StringFog 5.x版本起有问题,已暂时停用此功能** 170 | 加解密的字符串明文和暗文会自动生成mapping映射文件,位于outputs/mapping/stringfog.txt。 171 | 172 | ## 范例 173 | - 默认加解密算法集成,参考[sample1](https://github.com/MegatronKing/StringFog-Sample1) 174 | - 自定义加解密算法集成,参考[sample2](https://github.com/MegatronKing/StringFog-Sample2) 175 | 176 | ## 更新日志 177 | 178 | ### v5.2.0 179 | - 从ASM7升级到ASM9。 180 | - 修复多模块配置问题。 181 | 182 | ### v5.1.0 183 | - 修复获取无法获取packageName的问题。 184 | - 修复无法指定KeyGenerator的问题。 185 | - 优化生成StringFog.java文件的任务逻辑。 186 | - 暂时移除Mapping文件生成逻辑,可能导致无法删除的问题。 187 | 188 | ### v5.0.0 189 | - 支持Gradle 8.0。 190 | 191 | ### v4.0.1 192 | - 修复Base64 API版本兼容问题。 193 | 194 | ### v4.0.0 195 | - 使用ASM7以支持Android 12。 196 | - 支持AGP(Android Gradle Plugin) 7.x版本。 197 | - DSL新增StringFogMode选项,用于控制字符串加密后在字节码中的存在形式,支持base64和bytes两种模式,默认使用base64。 198 | - base64模式:将字符串加密后的字节序列使用base64编码,行为同1.x和2.x版本。 199 | - bytes模式:将字符串加密后的字节序列直接呈现在字节码中,行为同3.x版本。 200 | 201 | ### v3.0.0 202 | - 密文不再以String形式存在,改为直接字节数组,感谢PR #50。 203 | - 重构公开API相关代码(不兼容历史版本)。 204 | - 删除AES加密实现,考虑到存在bug和性能问题且意义不大。 205 | - xor算法移除base64编码。 206 | - 固定加密字符串key改为随机key,且提供IKeyGenerator接口支持自定义实现。 207 | - 插件依赖的ASM库由5.x升级到9.2。 208 | 209 | ### v2.2.1 210 | - 修复module-info类导致的报错问题 211 | 212 | ### v2.2.0 213 | - 支持AGP(Android Gradle Plugin) 3.3.0+版本 214 | 215 | ### v2.1.0 216 | - 修复kotlin打包的bug 217 | 218 | ### v2.0.1 219 | - 增加implementation自定义算法实现类详细报错信息 220 | 221 | ### v2.0.0 222 | - 修改gradle配置(必须配置implementation指定算法实现)。 223 | - 修复大字符串编译失败的问题。 224 | - 新增自定义加解密算法扩展。 225 | - 新增生成mapping映射表文件。 226 | 227 | ### v1.4.1 228 | - 修复使用Java 8时出现的ZipException编译错误 229 | 230 | ### v1.4.0 231 | - 新增指定包名加密的配置项:fogPackages 232 | - 移除指定包名不加密的配置项:exclude 233 | 234 | ### v1.3.0 235 | - 修复gradle 3.0+编译报错的bug 236 | 237 | ### v1.2.2 238 | - 修复windows下打包后报错的bug 239 | 240 | ### v1.2.1 241 | - 修复windows下文件分隔符的bug 242 | - 修复applicationId和packageName不一致导致无法编译的bug 243 | - 优化功能,不需要再手动exclude已使用StringFog的库 244 | 245 | ### v1.2.0 246 | - 支持在library中使用,每个library可以使用不同key 247 | - 支持exclude指定包名不进行加密 248 | - 修复一些已知bug 249 | 250 | 251 | -------- 252 | 253 | Copyright (C) 2016-2023, Megatron King 254 | 255 | Licensed under the Apache License, Version 2.0 (the "License"); 256 | you may not use this file except in compliance with the License. 257 | You may obtain a copy of the License at 258 | 259 | http://www.apache.org/licenses/LICENSE-2.0 260 | 261 | Unless required by applicable law or agreed to in writing, software 262 | distributed under the License is distributed on an "AS IS" BASIS, 263 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 264 | See the License for the specific language governing permissions and 265 | limitations under the License. 266 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace "com.github.megatronking.stringfog" 7 | compileSdk 31 8 | buildFeatures { 9 | buildConfig = true 10 | } 11 | defaultConfig { 12 | applicationId "com.github.megatronking.stringfog" 13 | minSdk 15 14 | targetSdk 28 15 | versionCode 1 16 | versionName "1.0" 17 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 18 | } 19 | buildTypes { 20 | debug { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation 'androidx.appcompat:appcompat:1.4.1' 30 | implementation 'com.github.megatronking.stringfog:xor:5.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/megatronking/stringfog/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.github.megatronking.stringfog", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/megatronking/stringfog/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | public class MainActivity extends AppCompatActivity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | setContentView(R.layout.activity_main); 13 | // 范例请参考: 14 | // https://github.com/MegatronKing/StringFog-Sample1 15 | // https://github.com/MegatronKing/StringFog-Sample2 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | StringFog 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/github/megatronking/stringfog/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /assets/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/assets/flow.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | mavenLocal() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:8.0.0' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | classpath 'com.github.megatronking.stringfog:gradle-plugin:5.2.0' 14 | // 选用加解密算法库,默认实现了xor算法,也可以使用自己的加解密库。 15 | classpath 'com.github.megatronking.stringfog:xor:5.0.0' 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | mavenCentral() 23 | mavenLocal() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | GROUP=com.github.megatronking 20 | 21 | POM_LICENCE_URL=http\://www.apache.org/licenses/LICENSE-2.0.txt 22 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 23 | POM_LICENCE_DIST=repo 24 | 25 | POM_DEVELOPER_ID=Megatron King 26 | POM_DEVELOPER_NAME=Megatron King 27 | POM_DEVELOPER_EMAIL=1256980529@qq.com 28 | 29 | NEXUS_RELEASE_REPOSITORY_URL=https\://oss.sonatype.org/service/local/staging/deploy/maven2 30 | NEXUS_SNAPSHOT_REPOSITORY_URL=https\://oss.sonatype.org/content/repositories/snapshots 31 | 32 | POM_NAME=StringFog 33 | POM_DESCRIPTION=A String encryption Library for Android 34 | POM_URL=https\://github.com/MegatronKing/StringFog 35 | POM_SCM_CONNECTION=scm\:git@github.com\:MegatronKing/StringFog.git 36 | POM_PACKAGING=aar 37 | 38 | android.useAndroidX=true 39 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegatronKing/StringFog/b16ef3ca0f0ca79cf9c79c31444c52c7b3de1ecb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 22 18:05:10 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /maven_local.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | publishing { 4 | publications { 5 | StringFogPlugin(MavenPublication) { 6 | from components.java 7 | groupId GROUP 8 | artifactId ARTIFACTID 9 | version VERSION 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /maven_nexus.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | def getReleaseRepositoryUrl() { 5 | return hasProperty('NEXUS_RELEASE_REPOSITORY_URL') ? NEXUS_RELEASE_REPOSITORY_URL 6 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 7 | } 8 | 9 | def getSnapshotRepositoryUrl() { 10 | return hasProperty('NEXUS_SNAPSHOT_REPOSITORY_URL') ? NEXUS_SNAPSHOT_REPOSITORY_URL 11 | : "https://oss.sonatype.org/content/repositories/snapshots/" 12 | } 13 | 14 | def getRepositoryUsername() { 15 | def pFile = file("${rootDir}/local.properties") 16 | Properties p = new Properties() 17 | pFile.withInputStream { stream-> 18 | p.load(stream) 19 | } 20 | return p.getProperty('NEXUS_USERNAME') 21 | } 22 | 23 | def getRepositoryPassword() { 24 | def pFile = file("${rootDir}/local.properties") 25 | Properties p = new Properties() 26 | pFile.withInputStream { stream-> 27 | p.load(stream) 28 | } 29 | return p.getProperty('NEXUS_PASSWORD') 30 | } 31 | 32 | java { 33 | withJavadocJar() 34 | withSourcesJar() 35 | } 36 | 37 | tasks.withType(Javadoc) { 38 | options.addStringOption('Xdoclint:none', '-quiet') 39 | } 40 | 41 | publishing { 42 | publications { 43 | mavenJava(MavenPublication) { 44 | group = GROUP 45 | artifactId = ARTIFACTID 46 | version = VERSION 47 | from components.java 48 | 49 | pom { 50 | name = POM_NAME 51 | description = POM_DESCRIPTION 52 | url = POM_URL 53 | licenses { 54 | license { 55 | name = POM_LICENCE_NAME 56 | url = POM_LICENCE_URL 57 | } 58 | } 59 | developers { 60 | developer { 61 | id = POM_DEVELOPER_ID 62 | name = POM_DEVELOPER_NAME 63 | email = POM_DEVELOPER_EMAIL 64 | } 65 | } 66 | scm { 67 | connection = POM_SCM_CONNECTION 68 | developerConnection = POM_SCM_CONNECTION 69 | url = POM_URL 70 | } 71 | } 72 | } 73 | } 74 | repositories { 75 | maven { 76 | def releasesRepoUrl = getReleaseRepositoryUrl() 77 | def snapshotsRepoUrl = getSnapshotRepositoryUrl() 78 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 79 | credentials { 80 | username = getRepositoryUsername() 81 | password = getRepositoryPassword() 82 | } 83 | } 84 | } 85 | } 86 | 87 | signing { 88 | sign publishing.publications.mavenJava 89 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':stringfog-gradle-plugin', ':stringfog-core', ':stringfog-interface' 2 | file('stringfog-ext').listFiles().each { 3 | if (it.isDirectory()) { 4 | include ":stringfog-ext:${it.name}" 5 | } 6 | } -------------------------------------------------------------------------------- /stringfog-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /stringfog-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | implementation 'org.ow2.asm:asm:9.2' 6 | implementation 'com.squareup:javawriter:2.5.1' 7 | implementation project(':stringfog-interface') 8 | } 9 | 10 | sourceCompatibility = JavaVersion.VERSION_11 11 | targetCompatibility = JavaVersion.VERSION_11 12 | 13 | apply from: '../maven_local.gradle' 14 | 15 | // The following is used for mavenCentral publish. 16 | // apply from: '../maven_nexus.gradle' -------------------------------------------------------------------------------- /stringfog-core/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.github.megatronking.stringfog 2 | ARTIFACTID=core 3 | VERSION=5.2.0 -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/ClassStringField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package com.github.megatronking.stringfog.plugin; 15 | 16 | /** 17 | * The String fields in class. 18 | * 19 | * @author Megatron King 20 | * @since 2017/3/7 10:54 21 | */ 22 | 23 | /* package */ class ClassStringField { 24 | 25 | public static final String STRING_DESC = "Ljava/lang/String;"; 26 | 27 | /* package */ ClassStringField(String name, String value) { 28 | this.name = name; 29 | this.value = value; 30 | } 31 | 32 | public String name; 33 | public String value; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/ClassVisitorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package com.github.megatronking.stringfog.plugin; 15 | 16 | 17 | import com.github.megatronking.stringfog.IKeyGenerator; 18 | import com.github.megatronking.stringfog.IStringFog; 19 | import com.github.megatronking.stringfog.plugin.utils.Log; 20 | import com.github.megatronking.stringfog.plugin.utils.TextUtils; 21 | 22 | import org.objectweb.asm.ClassVisitor; 23 | import org.objectweb.asm.Opcodes; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * A factory creates {@link ClassVisitor}. 29 | * 30 | * @author Megatron King 31 | * @since 2017/3/7 19:56 32 | */ 33 | 34 | public final class ClassVisitorFactory { 35 | 36 | private ClassVisitorFactory() { 37 | } 38 | 39 | public static ClassVisitor create(IStringFog stringFogImpl, List logs, 40 | String[] fogPackages, IKeyGenerator kg, String fogClassName, 41 | String className, StringFogMode mode, ClassVisitor cv) { 42 | if (WhiteLists.inWhiteList(className) || !isInFogPackages(fogPackages, className)) { 43 | Log.v("StringFog ignore: " + className); 44 | return createEmpty(cv); 45 | } 46 | Log.v("StringFog execute: " + className); 47 | return new StringFogClassVisitor(stringFogImpl, logs, fogClassName, cv, kg, mode); 48 | } 49 | 50 | private static ClassVisitor createEmpty(ClassVisitor cv) { 51 | return new ClassVisitor(Opcodes.ASM9, cv) {}; 52 | } 53 | 54 | private static boolean isInFogPackages(String[] fogPackages, String className) { 55 | if (TextUtils.isEmpty(className)) { 56 | return false; 57 | } 58 | if (fogPackages == null || fogPackages.length == 0) { 59 | // default we fog all packages. 60 | return true; 61 | } 62 | for (String fogPackage : fogPackages) { 63 | if (className.replace('/', '.').startsWith(fogPackage + ".")) { 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/StringFogClassGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.plugin; 16 | 17 | import com.github.megatronking.stringfog.plugin.utils.SetUtils; 18 | import com.squareup.javawriter.JavaWriter; 19 | 20 | import java.io.File; 21 | import java.io.FileWriter; 22 | import java.io.IOException; 23 | 24 | import javax.lang.model.element.Modifier; 25 | 26 | /** 27 | * Generate the StringFog class. 28 | * 29 | * @author Megatron King 30 | * @since 2018/9/20 17:41 31 | */ 32 | public final class StringFogClassGenerator { 33 | 34 | 35 | public static void generate(File outputFile, String packageName, String className, 36 | String implementation, StringFogMode mode) throws IOException { 37 | File outputDir = outputFile.getParentFile(); 38 | if (!outputDir.exists() && !outputDir.mkdirs()) { 39 | throw new IOException("Can not mkdirs the dir: " + outputDir); 40 | } 41 | 42 | int lastIndexOfDot = implementation.lastIndexOf("."); 43 | String implementationSimpleClassName = lastIndexOfDot == -1 ? implementation : 44 | implementation.substring(implementation.lastIndexOf(".") + 1); 45 | 46 | JavaWriter javaWriter = new JavaWriter(new FileWriter(outputFile)); 47 | javaWriter.emitPackage(packageName); 48 | javaWriter.emitEmptyLine(); 49 | javaWriter.emitImports(implementation); 50 | javaWriter.emitEmptyLine(); 51 | if (mode == StringFogMode.base64) { 52 | javaWriter.emitImports("com.github.megatronking.stringfog.Base64"); 53 | } 54 | 55 | javaWriter.emitJavadoc("Generated code from StringFog gradle plugin. Do not modify!"); 56 | javaWriter.beginType(className, "class", SetUtils.fromArray(Modifier.PUBLIC, 57 | Modifier.FINAL)); 58 | 59 | javaWriter.emitField(implementationSimpleClassName, "IMPL", 60 | SetUtils.fromArray(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL), 61 | "new " + implementationSimpleClassName + "()"); 62 | 63 | javaWriter.emitEmptyLine(); 64 | if (mode == StringFogMode.base64) { 65 | javaWriter.beginMethod(String.class.getSimpleName(), "decrypt", 66 | SetUtils.fromArray(Modifier.PUBLIC, Modifier.STATIC), 67 | String.class.getSimpleName(), "value", 68 | String.class.getSimpleName(), "key"); 69 | javaWriter.emitStatement("return IMPL.decrypt(Base64.decode(value, Base64.DEFAULT), " + 70 | "Base64.decode(key, Base64.DEFAULT))"); 71 | javaWriter.endMethod(); 72 | } else if (mode == StringFogMode.bytes) { 73 | javaWriter.beginMethod(String.class.getSimpleName(), "decrypt", 74 | SetUtils.fromArray(Modifier.PUBLIC, Modifier.STATIC), 75 | byte[].class.getSimpleName(), "value", 76 | byte[].class.getSimpleName(), "key"); 77 | javaWriter.emitStatement("return IMPL.decrypt(value, key)"); 78 | javaWriter.endMethod(); 79 | } 80 | 81 | javaWriter.emitEmptyLine(); 82 | javaWriter.endType(); 83 | 84 | javaWriter.close(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/StringFogClassVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.plugin; 16 | 17 | import com.github.megatronking.stringfog.Base64; 18 | import com.github.megatronking.stringfog.IKeyGenerator; 19 | import com.github.megatronking.stringfog.IStringFog; 20 | import com.github.megatronking.stringfog.plugin.utils.TextUtils; 21 | 22 | import org.objectweb.asm.AnnotationVisitor; 23 | import org.objectweb.asm.ClassVisitor; 24 | import org.objectweb.asm.FieldVisitor; 25 | import org.objectweb.asm.MethodVisitor; 26 | import org.objectweb.asm.Opcodes; 27 | import org.objectweb.asm.Type; 28 | 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.List; 32 | 33 | /** 34 | * Visit the class to execute string fog. 35 | * 36 | * @author Megatron King 37 | * @since 2017/3/6 20:37 38 | */ 39 | 40 | /* package */ class StringFogClassVisitor extends ClassVisitor { 41 | 42 | private static final String IGNORE_ANNOTATION = "Lcom/github/megatronking/stringfog" + 43 | "/annotation/StringFogIgnore;"; 44 | 45 | private boolean isClInitExists; 46 | 47 | private final List mStaticFinalFields = new ArrayList<>(); 48 | private final List mStaticFields = new ArrayList<>(); 49 | private final List mFinalFields = new ArrayList<>(); 50 | private final List mFields = new ArrayList<>(); 51 | 52 | private final IStringFog mStringFogImpl; 53 | private final List mLogs; 54 | private final IKeyGenerator mKeyGenerator; 55 | private String mClassName; 56 | private final InstructionWriter mInstructionWriter; 57 | 58 | private boolean mIgnoreClass; 59 | 60 | 61 | /* package */ StringFogClassVisitor(IStringFog stringFogImpl, List logs, 62 | String fogClassName, ClassVisitor cv, IKeyGenerator kg, StringFogMode mode) { 63 | super(Opcodes.ASM9, cv); 64 | this.mStringFogImpl = stringFogImpl; 65 | this.mLogs = logs; 66 | this.mKeyGenerator = kg; 67 | this.mLogs.add(fogClassName); 68 | fogClassName = fogClassName.replace('.', '/'); 69 | if (mode == StringFogMode.base64) { 70 | this.mInstructionWriter = new Base64InstructionWriter(fogClassName); 71 | } else if (mode == StringFogMode.bytes) { 72 | this.mInstructionWriter = new ByteArrayInstructionWriter(fogClassName); 73 | } else { 74 | throw new IllegalArgumentException("Unknown stringfog mode: " + mode); 75 | } 76 | } 77 | 78 | @Override 79 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 80 | this.mClassName = name; 81 | super.visit(version, access, name, signature, superName, interfaces); 82 | } 83 | 84 | @Override 85 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 86 | mIgnoreClass = IGNORE_ANNOTATION.equals(desc); 87 | return super.visitAnnotation(desc, visible); 88 | } 89 | 90 | @Override 91 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 92 | if (ClassStringField.STRING_DESC.equals(desc) && name != null && !mIgnoreClass) { 93 | // static final, in this condition, the value is null or not null. 94 | if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) != 0) { 95 | mStaticFinalFields.add(new ClassStringField(name, (String) value)); 96 | value = null; 97 | } 98 | // static, in this condition, the value is null. 99 | if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) == 0) { 100 | mStaticFields.add(new ClassStringField(name, (String) value)); 101 | value = null; 102 | } 103 | 104 | // final, in this condition, the value is null or not null. 105 | if ((access & Opcodes.ACC_STATIC) == 0 && (access & Opcodes.ACC_FINAL) != 0) { 106 | mFinalFields.add(new ClassStringField(name, (String) value)); 107 | value = null; 108 | } 109 | 110 | // normal, in this condition, the value is null. 111 | if ((access & Opcodes.ACC_STATIC) == 0 && (access & Opcodes.ACC_FINAL) == 0) { 112 | mFields.add(new ClassStringField(name, (String) value)); 113 | value = null; 114 | } 115 | } 116 | return super.visitField(access, name, desc, signature, value); 117 | } 118 | 119 | @Override 120 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 121 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 122 | if (mv == null || mIgnoreClass) { 123 | return mv; 124 | } 125 | if ("".equals(name)) { 126 | isClInitExists = true; 127 | // If clinit exists meaning the static fields (not final) would have be inited here. 128 | mv = new MethodVisitor(Opcodes.ASM9, mv) { 129 | 130 | private String lastStashCst; 131 | 132 | @Override 133 | public void visitCode() { 134 | super.visitCode(); 135 | // Here init static final fields. 136 | for (ClassStringField field : mStaticFinalFields) { 137 | if (!canEncrypted(field.value)) { 138 | continue; 139 | } 140 | encryptAndWrite(field.value, mv); 141 | super.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 142 | } 143 | } 144 | 145 | @Override 146 | public void visitLdcInsn(Object cst) { 147 | // Here init static or static final fields, but we must check field name int 'visitFieldInsn' 148 | if (cst instanceof String && canEncrypted((String) cst)) { 149 | lastStashCst = (String) cst; 150 | encryptAndWrite(lastStashCst, mv); 151 | } else { 152 | lastStashCst = null; 153 | super.visitLdcInsn(cst); 154 | } 155 | } 156 | 157 | @Override 158 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 159 | if (mClassName.equals(owner) && lastStashCst != null) { 160 | boolean isContain = false; 161 | for (ClassStringField field : mStaticFields) { 162 | if (field.name.equals(name)) { 163 | isContain = true; 164 | break; 165 | } 166 | } 167 | if (!isContain) { 168 | for (ClassStringField field : mStaticFinalFields) { 169 | if (field.name.equals(name) && field.value == null) { 170 | field.value = lastStashCst; 171 | break; 172 | } 173 | } 174 | } 175 | } 176 | lastStashCst = null; 177 | super.visitFieldInsn(opcode, owner, name, desc); 178 | } 179 | }; 180 | 181 | } else if ("".equals(name)) { 182 | // Here init final(not static) and normal fields 183 | mv = new MethodVisitor(Opcodes.ASM9, mv) { 184 | @Override 185 | public void visitLdcInsn(Object cst) { 186 | // We don't care about whether the field is final or normal 187 | if (cst instanceof String && canEncrypted((String) cst)) { 188 | encryptAndWrite((String) cst, mv); 189 | } else { 190 | super.visitLdcInsn(cst); 191 | } 192 | } 193 | }; 194 | } else { 195 | mv = new MethodVisitor(Opcodes.ASM9, mv) { 196 | 197 | @Override 198 | public void visitLdcInsn(Object cst) { 199 | if (cst instanceof String && canEncrypted((String) cst)) { 200 | // If the value is a static final field 201 | for (ClassStringField field : mStaticFinalFields) { 202 | if (cst.equals(field.value)) { 203 | super.visitFieldInsn(Opcodes.GETSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 204 | return; 205 | } 206 | } 207 | // If the value is a final field (not static) 208 | for (ClassStringField field : mFinalFields) { 209 | // if the value of a final field is null, we ignore it 210 | if (cst.equals(field.value)) { 211 | super.visitVarInsn(Opcodes.ALOAD, 0); 212 | super.visitFieldInsn(Opcodes.GETFIELD, mClassName, field.name, "Ljava/lang/String;"); 213 | return; 214 | } 215 | } 216 | // local variables 217 | encryptAndWrite((String) cst, mv); 218 | return; 219 | } 220 | super.visitLdcInsn(cst); 221 | } 222 | 223 | }; 224 | } 225 | return mv; 226 | } 227 | 228 | @Override 229 | public void visitEnd() { 230 | if (!mIgnoreClass && !isClInitExists && !mStaticFinalFields.isEmpty()) { 231 | MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); 232 | mv.visitCode(); 233 | // Here init static final fields. 234 | for (ClassStringField field : mStaticFinalFields) { 235 | if (!canEncrypted(field.value)) { 236 | continue; 237 | } 238 | encryptAndWrite(field.value, mv); 239 | mv.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 240 | } 241 | mv.visitInsn(Opcodes.RETURN); 242 | mv.visitMaxs(1, 0); 243 | mv.visitEnd(); 244 | } 245 | super.visitEnd(); 246 | } 247 | 248 | private boolean canEncrypted(String value) { 249 | return !TextUtils.isEmptyAfterTrim(value) && value.length() < 65536 >> 2 && mStringFogImpl.shouldFog(value); 250 | } 251 | 252 | private void encryptAndWrite(String value, MethodVisitor mv) { 253 | byte[] key = mKeyGenerator.generate(value); 254 | byte[] encryptValue = mStringFogImpl.encrypt(value, key); 255 | String result = mInstructionWriter.write(key, encryptValue, mv); 256 | mLogs.add(value + " -> " + result); 257 | } 258 | 259 | private String getJavaClassName() { 260 | return mClassName != null ? mClassName.replace('/', '.') : null; 261 | } 262 | 263 | private static abstract class InstructionWriter { 264 | 265 | private final String mFogClassName; 266 | 267 | InstructionWriter(String fogClassName) { 268 | mFogClassName = fogClassName; 269 | } 270 | 271 | abstract String write(byte[] key, byte[] value, MethodVisitor mv); 272 | 273 | protected void writeClass(MethodVisitor mv, String descriptor) { 274 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, mFogClassName, "decrypt", descriptor, false); 275 | } 276 | 277 | } 278 | 279 | private static class Base64InstructionWriter extends InstructionWriter { 280 | 281 | private Base64InstructionWriter(String fogClassName) { 282 | super(fogClassName); 283 | } 284 | 285 | @Override 286 | String write(byte[] key, byte[] value, MethodVisitor mv) { 287 | String base64Key = new String(Base64.encode(key, Base64.DEFAULT)); 288 | String base64Value = new String(Base64.encode(value, Base64.DEFAULT)); 289 | mv.visitLdcInsn(base64Value); 290 | mv.visitLdcInsn(base64Key); 291 | super.writeClass(mv, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); 292 | return base64Value; 293 | } 294 | 295 | } 296 | 297 | private static class ByteArrayInstructionWriter extends InstructionWriter { 298 | 299 | private ByteArrayInstructionWriter(String fogClassName) { 300 | super(fogClassName); 301 | } 302 | 303 | @Override 304 | String write(byte[] key, byte[] value, MethodVisitor mv) { 305 | pushArray(mv, value); 306 | pushArray(mv, key); 307 | super.writeClass(mv, "([B[B)Ljava/lang/String;"); 308 | return Arrays.toString(value); 309 | } 310 | 311 | private void pushArray(MethodVisitor mv, byte[] buffer) { 312 | pushNumber(mv, buffer.length); 313 | mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); 314 | mv.visitInsn(Opcodes.DUP); 315 | for (int i = 0; i < buffer.length; i++) { 316 | pushNumber(mv, i); 317 | pushNumber(mv, buffer[i]); 318 | mv.visitInsn(Type.BYTE_TYPE.getOpcode(Opcodes.IASTORE)); 319 | if (i < buffer.length - 1) mv.visitInsn(Opcodes.DUP); 320 | } 321 | } 322 | 323 | private void pushNumber(MethodVisitor mv, final int value) { 324 | if (value >= -1 && value <= 5) { 325 | mv.visitInsn(Opcodes.ICONST_0 + value); 326 | } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 327 | mv.visitIntInsn(Opcodes.BIPUSH, value); 328 | } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 329 | mv.visitIntInsn(Opcodes.SIPUSH, value); 330 | } else { 331 | mv.visitLdcInsn(value); 332 | } 333 | } 334 | 335 | } 336 | 337 | } 338 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/StringFogMappingPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.plugin; 16 | 17 | import com.github.megatronking.stringfog.plugin.utils.Log; 18 | import com.github.megatronking.stringfog.plugin.utils.TextUtils; 19 | 20 | import java.io.BufferedWriter; 21 | import java.io.File; 22 | import java.io.FileWriter; 23 | import java.io.IOException; 24 | 25 | /** 26 | * Output StringFog mapping to file. 27 | * 28 | * @author Megatron King 29 | * @since 2018/9/21 11:07 30 | */ 31 | final class StringFogMappingPrinter { 32 | 33 | private final File mMappingFile; 34 | private BufferedWriter mWriter; 35 | 36 | private String mCurrentClassName; 37 | 38 | public StringFogMappingPrinter(File mappingFile) { 39 | this.mMappingFile = mappingFile; 40 | } 41 | 42 | /* package */ void startMappingOutput(String implementation, StringFogMode mode) { 43 | try { 44 | if (mMappingFile.exists() && !mMappingFile.delete()) { 45 | throw new IOException("delete stringfog mappingFile failed"); 46 | } 47 | File dir = mMappingFile.getParentFile(); 48 | if (dir.exists() || dir.mkdirs()) { 49 | mWriter = new BufferedWriter(new FileWriter(mMappingFile)); 50 | } else { 51 | throw new IOException("Failed to create dir: " + dir.getPath()); 52 | } 53 | mWriter.write("stringfog impl: " + implementation); 54 | mWriter.newLine(); 55 | mWriter.write("stringfog mode: " + mode); 56 | mWriter.newLine(); 57 | } catch (IOException e) { 58 | Log.e("Create stringfog mapping file failed."); 59 | } 60 | } 61 | 62 | /* package */ void output(String line) { 63 | try { 64 | mWriter.write(line); 65 | mWriter.newLine(); 66 | } catch (IOException e) { 67 | // Ignore 68 | } 69 | } 70 | 71 | /* package */ void output(String className, String originValue, String encryptValue) { 72 | if (TextUtils.isEmpty(className)) { 73 | return; 74 | } 75 | try { 76 | if (!className.equals(mCurrentClassName)) { 77 | mWriter.newLine(); 78 | mWriter.write("[" + className + "]"); 79 | mWriter.newLine(); 80 | mCurrentClassName = className; 81 | } 82 | mWriter.write(originValue + " -> " + encryptValue); 83 | mWriter.newLine(); 84 | } catch (IOException e) { 85 | // Ignore 86 | } 87 | } 88 | 89 | /* package */ void endMappingOutput() { 90 | if (mWriter != null) { 91 | try { 92 | mWriter.close(); 93 | } catch (IOException e) { 94 | // Ignore 95 | } 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/StringFogMode.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin; 2 | 3 | /** 4 | * Define how the encrypted string presents in java class. 5 | */ 6 | public enum StringFogMode { 7 | 8 | /** 9 | * Replace the origin string with an encrypted and base64 encoded string. 10 | */ 11 | base64, 12 | 13 | /** 14 | * Replace the origin string with an encrypted byte array. 15 | * Warning: this mode will increase the apk file size. 16 | */ 17 | bytes 18 | } -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/WhiteLists.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.plugin; 16 | 17 | import com.github.megatronking.stringfog.plugin.utils.TextUtils; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * The white list contains some ignored levels. We defined some popular 24 | * library domains and classes which must be ignored when executing string fog. 25 | * 26 | * @author Megatron King 27 | * @since 2017/3/7 19:34 28 | */ 29 | 30 | final class WhiteLists { 31 | 32 | private static final List CLASS_WHITE_LIST = new ArrayList<>(); 33 | 34 | static { 35 | // default classes short name in white list. 36 | addWhiteList("BuildConfig"); 37 | addWhiteList("R"); 38 | addWhiteList("R2"); 39 | addWhiteList("StringFog"); 40 | } 41 | 42 | static boolean inWhiteList(String name) { 43 | return !TextUtils.isEmpty(name) && checkClass(shortClassName(name)); 44 | } 45 | 46 | private WhiteLists() { 47 | } 48 | 49 | private static void addWhiteList(String name) { 50 | CLASS_WHITE_LIST.add(name); 51 | } 52 | 53 | private static boolean checkClass(String name) { 54 | for (String className : CLASS_WHITE_LIST) { 55 | if (name.equals(className)) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | private static String trueClassName(String className) { 63 | return className.replace('/', '.'); 64 | } 65 | 66 | private static String shortClassName(String className) { 67 | String[] spiltArrays = trueClassName(className).split("[.]"); 68 | return spiltArrays[spiltArrays.length - 1]; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/kg/HardCodeKeyGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin.kg; 2 | 3 | import com.github.megatronking.stringfog.IKeyGenerator; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | 7 | /** 8 | * Hard code a binary security key for encryption. 9 | * 10 | * @author Megatron King 11 | * @since 2022/2/14 22:34 12 | */ 13 | public class HardCodeKeyGenerator implements IKeyGenerator { 14 | 15 | private final byte[] mKey; 16 | 17 | public HardCodeKeyGenerator(String key) { 18 | this(key.getBytes(StandardCharsets.UTF_8)); 19 | } 20 | 21 | public HardCodeKeyGenerator(byte[] key) { 22 | mKey = key; 23 | } 24 | 25 | @Override 26 | public byte[] generate(String text) { 27 | return mKey; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/kg/RandomKeyGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin.kg; 2 | 3 | import com.github.megatronking.stringfog.IKeyGenerator; 4 | 5 | import java.security.SecureRandom; 6 | 7 | /** 8 | * Dynamic random security key for encryption. 9 | * 10 | * @author Megatron King 11 | * @since 2022/2/14 22:40 12 | */ 13 | public class RandomKeyGenerator implements IKeyGenerator { 14 | 15 | private static final int DEFAULT_LENGTH = 8; 16 | 17 | private final SecureRandom mSecureRandom; 18 | private final int mLength; 19 | 20 | public RandomKeyGenerator() { 21 | this(DEFAULT_LENGTH); 22 | } 23 | 24 | public RandomKeyGenerator(int length) { 25 | mLength = length; 26 | mSecureRandom = new SecureRandom(); 27 | } 28 | 29 | @Override 30 | public byte[] generate(String text) { 31 | byte[] key = new byte[mLength]; 32 | mSecureRandom.nextBytes(key); 33 | return key; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/utils/Log.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin.utils; 2 | 3 | public class Log { 4 | 5 | private static boolean isDebug; 6 | 7 | public static void setDebug(boolean debug) { 8 | isDebug = debug; 9 | } 10 | 11 | public static void v(String msg) { 12 | if (isDebug) { 13 | System.out.println(msg); 14 | } 15 | } 16 | 17 | public static void e(String msg) { 18 | if (isDebug) { 19 | System.err.println(msg); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/utils/MD5.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package com.github.megatronking.stringfog.plugin.utils; 15 | 16 | import java.security.MessageDigest; 17 | 18 | public class MD5 { 19 | 20 | private MD5() { 21 | } 22 | 23 | /** 24 | * get md5 string for input buffer 25 | * 26 | * @param buffer data to be calculated 27 | * @return md5 result in string format 28 | */ 29 | public static String getMessageDigest(byte[] buffer) { 30 | char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 31 | try { 32 | MessageDigest mdTemp = MessageDigest.getInstance("MD5"); 33 | mdTemp.update(buffer); 34 | byte[] md = mdTemp.digest(); 35 | int j = md.length; 36 | char[] str = new char[j * 2]; 37 | int k = 0; 38 | for (byte byte0: md) { 39 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 40 | str[k++] = hexDigits[byte0 & 0xf]; 41 | } 42 | return new String(str); 43 | } catch (Exception e) { 44 | return null; 45 | } 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/utils/SetUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin.utils; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public class SetUtils { 8 | 9 | private SetUtils() { 10 | } 11 | 12 | @SafeVarargs 13 | public static Set fromArray(T ... array) { 14 | Set set = new HashSet<>(); 15 | Collections.addAll(set, array); 16 | return set; 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /stringfog-core/src/main/java/com/github/megatronking/stringfog/plugin/utils/TextUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package com.github.megatronking.stringfog.plugin.utils; 15 | 16 | public final class TextUtils { 17 | 18 | private TextUtils() { 19 | } 20 | 21 | /** 22 | * Returns true if the string is null or 0-length. 23 | * @param str the string to be examined 24 | * @return true if str is null or zero length 25 | */ 26 | public static boolean isEmpty(CharSequence str) { 27 | return str == null || str.length() == 0; 28 | } 29 | 30 | /** 31 | * Returns true if the string is null or 0-length after trims. 32 | * @param str the string to be examined 33 | * @return true if str is null or zero length 34 | */ 35 | public static boolean isEmptyAfterTrim(String str) { 36 | return str == null || str.length() == 0 || str.trim().length() == 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /stringfog-ext/xor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *iml -------------------------------------------------------------------------------- /stringfog-ext/xor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | api project(':stringfog-interface') 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_11 8 | targetCompatibility = JavaVersion.VERSION_11 9 | 10 | apply from: '../../maven_local.gradle' 11 | 12 | // The following is used for mavenCentral publish. 13 | // apply from: '../../maven_nexus.gradle' 14 | -------------------------------------------------------------------------------- /stringfog-ext/xor/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.github.megatronking.stringfog 2 | ARTIFACTID=xor 3 | VERSION=5.0.0 4 | 5 | -------------------------------------------------------------------------------- /stringfog-ext/xor/src/main/java/com/github/megatronking/stringfog/xor/StringFogImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.xor; 16 | 17 | import com.github.megatronking.stringfog.IStringFog; 18 | import java.nio.charset.StandardCharsets; 19 | 20 | /** 21 | * StringFog xor encrypt and decrypt implementation. 22 | * 23 | * @author Megatron King 24 | * @since 2018/9/2 14:34 25 | */ 26 | public final class StringFogImpl implements IStringFog { 27 | 28 | @Override 29 | public byte[] encrypt(String data, byte[] key) { 30 | return xor(data.getBytes(StandardCharsets.UTF_8), key); 31 | } 32 | 33 | @Override 34 | public String decrypt(byte[] data, byte[] key) { 35 | return new String(xor(data, key), StandardCharsets.UTF_8); 36 | } 37 | 38 | @Override 39 | public boolean shouldFog(String data) { 40 | return true; 41 | } 42 | 43 | private static byte[] xor(byte[] data, byte[] key) { 44 | int len = data.length; 45 | int lenKey = key.length; 46 | int i = 0; 47 | int j = 0; 48 | while (i < len) { 49 | if (j >= lenKey) { 50 | j = 0; 51 | } 52 | data[i] = (byte) (data[i] ^ key[j]); 53 | i++; 54 | j++; 55 | } 56 | return data; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /stringfog-gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /stringfog-gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | id "org.jetbrains.kotlin.jvm" version "1.8.21" 4 | } 5 | 6 | dependencies { 7 | implementation gradleApi() 8 | implementation localGroovy() 9 | implementation 'com.android.tools.build:gradle:8.0.0' 10 | implementation project(':stringfog-core') 11 | implementation project(':stringfog-interface') 12 | } 13 | 14 | sourceSets { 15 | main { 16 | java { 17 | srcDir 'src/main/java' 18 | } 19 | resources { 20 | srcDir 'src/main/resources' 21 | } 22 | } 23 | } 24 | 25 | tasks.withType(ProcessResources){ 26 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 27 | } 28 | tasks.withType(Jar){ 29 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 30 | } 31 | 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | 35 | tasks { 36 | compileKotlin { 37 | kotlinOptions.jvmTarget = targetCompatibility 38 | } 39 | } 40 | 41 | apply from: '../maven_local.gradle' 42 | 43 | // The following is used for mavenCentral publish. 44 | // apply from: '../maven_nexus.gradle' -------------------------------------------------------------------------------- /stringfog-gradle-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.github.megatronking.stringfog 2 | ARTIFACTID=gradle-plugin 3 | VERSION=5.2.0 4 | 5 | -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/java/com/github/megatronking/stringfog/plugin/SourceGeneratingTask.kt: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin; 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.CacheableTask 6 | import org.gradle.api.tasks.Input 7 | import org.gradle.api.tasks.TaskAction 8 | import java.io.File 9 | import javax.inject.Inject 10 | 11 | @CacheableTask 12 | abstract class SourceGeneratingTask @Inject constructor(): DefaultTask() { 13 | companion object { 14 | const val FOG_CLASS_NAME = "StringFog" 15 | } 16 | 17 | @get:Input 18 | abstract val genDir: Property 19 | @get:Input 20 | abstract val applicationId: Property 21 | @get:Input 22 | abstract val implementation: Property 23 | @get:Input 24 | abstract val mode: Property 25 | 26 | @TaskAction 27 | fun injectSource() { 28 | 29 | if (!genDir.get().exists()) { 30 | genDir.get().mkdirs() 31 | } 32 | 33 | val outputFile = File(genDir.get(), applicationId.get().replace('.', File.separatorChar) + File.separator + "StringFog.java") 34 | StringFogClassGenerator.generate(outputFile, applicationId.get(), FOG_CLASS_NAME, 35 | implementation.get(), mode.get()) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/java/com/github/megatronking/stringfog/plugin/StringFogExtension.kt: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin 2 | 3 | import com.github.megatronking.stringfog.IKeyGenerator 4 | import com.github.megatronking.stringfog.plugin.kg.RandomKeyGenerator 5 | 6 | abstract class StringFogExtension { 7 | 8 | companion object { 9 | val base64 = StringFogMode.base64 10 | val bytes = StringFogMode.bytes 11 | } 12 | 13 | /** 14 | * The package name of the application or the library. 15 | */ 16 | var packageName: String? = null 17 | 18 | /** 19 | * The algorithm implementation for String encryption and decryption. 20 | * It is required. 21 | */ 22 | var implementation: String? = null 23 | 24 | /** 25 | * A generator to generate a security key for the encryption and decryption. 26 | * 27 | * StringFog use a 8 length random key generator default. 28 | */ 29 | var kg : IKeyGenerator = RandomKeyGenerator() 30 | 31 | /** 32 | * How the encrypted string presents in java class, default is base64. 33 | */ 34 | var mode: StringFogMode = base64 35 | 36 | /** 37 | * Enable or disable the StringFog plugin. Default is enabled. 38 | */ 39 | var enable: Boolean = true 40 | 41 | /** 42 | * Enable or disable the StringFog debug message print. Default is disabled. 43 | */ 44 | var debug: Boolean = false 45 | 46 | /** 47 | * The java packages will be applied. Default is effect on all packages. 48 | */ 49 | var fogPackages : Array = emptyArray() 50 | 51 | } -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/java/com/github/megatronking/stringfog/plugin/StringFogInstrumentationParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin 2 | 3 | import com.android.build.api.instrumentation.InstrumentationParameters 4 | import com.github.megatronking.stringfog.StringFogWrapper 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.tasks.Input 7 | import java.lang.ref.WeakReference 8 | import java.util.WeakHashMap 9 | 10 | abstract class StringFogInstrumentationParams : InstrumentationParameters { 11 | @get:Input 12 | abstract val applicationId: Property 13 | 14 | @get:Input 15 | abstract val className: Property 16 | } 17 | 18 | private class NonSerializableParams( 19 | val logs: MutableList, 20 | val implementation: StringFogWrapper 21 | ) 22 | 23 | private val extensionForApplicationId = mutableMapOf>() 24 | private val extensionNonSerializableParams = WeakHashMap() 25 | 26 | internal val StringFogInstrumentationParams.extension 27 | get() = extensionForApplicationId[applicationId.get()]?.get() 28 | ?: throw IllegalStateException("Extension has not been registered with setParameters") 29 | 30 | private val StringFogInstrumentationParams.nonSerializableParameters 31 | get() = extension.let { extensionNonSerializableParams[it] } 32 | ?: throw IllegalStateException("runtimeParameters have not been registered with setParameters") 33 | 34 | internal val StringFogInstrumentationParams.logs get() = nonSerializableParameters.logs 35 | 36 | internal val StringFogInstrumentationParams.implementation get() = nonSerializableParameters.implementation 37 | 38 | internal fun StringFogInstrumentationParams.setParameters( 39 | applicationId: String, 40 | extension: StringFogExtension, 41 | logs: MutableList, 42 | className: String 43 | ) { 44 | this.applicationId.set(applicationId) 45 | this.className.set(className) 46 | extensionForApplicationId[applicationId] = WeakReference(extension) 47 | extensionNonSerializableParams[extension] = NonSerializableParams( 48 | logs = logs, 49 | implementation = StringFogWrapper(extension.implementation) 50 | ) 51 | logs.add("stringfog impl: " + extension.implementation) 52 | logs.add("stringfog mode: " + extension.mode) 53 | } -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/java/com/github/megatronking/stringfog/plugin/StringFogPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin 2 | 3 | import com.android.build.api.instrumentation.FramesComputationMode 4 | import com.android.build.api.instrumentation.InstrumentationScope 5 | import com.android.build.api.variant.AndroidComponentsExtension 6 | import com.android.build.gradle.AppExtension 7 | import com.android.build.gradle.BaseExtension 8 | import com.android.build.gradle.LibraryExtension 9 | import groovy.xml.XmlParser 10 | import org.gradle.api.GradleException 11 | import org.gradle.api.Plugin 12 | import org.gradle.api.Project 13 | import org.gradle.configurationcache.extensions.capitalized 14 | import java.io.File 15 | import java.io.FileInputStream 16 | import java.io.InputStreamReader 17 | 18 | class StringFogPlugin : Plugin { 19 | 20 | companion object { 21 | private const val PLUGIN_NAME = "stringfog" 22 | } 23 | 24 | private fun forEachVariant( 25 | extension: BaseExtension, 26 | action: (com.android.build.gradle.api.BaseVariant) -> Unit 27 | ) { 28 | when (extension) { 29 | is AppExtension -> extension.applicationVariants.all(action) 30 | is LibraryExtension -> { 31 | extension.libraryVariants.all(action) 32 | } else -> throw GradleException( 33 | "StringFog plugin must be used with android app," + 34 | "library or feature plugin" 35 | ) 36 | } 37 | } 38 | 39 | override fun apply(project: Project) { 40 | project.extensions.create(PLUGIN_NAME, StringFogExtension::class.java) 41 | val extension = project.extensions.findByType(BaseExtension::class.java) 42 | ?: throw GradleException("StringFog plugin must be used with android plugin") 43 | 44 | val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) 45 | androidComponents.onVariants { variant -> 46 | // Check stringfog extension 47 | val stringfog = project.extensions.getByType(StringFogExtension::class.java) 48 | if (stringfog.implementation.isNullOrEmpty()) { 49 | throw IllegalArgumentException("Missing stringfog implementation config") 50 | } 51 | if (!stringfog.enable) { 52 | return@onVariants 53 | } 54 | var applicationId: String? = null 55 | // We must get the package name to generate .StringFog.java 56 | // Priority: AndroidManifest -> namespace -> stringfog.packageName 57 | val manifestFile = project.file("src/main/AndroidManifest.xml") 58 | if (manifestFile.exists()) { 59 | val parsedManifest = XmlParser().parse( 60 | InputStreamReader(FileInputStream(manifestFile), "utf-8") 61 | ) 62 | if (!manifestFile.exists()) { 63 | throw IllegalArgumentException("Failed to parse file $manifestFile") 64 | } 65 | applicationId = parsedManifest.attribute("package")?.toString() 66 | } 67 | if (applicationId.isNullOrEmpty()) { 68 | applicationId = extension.namespace 69 | } 70 | if (applicationId.isNullOrEmpty()) { 71 | applicationId = stringfog.packageName 72 | } 73 | if (applicationId.isNullOrEmpty()) { 74 | throw IllegalArgumentException("Unable to resolve applicationId") 75 | } 76 | 77 | val logs = mutableListOf() 78 | variant.instrumentation.transformClassesWith( 79 | StringFogTransform::class.java, 80 | InstrumentationScope.PROJECT 81 | ) { params -> 82 | params.setParameters( 83 | applicationId, 84 | stringfog, 85 | logs, 86 | "$applicationId.${SourceGeneratingTask.FOG_CLASS_NAME}" 87 | ) 88 | } 89 | variant.instrumentation.setAsmFramesComputationMode( 90 | FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS 91 | ) 92 | 93 | // TODO This will not work on Gradle 9.0 94 | forEachVariant(extension) { 95 | val generateTaskName = "generateStringFog${it.name.capitalized()}" 96 | if (project.getTasksByName(generateTaskName, true).isNotEmpty()) { 97 | return@forEachVariant 98 | } 99 | val stringfogDir = File(project.buildDir, "generated" + 100 | File.separatorChar + "source" + File.separatorChar + "stringFog" + File.separatorChar + it.name.capitalized().lowercase()) 101 | val provider = project.tasks.register(generateTaskName, SourceGeneratingTask::class.java) { task -> 102 | task.genDir.set(stringfogDir) 103 | task.applicationId.set(applicationId) 104 | task.implementation.set(stringfog.implementation) 105 | task.mode.set(stringfog.mode) 106 | } 107 | it.registerJavaGeneratingTask(provider, stringfogDir) 108 | } 109 | // TODO Need a final task to write logs to file 110 | // val printFile = File(project.buildDir, "outputs/mapping/${variant.name.lowercase()}/stringfog.txt") 111 | // printFile.writeText(logs.joinToString("\n")) 112 | } 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/java/com/github/megatronking/stringfog/plugin/StringFogTransform.kt: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog.plugin 2 | 3 | import com.android.build.api.instrumentation.AsmClassVisitorFactory 4 | import com.android.build.api.instrumentation.ClassContext 5 | import com.android.build.api.instrumentation.ClassData 6 | import com.android.build.api.instrumentation.InstrumentationParameters 7 | import com.android.build.gradle.internal.tasks.mlkit.codegen.ClassNames 8 | import com.github.megatronking.stringfog.IKeyGenerator 9 | import com.github.megatronking.stringfog.StringFogWrapper 10 | import org.gradle.api.provider.Property 11 | import org.gradle.api.tasks.Input 12 | import org.objectweb.asm.ClassVisitor 13 | import java.io.File 14 | 15 | abstract class StringFogTransform : AsmClassVisitorFactory { 16 | 17 | override fun createClassVisitor( 18 | classContext: ClassContext, 19 | nextClassVisitor: ClassVisitor 20 | ): ClassVisitor { 21 | return with(parameters.get()) { 22 | ClassVisitorFactory.create( 23 | implementation, logs, extension.fogPackages, extension.kg, className.get(), 24 | classContext.currentClassData.className, extension.mode, nextClassVisitor 25 | ) 26 | } 27 | } 28 | 29 | override fun isInstrumentable(classData: ClassData): Boolean { 30 | return true 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /stringfog-gradle-plugin/src/main/resources/META-INF/gradle-plugins/stringfog.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.github.megatronking.stringfog.plugin.StringFogPlugin -------------------------------------------------------------------------------- /stringfog-interface/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /stringfog-interface/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_11 8 | targetCompatibility = JavaVersion.VERSION_11 9 | 10 | apply from: '../maven_local.gradle' 11 | 12 | // The following is used for mavenCentral publish. 13 | // apply from: '../maven_nexus.gradle' 14 | -------------------------------------------------------------------------------- /stringfog-interface/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.github.megatronking.stringfog 2 | ARTIFACTID=interface 3 | VERSION=5.0.0 4 | 5 | -------------------------------------------------------------------------------- /stringfog-interface/src/main/java/com/github/megatronking/stringfog/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.megatronking.stringfog; 18 | 19 | /** 20 | * Utilities for encoding and decoding the Base64 representation of 21 | * binary data. See RFCs 2045 and 3548. 24 | */ 25 | public final class Base64 { 26 | 27 | /** 28 | * Default values for encoder/decoder flags. 29 | */ 30 | public static final int DEFAULT = 0; 31 | 32 | /** 33 | * Encoder flag bit to omit the padding '=' characters at the end 34 | * of the output (if any). 35 | */ 36 | public static final int NO_PADDING = 1; 37 | 38 | /** 39 | * Encoder flag bit to omit all line terminators (i.e., the output 40 | * will be on one long line). 41 | */ 42 | public static final int NO_WRAP = 2; 43 | 44 | /** 45 | * Encoder flag bit to indicate lines should be terminated with a 46 | * CRLF pair instead of just an LF. Has no effect if {@code 47 | * NO_WRAP} is specified as well. 48 | */ 49 | private static final int CRLF = 4; 50 | 51 | /** 52 | * Encoder/decoder flag bit to indicate using the "URL and 53 | * filename safe" variant of Base64 (see RFC 3548 section 4) where 54 | * {@code -} and {@code _} are used in place of {@code +} and 55 | * {@code /}. 56 | */ 57 | private static final int URL_SAFE = 8; 58 | 59 | // -------------------------------------------------------- 60 | // shared code 61 | // -------------------------------------------------------- 62 | 63 | /* package */ static abstract class Coder { 64 | byte[] output; 65 | int op; 66 | 67 | /** 68 | * Encode/decode another block of input data. this.output is 69 | * provided by the caller, and must be big enough to hold all 70 | * the coded data. On exit, this.opwill be set to the length 71 | * of the coded data. 72 | * 73 | * @param finish true if this is the final call to process for 74 | * this object. Will finalize the coder state and 75 | * include any final bytes in the output. 76 | * 77 | * @return true if the input so far is good; false if some 78 | * error has been detected in the input stream.. 79 | */ 80 | protected abstract boolean process(byte[] input, int offset, int len, boolean finish); 81 | 82 | /** 83 | * @return the maximum number of bytes a call to process() 84 | * could produce for the given number of input bytes. This may 85 | * be an overestimate. 86 | */ 87 | protected abstract int maxOutputSize(int len); 88 | } 89 | 90 | // -------------------------------------------------------- 91 | // decoding 92 | // -------------------------------------------------------- 93 | 94 | /** 95 | * Decode the Base64-encoded data in input and return the data in 96 | * a new byte array. 97 | * 98 | *

The padding '=' characters at the end are considered optional, but 99 | * if any are present, there must be the correct number of them. 100 | * 101 | * @param str the input String to decode, which is converted to 102 | * bytes using the default charset 103 | * @param flags controls certain features of the decoded output. 104 | * Pass {@code DEFAULT} to decode standard Base64. 105 | * 106 | * @throws IllegalArgumentException if the input contains 107 | * incorrect padding 108 | */ 109 | public static byte[] decode(String str, int flags) { 110 | return decode(str.getBytes(), flags); 111 | } 112 | 113 | /** 114 | * Decode the Base64-encoded data in input and return the data in 115 | * a new byte array. 116 | * 117 | *

The padding '=' characters at the end are considered optional, but 118 | * if any are present, there must be the correct number of them. 119 | * 120 | * @param input the input array to decode 121 | * @param flags controls certain features of the decoded output. 122 | * Pass {@code DEFAULT} to decode standard Base64. 123 | * 124 | * @throws IllegalArgumentException if the input contains 125 | * incorrect padding 126 | */ 127 | public static byte[] decode(byte[] input, int flags) { 128 | return decode(input, 0, input.length, flags); 129 | } 130 | 131 | /** 132 | * Decode the Base64-encoded data in input and return the data in 133 | * a new byte array. 134 | * 135 | *

The padding '=' characters at the end are considered optional, but 136 | * if any are present, there must be the correct number of them. 137 | * 138 | * @param input the data to decode 139 | * @param offset the position within the input array at which to start 140 | * @param len the number of bytes of input to decode 141 | * @param flags controls certain features of the decoded output. 142 | * Pass {@code DEFAULT} to decode standard Base64. 143 | * 144 | * @throws IllegalArgumentException if the input contains 145 | * incorrect padding 146 | */ 147 | private static byte[] decode(byte[] input, int offset, int len, int flags) { 148 | // Allocate space for the most data the input could represent. 149 | // (It could contain less if it contains whitespace, etc.) 150 | Decoder decoder = new Decoder(flags, new byte[len*3/4]); 151 | 152 | if (!decoder.process(input, offset, len, true)) { 153 | throw new IllegalArgumentException("bad base-64"); 154 | } 155 | 156 | // Maybe we got lucky and allocated exactly enough output space. 157 | if (decoder.op == decoder.output.length) { 158 | return decoder.output; 159 | } 160 | 161 | // Need to shorten the array, so allocate a new one of the 162 | // right size and copy. 163 | byte[] temp = new byte[decoder.op]; 164 | System.arraycopy(decoder.output, 0, temp, 0, decoder.op); 165 | return temp; 166 | } 167 | 168 | private static class Decoder extends Coder { 169 | /** 170 | * Lookup table for turning bytes into their position in the 171 | * Base64 alphabet. 172 | */ 173 | private static final int DECODE[] = { 174 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 175 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 176 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 177 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 178 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 179 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 180 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 181 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 182 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 183 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 184 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 185 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 186 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 187 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 188 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 189 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 190 | }; 191 | 192 | /** 193 | * Decode lookup table for the "web safe" variant (RFC 3548 194 | * sec. 4) where - and _ replace + and /. 195 | */ 196 | private static final int DECODE_WEBSAFE[] = { 197 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 198 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 199 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 200 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 201 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 202 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, 203 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 204 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 205 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 206 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 207 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 208 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 209 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 210 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 211 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 212 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 213 | }; 214 | 215 | /** Non-data values in the DECODE arrays. */ 216 | private static final int SKIP = -1; 217 | private static final int EQUALS = -2; 218 | 219 | /** 220 | * States 0-3 are reading through the next input tuple. 221 | * State 4 is having read one '=' and expecting exactly 222 | * one more. 223 | * State 5 is expecting no more data or padding characters 224 | * in the input. 225 | * State 6 is the error state; an error has been detected 226 | * in the input and no future input can "fix" it. 227 | */ 228 | private int state; // state number (0 to 6) 229 | private int value; 230 | 231 | final private int[] alphabet; 232 | 233 | private Decoder(int flags, byte[] output) { 234 | this.output = output; 235 | 236 | alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; 237 | state = 0; 238 | value = 0; 239 | } 240 | 241 | /** 242 | * @return an overestimate for the number of bytes {@code 243 | * len} bytes could decode to. 244 | */ 245 | public int maxOutputSize(int len) { 246 | return len * 3/4 + 10; 247 | } 248 | 249 | /** 250 | * Decode another block of input data. 251 | * 252 | * @return true if the state machine is still healthy. false if 253 | * bad base-64 data has been detected in the input stream. 254 | */ 255 | public boolean process(byte[] input, int offset, int len, boolean finish) { 256 | if (this.state == 6) return false; 257 | 258 | int p = offset; 259 | len += offset; 260 | 261 | // Using local variables makes the decoder about 12% 262 | // faster than if we manipulate the member variables in 263 | // the loop. (Even alphabet makes a measurable 264 | // difference, which is somewhat surprising to me since 265 | // the member variable is final.) 266 | int state = this.state; 267 | int value = this.value; 268 | int op = 0; 269 | final byte[] output = this.output; 270 | final int[] alphabet = this.alphabet; 271 | 272 | while (p < len) { 273 | // Try the fast path: we're starting a new tuple and the 274 | // next four bytes of the input stream are all data 275 | // bytes. This corresponds to going through states 276 | // 0-1-2-3-0. We expect to use this method for most of 277 | // the data. 278 | // 279 | // If any of the next four bytes of input are non-data 280 | // (whitespace, etc.), value will end up negative. (All 281 | // the non-data values in decode are small negative 282 | // numbers, so shifting any of them up and or'ing them 283 | // together will result in a value with its top bit set.) 284 | // 285 | // You can remove this whole block and the output should 286 | // be the same, just slower. 287 | if (state == 0) { 288 | while (p+4 <= len && 289 | (value = ((alphabet[input[p] & 0xff] << 18) | 290 | (alphabet[input[p+1] & 0xff] << 12) | 291 | (alphabet[input[p+2] & 0xff] << 6) | 292 | (alphabet[input[p+3] & 0xff]))) >= 0) { 293 | output[op+2] = (byte) value; 294 | output[op+1] = (byte) (value >> 8); 295 | output[op] = (byte) (value >> 16); 296 | op += 3; 297 | p += 4; 298 | } 299 | if (p >= len) break; 300 | } 301 | 302 | // The fast path isn't available -- either we've read a 303 | // partial tuple, or the next four input bytes aren't all 304 | // data, or whatever. Fall back to the slower state 305 | // machine implementation. 306 | 307 | int d = alphabet[input[p++] & 0xff]; 308 | 309 | switch (state) { 310 | case 0: 311 | if (d >= 0) { 312 | value = d; 313 | ++state; 314 | } else if (d != SKIP) { 315 | this.state = 6; 316 | return false; 317 | } 318 | break; 319 | 320 | case 1: 321 | if (d >= 0) { 322 | value = (value << 6) | d; 323 | ++state; 324 | } else if (d != SKIP) { 325 | this.state = 6; 326 | return false; 327 | } 328 | break; 329 | 330 | case 2: 331 | if (d >= 0) { 332 | value = (value << 6) | d; 333 | ++state; 334 | } else if (d == EQUALS) { 335 | // Emit the last (partial) output tuple; 336 | // expect exactly one more padding character. 337 | output[op++] = (byte) (value >> 4); 338 | state = 4; 339 | } else if (d != SKIP) { 340 | this.state = 6; 341 | return false; 342 | } 343 | break; 344 | 345 | case 3: 346 | if (d >= 0) { 347 | // Emit the output triple and return to state 0. 348 | value = (value << 6) | d; 349 | output[op+2] = (byte) value; 350 | output[op+1] = (byte) (value >> 8); 351 | output[op] = (byte) (value >> 16); 352 | op += 3; 353 | state = 0; 354 | } else if (d == EQUALS) { 355 | // Emit the last (partial) output tuple; 356 | // expect no further data or padding characters. 357 | output[op+1] = (byte) (value >> 2); 358 | output[op] = (byte) (value >> 10); 359 | op += 2; 360 | state = 5; 361 | } else if (d != SKIP) { 362 | this.state = 6; 363 | return false; 364 | } 365 | break; 366 | 367 | case 4: 368 | if (d == EQUALS) { 369 | ++state; 370 | } else if (d != SKIP) { 371 | this.state = 6; 372 | return false; 373 | } 374 | break; 375 | 376 | case 5: 377 | if (d != SKIP) { 378 | this.state = 6; 379 | return false; 380 | } 381 | break; 382 | } 383 | } 384 | 385 | if (!finish) { 386 | // We're out of input, but a future call could provide 387 | // more. 388 | this.state = state; 389 | this.value = value; 390 | this.op = op; 391 | return true; 392 | } 393 | 394 | // Done reading input. Now figure out where we are left in 395 | // the state machine and finish up. 396 | 397 | switch (state) { 398 | case 0: 399 | // Output length is a multiple of three. Fine. 400 | break; 401 | case 1: 402 | // Read one extra input byte, which isn't enough to 403 | // make another output byte. Illegal. 404 | this.state = 6; 405 | return false; 406 | case 2: 407 | // Read two extra input bytes, enough to emit 1 more 408 | // output byte. Fine. 409 | output[op++] = (byte) (value >> 4); 410 | break; 411 | case 3: 412 | // Read three extra input bytes, enough to emit 2 more 413 | // output bytes. Fine. 414 | output[op++] = (byte) (value >> 10); 415 | output[op++] = (byte) (value >> 2); 416 | break; 417 | case 4: 418 | // Read one padding '=' when we expected 2. Illegal. 419 | this.state = 6; 420 | return false; 421 | case 5: 422 | // Read all the padding '='s we expected and no more. 423 | // Fine. 424 | break; 425 | } 426 | 427 | this.state = state; 428 | this.op = op; 429 | return true; 430 | } 431 | } 432 | 433 | /** 434 | * Base64-encode the given data and return a newly allocated 435 | * byte[] with the result. 436 | * 437 | * @param str the string to encode 438 | * @param flags controls certain features of the encoded output. 439 | * Passing {@code DEFAULT} results in output that 440 | * adheres to RFC 2045. 441 | */ 442 | public static byte[] encode(String str, int flags) { 443 | return encode(str.getBytes(), flags); 444 | } 445 | 446 | /** 447 | * Base64-encode the given data and return a newly allocated 448 | * byte[] with the result. 449 | * 450 | * @param input the data to encode 451 | * @param flags controls certain features of the encoded output. 452 | * Passing {@code DEFAULT} results in output that 453 | * adheres to RFC 2045. 454 | */ 455 | public static byte[] encode(byte[] input, int flags) { 456 | return encode(input, 0, input.length, flags); 457 | } 458 | 459 | /** 460 | * Base64-encode the given data and return a newly allocated 461 | * byte[] with the result. 462 | * 463 | * @param input the data to encode 464 | * @param offset the position within the input array at which to 465 | * start 466 | * @param len the number of bytes of input to encode 467 | * @param flags controls certain features of the encoded output. 468 | * Passing {@code DEFAULT} results in output that 469 | * adheres to RFC 2045. 470 | */ 471 | private static byte[] encode(byte[] input, int offset, int len, int flags) { 472 | Encoder encoder = new Encoder(flags, null); 473 | 474 | // Compute the exact length of the array we will produce. 475 | int output_len = len / 3 * 4; 476 | 477 | // Account for the tail of the data and the padding bytes, if any. 478 | if (encoder.do_padding) { 479 | if (len % 3 > 0) { 480 | output_len += 4; 481 | } 482 | } else { 483 | switch (len % 3) { 484 | case 0: break; 485 | case 1: output_len += 2; break; 486 | case 2: output_len += 3; break; 487 | } 488 | } 489 | 490 | // Account for the newlines, if any. 491 | if (encoder.do_newline && len > 0) { 492 | output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * 493 | (encoder.do_cr ? 2 : 1); 494 | } 495 | 496 | encoder.output = new byte[output_len]; 497 | encoder.process(input, offset, len, true); 498 | 499 | return encoder.output; 500 | } 501 | 502 | private static class Encoder extends Coder { 503 | /** 504 | * Emit a new line every this many output tuples. Corresponds to 505 | * a 76-character line length (the maximum allowable according to 506 | * RFC 2045). 507 | */ 508 | static final int LINE_GROUPS = 19; 509 | 510 | /** 511 | * Lookup table for turning Base64 alphabet positions (6 bits) 512 | * into output bytes. 513 | */ 514 | private static final byte ENCODE[] = { 515 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 516 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 517 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 518 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 519 | }; 520 | 521 | /** 522 | * Lookup table for turning Base64 alphabet positions (6 bits) 523 | * into output bytes. 524 | */ 525 | private static final byte ENCODE_WEBSAFE[] = { 526 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 527 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 528 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 529 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', 530 | }; 531 | 532 | final private byte[] tail; 533 | /* package */ int tailLen; 534 | private int count; 535 | 536 | final boolean do_padding; 537 | final boolean do_newline; 538 | final boolean do_cr; 539 | final private byte[] alphabet; 540 | 541 | private Encoder(int flags, byte[] output) { 542 | this.output = output; 543 | 544 | do_padding = (flags & NO_PADDING) == 0; 545 | do_newline = (flags & NO_WRAP) == 0; 546 | do_cr = (flags & CRLF) != 0; 547 | alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; 548 | 549 | tail = new byte[2]; 550 | tailLen = 0; 551 | 552 | count = do_newline ? LINE_GROUPS : -1; 553 | } 554 | 555 | /** 556 | * @return an overestimate for the number of bytes {@code 557 | * len} bytes could encode to. 558 | */ 559 | public int maxOutputSize(int len) { 560 | return len * 8/5 + 10; 561 | } 562 | 563 | public boolean process(byte[] input, int offset, int len, boolean finish) { 564 | // Using local variables makes the encoder about 9% faster. 565 | final byte[] alphabet = this.alphabet; 566 | final byte[] output = this.output; 567 | int op = 0; 568 | int count = this.count; 569 | 570 | int p = offset; 571 | len += offset; 572 | int v = -1; 573 | 574 | // First we need to concatenate the tail of the previous call 575 | // with any input bytes available now and see if we can empty 576 | // the tail. 577 | 578 | switch (tailLen) { 579 | case 0: 580 | // There was no tail. 581 | break; 582 | 583 | case 1: 584 | if (p+2 <= len) { 585 | // A 1-byte tail with at least 2 bytes of 586 | // input available now. 587 | v = ((tail[0] & 0xff) << 16) | 588 | ((input[p++] & 0xff) << 8) | 589 | (input[p++] & 0xff); 590 | tailLen = 0; 591 | } 592 | break; 593 | 594 | case 2: 595 | if (p+1 <= len) { 596 | // A 2-byte tail with at least 1 byte of input. 597 | v = ((tail[0] & 0xff) << 16) | 598 | ((tail[1] & 0xff) << 8) | 599 | (input[p++] & 0xff); 600 | tailLen = 0; 601 | } 602 | break; 603 | } 604 | 605 | if (v != -1) { 606 | output[op++] = alphabet[(v >> 18) & 0x3f]; 607 | output[op++] = alphabet[(v >> 12) & 0x3f]; 608 | output[op++] = alphabet[(v >> 6) & 0x3f]; 609 | output[op++] = alphabet[v & 0x3f]; 610 | if (--count == 0) { 611 | if (do_cr) output[op++] = '\r'; 612 | output[op++] = '\n'; 613 | count = LINE_GROUPS; 614 | } 615 | } 616 | 617 | // At this point either there is no tail, or there are fewer 618 | // than 3 bytes of input available. 619 | 620 | // The main loop, turning 3 input bytes into 4 output bytes on 621 | // each iteration. 622 | while (p+3 <= len) { 623 | v = ((input[p] & 0xff) << 16) | 624 | ((input[p+1] & 0xff) << 8) | 625 | (input[p+2] & 0xff); 626 | output[op] = alphabet[(v >> 18) & 0x3f]; 627 | output[op+1] = alphabet[(v >> 12) & 0x3f]; 628 | output[op+2] = alphabet[(v >> 6) & 0x3f]; 629 | output[op+3] = alphabet[v & 0x3f]; 630 | p += 3; 631 | op += 4; 632 | if (--count == 0) { 633 | if (do_cr) output[op++] = '\r'; 634 | output[op++] = '\n'; 635 | count = LINE_GROUPS; 636 | } 637 | } 638 | 639 | if (finish) { 640 | // Finish up the tail of the input. Note that we need to 641 | // consume any bytes in tail before any bytes 642 | // remaining in input; there should be at most two bytes 643 | // total. 644 | 645 | if (p-tailLen == len-1) { 646 | int t = 0; 647 | v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; 648 | tailLen -= t; 649 | output[op++] = alphabet[(v >> 6) & 0x3f]; 650 | output[op++] = alphabet[v & 0x3f]; 651 | if (do_padding) { 652 | output[op++] = '='; 653 | output[op++] = '='; 654 | } 655 | if (do_newline) { 656 | if (do_cr) output[op++] = '\r'; 657 | output[op++] = '\n'; 658 | } 659 | } else if (p-tailLen == len-2) { 660 | int t = 0; 661 | v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | 662 | (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); 663 | tailLen -= t; 664 | output[op++] = alphabet[(v >> 12) & 0x3f]; 665 | output[op++] = alphabet[(v >> 6) & 0x3f]; 666 | output[op++] = alphabet[v & 0x3f]; 667 | if (do_padding) { 668 | output[op++] = '='; 669 | } 670 | if (do_newline) { 671 | if (do_cr) output[op++] = '\r'; 672 | output[op++] = '\n'; 673 | } 674 | } else if (do_newline && op > 0 && count != LINE_GROUPS) { 675 | if (do_cr) output[op++] = '\r'; 676 | output[op++] = '\n'; 677 | } 678 | } else { 679 | // Save the leftovers in tail to be consumed on the next 680 | // call to encodeInternal. 681 | 682 | if (p == len-1) { 683 | tail[tailLen++] = input[p]; 684 | } else if (p == len-2) { 685 | tail[tailLen++] = input[p]; 686 | tail[tailLen++] = input[p+1]; 687 | } 688 | } 689 | 690 | this.op = op; 691 | this.count = count; 692 | 693 | return true; 694 | } 695 | } 696 | 697 | private Base64() { } // don't instantiate 698 | } 699 | -------------------------------------------------------------------------------- /stringfog-interface/src/main/java/com/github/megatronking/stringfog/IKeyGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.megatronking.stringfog; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A generator uses to generate security keys. 7 | * 8 | * @author Megatron King 9 | * @since 2022/1/14 22:15 10 | */ 11 | public interface IKeyGenerator { 12 | 13 | /** 14 | * Generate a security key. 15 | * 16 | * @param text The content text will be encrypted. 17 | * @return A security key for the encryption. 18 | */ 19 | byte[] generate(String text); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /stringfog-interface/src/main/java/com/github/megatronking/stringfog/IStringFog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog; 16 | 17 | /** 18 | * Interface of how to encrypt and decrypt a string. 19 | * 20 | * @author Megatron King 21 | * @since 2018/9/20 16:15 22 | */ 23 | public interface IStringFog { 24 | 25 | /** 26 | * Encrypt the data by the special key. 27 | * 28 | * @param data The original data. 29 | * @param key Encrypt key. 30 | * @return The encrypted data. 31 | */ 32 | byte[] encrypt(String data, byte[] key); 33 | 34 | /** 35 | * Decrypt the data to origin by the special key. 36 | * 37 | * @param data The encrypted data. 38 | * @param key Encrypt key. 39 | * @return The original data. 40 | */ 41 | String decrypt(byte[] data, byte[] key); 42 | 43 | /** 44 | * Whether the string should be encrypted. 45 | * 46 | * @param data The original data. 47 | * @return If you want to skip this String, return false. 48 | */ 49 | boolean shouldFog(String data); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /stringfog-interface/src/main/java/com/github/megatronking/stringfog/StringFogWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog; 16 | 17 | import java.lang.reflect.InvocationTargetException; 18 | 19 | /** 20 | * A wrapper for the real implementation of fogs. 21 | * 22 | * @author Megatron King 23 | * @since 2018/9/20 16:14 24 | */ 25 | public final class StringFogWrapper implements IStringFog { 26 | 27 | private final IStringFog mStringFogImpl; 28 | 29 | public StringFogWrapper(String impl) { 30 | try { 31 | mStringFogImpl = (IStringFog) Class.forName(impl).getDeclaredConstructor().newInstance(); 32 | } catch (ClassNotFoundException e) { 33 | throw new IllegalArgumentException("Stringfog implementation class not found: " + impl); 34 | } catch (InstantiationException e) { 35 | throw new IllegalArgumentException("Stringfog implementation class new instance failed: " 36 | + e.getMessage()); 37 | } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 38 | throw new IllegalArgumentException("Stringfog implementation class create instance failed: " 39 | + e.getMessage()); 40 | } 41 | } 42 | 43 | @Override 44 | public byte[] encrypt(String data, byte[] key) { 45 | return mStringFogImpl == null ? data.getBytes() : mStringFogImpl.encrypt(data, key); 46 | } 47 | 48 | @Override 49 | public String decrypt(byte[] data, byte[] key) { 50 | return mStringFogImpl == null ? new String(data) : mStringFogImpl.decrypt(data, key); 51 | } 52 | 53 | @Override 54 | public boolean shouldFog(String data) { 55 | return mStringFogImpl != null && mStringFogImpl.shouldFog(data); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /stringfog-interface/src/main/java/com/github/megatronking/stringfog/annotation/StringFogIgnore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Megatron King 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.github.megatronking.stringfog.annotation; 16 | 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | import java.lang.annotation.Target; 20 | 21 | import static java.lang.annotation.ElementType.TYPE; 22 | 23 | /** 24 | * This annotation could keep no fog for string. 25 | * 26 | * @author Megatron King 27 | * @since 2017/3/8 8:41 28 | */ 29 | @Retention(RetentionPolicy.CLASS) 30 | @Target(value={TYPE}) 31 | public @interface StringFogIgnore { 32 | 33 | } 34 | --------------------------------------------------------------------------------