├── ApkJiagu.iml
├── LICENSE
├── README.md
├── outlibs
├── AXMLEditor.jar
├── AXMLPrinter2.jar
└── apktool_2.5.0.jar
└── src
└── com
└── sakuqi
└── shell
├── Main.kt
└── utils
├── DexUtils.kt
├── EncryptUtils.kt
├── FileUtils.kt
├── SignUtils.kt
├── XmlParseUtils.kt
└── ZipUtils.kt
/ApkJiagu.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ApkJiagu
2 |
3 | #### 介绍
4 | 该项目讲诉如何将 apk 反编译、编译、修改 AndroidManifest文件、加密 dex 文件、重新打包、签名、加固
5 |
6 | [Android APK 加固技术探究(一)](https://juejin.cn/post/6985038911680544775/)
7 |
8 | [Android APK 加固技术探究(二)](https://juejin.cn/post/6985040096042942495/)
9 |
10 | [Android APK 加固技术探究(三)](https://juejin.cn/post/7025600894829854733/)
11 |
12 | > 为了保证 Android 应用的源码安全性,我们一般会对上线的应用进行代码混淆,然而仅仅做代码混淆还不够,我们还要对我们的应用加固,防止别人通过反编译获取到我们的源码。目前 apk 加固技术比较成熟完善,市面上比较流行的有“360加固”。本文就 apk 加固技术做一个技术探究,希望读者看过后能明白加固的其中原理,并也能自己实现加固方案。
13 |
14 | 在 [Android apk 加固技术探究(二)](https://juejin.cn/post/6985040096042942495/)中,我们已经通过创建 Steady 模块生成了一个 shell.arr 文件,用来对加密后的 dex 文件进行解密和类加载操作。这篇文章主要讲解如何对原 apk 的 dex 加密和把 shell.arr 打入到原 apk 中并最终生成一个新的 apk
15 |
16 | ### 一、反编译 APK 文件
17 | [Android APK 加固技术探究(一)](https://juejin.cn/post/6985038911680544775/)中讲解了如何反编译 apk 文件,这里使用 apktool 这个工具来反编译 apk。通过执行命令 java -jar outlibs/apktool_2.5.0.jar d '待解压apk路径' -o '解压后存放的路径'
18 |
19 | ```kotlin
20 | /**
21 | * 反编译 APK 文件
22 | */
23 | fun apkDecode(){
24 | println("开始反编译")
25 | val process = Runtime.getRuntime()
26 | .exec("java -jar outlibs/apktool_2.5.0.jar d "+ orginApk.absolutePath+" -o "+apkDecode.absolutePath)
27 | process.waitFor()
28 | if(process.exitValue() != 0) {
29 | FileUtils.printStream(process.errorStream)
30 | }else{
31 | FileUtils.printStream(process.inputStream)
32 | }
33 | process.destroy()
34 | }
35 | ```
36 | ### 二、修改 AndroidManifest.xml 文件
37 | 步骤一中获得了解压后的文件目录,找到目录中的 AndroidManifest 文件。这里修改 AndroidManifest.xml 文件尝试过有2种方式,一种是通过 “AXMLEditor.jar” 和 “AXMLPrinter2.jar” 工具修改 AndroidManifest.xml 文件,另一种是通过 SAX 的方式解析 xml 文件,然后在相应的节点位置插入需要的数据,最后发现方法一虽然修改了xml 文件但是最终打包的新 APK 中 AndroidManifest.xml 文件没有生效,后来使用方法二生效了。下面把2种方式的代码都贴出来,如果哪个大佬发现了方法一中的问题,还请不吝赐教。
38 |
39 | 方法一:关于 “AXMLEditor.jar” 和 “AXMLPrinter2.jar” 两个工具如何使用可以自行百度,这里不做展开
40 | ```kotlin
41 | /**
42 | * 修改 AndroidManifest
43 | */
44 | fun changeAndroidManifest(apkUnzipDir:File){
45 | val aManifest = apkUnzipDir.listFiles { _, name ->
46 | name?.equals("AndroidManifest.xml") == true
47 | }
48 | val file = if (aManifest != null && aManifest.isNotEmpty()) {
49 | aManifest[0]
50 | }else{null}
51 | file?.let {
52 | //将模版插入 AndroidManifest 中
53 | val process2 = Runtime.getRuntime()
54 | .exec("java -jar outlibs/AXMLEditor.jar -tag -i tool/src/main/assets/ApplicationName.xml " +
55 | file.absolutePath+" "+file.absolutePath)
56 | process2.waitFor()
57 | if(process2.exitValue() != 0) {
58 | println("2")
59 | FileUtils.printStream(process2.errorStream)
60 | }
61 | process2.destroy()
62 |
63 | //解析出原来的 Application 类名
64 | var process0 = Runtime.getRuntime()
65 | .exec("java -jar tool/libs/AXMLPrinter2.jar "+file.absolutePath)
66 | process0.waitFor()
67 | val applicationPath = XmlParseUtils.sax2xml(process0.inputStream)
68 | if(process0.exitValue() != 0){
69 | println("0")
70 | FileUtils.printStream(process0.errorStream)
71 | }
72 | process0.destroy()
73 |
74 | //参考 https://github.com/fourbrother/AXMLEditor
75 | //修改 Application 下 插入标签的值
76 | val process1 = Runtime.getRuntime()
77 | .exec("java -jar tool/libs/AXMLEditor.jar -attr -i meta-data package value "+applicationPath
78 | + " " + file.absolutePath+" "+file.absolutePath)
79 | process1.waitFor()
80 | if(process1.exitValue() != 0){
81 | println("1")
82 | FileUtils.printStream(process1.errorStream)
83 | }
84 | process1.destroy()
85 |
86 | //参考 https://github.com/fourbrother/AXMLEditor
87 | //修改 Application 下 name 标签
88 | val process3 = Runtime.getRuntime()
89 | .exec("java -jar tool/libs/AXMLEditor.jar -attr -m application package name com.sakuqi.shell.NewApplication"
90 | + " " + file.absolutePath+" "+file.absolutePath)
91 | process3.waitFor()
92 | if(process3.exitValue() != 0){
93 | println("3")
94 | FileUtils.printStream(process3.errorStream)
95 | }
96 | process3.destroy()
97 |
98 | //解析出原来的 Application 类名
99 | var process4 = Runtime.getRuntime()
100 | .exec("java -jar tool/libs/AXMLPrinter2.jar "+file.absolutePath)
101 | process4.waitFor()
102 | FileUtils.printStream(process4.inputStream)
103 | process4.destroy()
104 |
105 | }
106 | }
107 | ```
108 |
109 | 方法二:SAXReader 的使用方式自行查看相关 API 文档
110 | ```kotlin
111 | /**
112 | * 修改 xml 文件
113 | */
114 | fun changeAndroidManifest(){
115 | println("开始修改 AndroidManifest")
116 | var manifestFile = File("output/apktool/decode/AndroidManifest.xml")
117 | changeXmlBySax(manifestFile,"com.sakuqi.steady.SteadyApplication")
118 | //com.sakuqi.steady.SteadyApplication名称为 Shell.arr 中的Application 类
119 | }
120 |
121 | /**
122 | * 修改xml文件
123 | */
124 | fun changeXmlBySax(fileXml:File,newApplicationName:String){
125 | var sax = SAXReader()
126 | var document = sax.read(fileXml)
127 | var root = document.rootElement
128 | var application = root.element("application")
129 | //原有的 application 名称
130 | var applicationName = application.attributeValue("name")
131 | var applicationAttr = application.attribute("name")
132 | //将壳中的 application 替换原来的 application
133 | applicationAttr.text = newApplicationName
134 |
135 | var element = application.addElement("meta-data")
136 | element.addAttribute("android:name","app_name")
137 | element.addAttribute("android:value",applicationName)
138 | saveDocument(document,fileXml)
139 |
140 | }
141 | fun saveDocument(document:Document,file:File){
142 | var osWrite = OutputStreamWriter(FileOutputStream(file))
143 | var format = OutputFormat.createPrettyPrint()// 获取输出的指定格式
144 | format.encoding = "UTF-8"
145 | var writer = XMLWriter(osWrite,format)
146 | writer.write(document)
147 | writer.flush()
148 | writer.close()
149 | }
150 | ```
151 | ### 三、编译修改 AndroidManifest.xml 后的反编译目录
152 |
153 | ```
154 | /**
155 | * 编译 APK 文件
156 | */
157 | fun apkBuild(){
158 | println("开始重新编译")
159 | val process = Runtime.getRuntime()
160 | .exec("java -jar outlibs/apktool_2.5.0.jar b "+ "反编译后的目录"+" -o "+ “编译后的目录”)
161 | process.waitFor()
162 | if(process.exitValue() != 0) {
163 | FileUtils.printStream(process.errorStream)
164 | }else{
165 | FileUtils.printStream(process.inputStream)
166 | }
167 | process.destroy()
168 | }
169 | ```
170 |
171 | ### 四、解压 APK 文件并加密所以 Dex 文件
172 |
173 | 解压使用的是 java.util.zip.ZipFile 类,这里封装了工具类最后会放到源码里,这里就不展开了。解压后需要将原 apk 中的签名文件删除,以便后续重新签名。过滤出解压目录下的所有 dex 后缀文件,然后对其进行加密,需要注意的是加密方式需要和 shell.arr 中的解密方式保持一致,这里使用的是 AES 的加密方式,源代码会在后续的开源项目中展示。加密后需要将原来的 dex 文件删除。大致代码如下:
174 | ```kotlin
175 | /**
176 | * 解压 APK 文件并加密所有的dex文件
177 | */
178 | fun unZipApkAndEncrypt(){
179 | println("解压 APK")
180 | val apkUnzipDir = File("output/unzip/apk")
181 | if(!apkUnzipDir.exists()){
182 | apkUnzipDir.mkdirs()
183 | }
184 | FileUtils.delete(apkUnzipDir)
185 | ZipUtils.unZip(apkBuild,apkUnzipDir)
186 | //删除 META-INF/CERT.RSA,META-INF/CERT.SF,META-INF/MANIFEST.MF
187 | val certRSA = File(apkUnzipDir,"META-INF/CERT.RSA")
188 | certRSA.delete()
189 | val certSF = File(apkUnzipDir,"META-INF/CERT.SF")
190 | certSF.delete()
191 | val manifestMF = File(apkUnzipDir,"META-INF/MANIFEST.MF")
192 | manifestMF.delete()
193 | //changeAndroidManifest(apkUnzipDir)
194 | //获取dex 文件
195 | val apkFiles = apkUnzipDir.listFiles(object :FilenameFilter{
196 | override fun accept(dir: File?, name: String?): Boolean {
197 | return name?.endsWith(".dex") == true
198 | }
199 | })
200 | for (dexFile in apkFiles){
201 | val name = dexFile.name
202 | println("dex:$name")
203 | val bytes = DexUtils.getBytes(dexFile)
204 | val encrypt: ByteArray? = EncryptUtils.encrypt(bytes, EncryptUtils.ivBytes)
205 | val fos: FileOutputStream = FileOutputStream(
206 | File(
207 | dexFile.parent,
208 | "secret-" + dexFile.getName()
209 | )
210 | )
211 | fos.write(encrypt)
212 | fos.flush()
213 | fos.close()
214 | dexFile.delete()
215 | }
216 |
217 | }
218 | ```
219 |
220 | ### 五、解压壳 aar 得到 class.jar ,然后把 class.jar 在转换成 class.dex,再将class.dex 移到原 apk 的解压目录,最后压缩成新的 apk 文件
221 | 这里解压依然使用的是 unzip 的工具类,转换 class.dex 使用的是 Android SDK 中自带的命令 dx
222 |
223 | ```kotlin
224 | /**
225 | * 解压壳aar 并转化jar 为dex
226 | */
227 | fun makeDecodeDex(){
228 | println("解压壳 AAR")
229 | var shellUnzipDir = File("output/unzip/shell")
230 | if(!shellUnzipDir.exists()){
231 | shellUnzipDir.mkdirs()
232 | }
233 | FileUtils.delete(shellUnzipDir)
234 | //解压 AAR
235 | ZipUtils.unZip(shellAAR,shellUnzipDir)
236 | //将 jar 转成 dex
237 | println("将 jar 转成 dex")
238 | var shellJar = File(shellUnzipDir,"classes.jar")
239 | var shellDex = File("output/unzip/apk","classes.dex")
240 | DexUtils.dxCommand(shellJar,shellDex)
241 | moveLibSoToApk()
242 | //打包
243 | println("打包 APK")
244 | var unsignedApk = File("output/unsigned_$orginApkName")
245 | ZipUtils.zip(File("output/unzip/apk"),unsignedApk)
246 | }
247 |
248 | /**
249 | * 将壳中的lib文件移到apk 中
250 | */
251 | fun moveLibSoToApk(){
252 | var shellUnzipLibDir = File("output/unzip/shell/jni")
253 | var apkUnzipLibDir = File("output/unzip/apk/lib")
254 | if(!apkUnzipLibDir.exists()){
255 | apkUnzipLibDir.mkdirs()
256 | }
257 |
258 | FileUtils.copy(shellUnzipLibDir,apkUnzipLibDir)
259 | }
260 | ```
261 | ```kotlin
262 | object DexUtils {
263 | @Throws(IOException::class,InterruptedException::class)
264 | fun dxCommand(jar:File,dex:File){
265 | var runtime = Runtime.getRuntime()
266 | var process = runtime.exec("dx --dex --output "+dex.absolutePath+" "+jar.absolutePath)
267 | try {
268 | process.waitFor()
269 | }catch (e:InterruptedException){
270 | e.printStackTrace()
271 | throw e
272 | }
273 | if(process.exitValue() != 0){
274 | val inputStream = process.errorStream
275 | var buffer = ByteArray(1024)
276 | val bos = ByteArrayOutputStream()
277 | var len = inputStream.read(buffer)
278 | while (len != -1){
279 | bos.write(buffer,0,len)
280 | len = inputStream.read(buffer)
281 | }
282 | System.out.println(String(bos.toByteArray(), Charset.forName("GBK")))
283 | throw RuntimeException("dx run failed")
284 | }else{
285 | System.out.println("执行成功:"+process.exitValue())
286 | }
287 | process.destroy()
288 | }
289 |
290 | /**
291 | * 读取文件
292 | * @param file
293 | * @return
294 | * @throws Exception
295 | */
296 | @Throws(Exception::class)
297 | fun getBytes(file: File?): ByteArray {
298 | val r = RandomAccessFile(file, "r")
299 | val buffer = ByteArray(r.length().toInt())
300 | r.readFully(buffer)
301 | r.close()
302 | return buffer
303 | }
304 | }
305 | ```
306 |
307 | ### 五、将压缩后的新的 apk 文件进行 zip 对齐操作
308 | ```kotlin
309 | /**
310 | * 对齐
311 | */
312 | fun zipalign(){
313 | println("将打包的 apk 对齐")
314 | var unsignedApk = File("output/unsigned_$orginApkName")
315 | val alignedApk = File("output/unsigned-aligned_$orginApkName")
316 | val process = Runtime.getRuntime().exec(
317 | "zipalign -p -f -v 4 " + unsignedApk.absolutePath + " " + alignedApk.absolutePath)
318 | process.waitFor(5,TimeUnit.SECONDS)
319 | try {
320 | if (process.exitValue() != 0) {
321 | println("zipalign 出错")
322 | FileUtils.printStream(process.errorStream)
323 | } else {
324 | FileUtils.printStream(process.inputStream)
325 | }
326 | println("完成 apk 的对齐")
327 | process.destroy()
328 | }catch (e:Exception){
329 | println("对齐超时...")
330 | }
331 | }
332 | ```
333 |
334 | ### 六、将对齐后的 apk 文件进行签名
335 | ```kotlin
336 | /**
337 | * 对 APK 签名
338 | */
339 | fun jksToApk(){
340 | println("签名 APK")
341 | var signedApk = File("output/signed_$orginApkName")
342 | val alignedApk = File("output/unsigned-aligned_$orginApkName")
343 | SignUtils.signature(alignedApk,signedApk,signFile.absolutePath)
344 | }
345 | ```
346 | ```kotlin
347 | object SignUtils {
348 | @Throws(InterruptedException::class, IOException::class)
349 | fun signature(unsignedApk: File, signedApk: File, keyStore: String) {
350 | val cmd = arrayOf(
351 | "jarsigner",
352 | "-sigalg",
353 | "SHA1withRSA",
354 | "-digestalg",
355 | "SHA1",
356 | "-keystore",
357 | keyStore,
358 | "-storepass",
359 | "密码",
360 | "-keypass",
361 | "密码",
362 | "-signedjar",
363 | signedApk.absolutePath,
364 | unsignedApk.absolutePath,
365 | "alinas"
366 | )
367 | val process = Runtime.getRuntime().exec(cmd)
368 | println("start sign")
369 | try {
370 | val waitResult = process.waitFor()
371 | println("waitResult: $waitResult")
372 | } catch (e: InterruptedException) {
373 | e.printStackTrace()
374 | throw e
375 | }
376 |
377 | println("process.exitValue() " + process.exitValue())
378 | if (process.exitValue() != 0) {
379 | val inputStream = process.errorStream
380 | var len: Int
381 | val buffer = ByteArray(2048)
382 | val bos = ByteArrayOutputStream()
383 | len = inputStream.read(buffer)
384 | while (len != -1) {
385 | bos.write(buffer, 0, len)
386 | len = inputStream.read(buffer)
387 | }
388 | println(String(bos.toByteArray(), Charset.forName("gbk")))
389 | throw RuntimeException("签名执行失败")
390 | }
391 | println("finish signed")
392 | process.destroy()
393 | }
394 | }
395 | ```
396 |
397 | 至此 apk 的加固流程全部讲完
398 |
399 | #### 参与贡献
400 |
401 | 1. Fork 本仓库
402 | 2. 新建 Feat_xxx 分支
403 | 3. 提交代码
404 | 4. 新建 Pull Request
405 |
406 |
407 | #### 特技
408 |
409 | 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
410 | 2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
411 | 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
412 | 4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
413 | 5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
414 | 6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
415 |
--------------------------------------------------------------------------------
/outlibs/AXMLEditor.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openJK-dev/AppJiagu-java/4456aaae7cd30392f652e5f085a3852f978a047a/outlibs/AXMLEditor.jar
--------------------------------------------------------------------------------
/outlibs/AXMLPrinter2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openJK-dev/AppJiagu-java/4456aaae7cd30392f652e5f085a3852f978a047a/outlibs/AXMLPrinter2.jar
--------------------------------------------------------------------------------
/outlibs/apktool_2.5.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openJK-dev/AppJiagu-java/4456aaae7cd30392f652e5f085a3852f978a047a/outlibs/apktool_2.5.0.jar
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/Main.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell
2 |
3 | import com.sakuqi.shell.utils.*
4 | import java.io.File
5 | import java.io.FileOutputStream
6 | import java.io.FilenameFilter
7 | import java.util.concurrent.TimeUnit
8 |
9 |
10 | // 未签名 APK
11 | lateinit var orginApk:File
12 | lateinit var orginApkName:String
13 | // 壳 AAR
14 | lateinit var shellAAR:File
15 | //签名文件
16 | var signFile = File("keystore/wuse.jks")
17 | //apk 反编译目录
18 | val apkDecode = File("output/apktool/decode")
19 | //apk 重新编译
20 | lateinit var apkBuild :File
21 |
22 | fun findOriginApk(){
23 | var inputDir = File("input/apk")
24 | var apkFile = inputDir.listFiles { _, name ->
25 | name.endsWith(".apk")
26 | }.first()
27 | if(apkFile.exists()){
28 | orginApk = apkFile
29 | orginApkName = orginApk.name
30 | apkBuild = File("output/apktool",orginApkName)
31 | }else{
32 | throw Exception("没有找到加固 apk")
33 | }
34 | }
35 |
36 | fun findOriginShell(){
37 | var inputDir = File("input/shell")
38 | var shellFile = inputDir.listFiles { _, name ->
39 | name.endsWith(".aar")
40 | }.first()
41 | if(shellFile.exists()){
42 | shellAAR = shellFile
43 | }else{
44 | throw Exception("没有找到壳 aar")
45 | }
46 | }
47 |
48 | /**
49 | * 反编译 APK 文件
50 | */
51 | fun apkDecode(){
52 | println("开始反编译")
53 | val process = Runtime.getRuntime()
54 | .exec("java -jar outlibs/apktool_2.5.0.jar d "+ orginApk.absolutePath+" -o "+apkDecode.absolutePath)
55 | process.waitFor()
56 | if(process.exitValue() != 0) {
57 | FileUtils.printStream(process.errorStream)
58 | }else{
59 | FileUtils.printStream(process.inputStream)
60 | }
61 | process.destroy()
62 | }
63 |
64 | /**
65 | * 编译 APK 文件
66 | */
67 | fun apkBuild(){
68 | println("开始重新编译")
69 | val process = Runtime.getRuntime()
70 | .exec("java -jar outlibs/apktool_2.5.0.jar b "+ apkDecode.absolutePath+" -o "+ apkBuild.absolutePath)
71 | process.waitFor()
72 | if(process.exitValue() != 0) {
73 | FileUtils.printStream(process.errorStream)
74 | }else{
75 | FileUtils.printStream(process.inputStream)
76 | }
77 | process.destroy()
78 | }
79 |
80 | /**
81 | * 解压 APK 文件并加密所有的dex文件
82 | */
83 | fun unZipApkAndEncrypt(){
84 | println("解压 APK")
85 | val apkUnzipDir = File("output/unzip/apk")
86 | if(!apkUnzipDir.exists()){
87 | apkUnzipDir.mkdirs()
88 | }
89 | FileUtils.delete(apkUnzipDir)
90 | ZipUtils.unZip(apkBuild,apkUnzipDir)
91 | //删除 META-INF/CERT.RSA,META-INF/CERT.SF,META-INF/MANIFEST.MF
92 | val certRSA = File(apkUnzipDir,"META-INF/CERT.RSA")
93 | certRSA.delete()
94 | val certSF = File(apkUnzipDir,"META-INF/CERT.SF")
95 | certSF.delete()
96 | val manifestMF = File(apkUnzipDir,"META-INF/MANIFEST.MF")
97 | manifestMF.delete()
98 | //changeAndroidManifest(apkUnzipDir)
99 | //获取dex 文件
100 | val apkFiles = apkUnzipDir.listFiles(object :FilenameFilter{
101 | override fun accept(dir: File?, name: String?): Boolean {
102 | return name?.endsWith(".dex") == true
103 | }
104 | })
105 | for (dexFile in apkFiles){
106 | val name = dexFile.name
107 | println("dex:$name")
108 | val bytes = DexUtils.getBytes(dexFile)
109 | val encrypt: ByteArray? = EncryptUtils.encrypt(bytes, EncryptUtils.ivBytes)
110 | val fos: FileOutputStream = FileOutputStream(
111 | File(
112 | dexFile.parent,
113 | "secret-" + dexFile.getName()
114 | )
115 | )
116 | fos.write(encrypt)
117 | fos.flush()
118 | fos.close()
119 | dexFile.delete()
120 | }
121 |
122 | }
123 |
124 | /**
125 | * 修改 xml 文件
126 | */
127 | fun changeAndroidManifest(){
128 | println("开始修改 AndroidManifest")
129 | var manifestFile = File("output/apktool/decode/AndroidManifest.xml")
130 | XmlParseUtils.changeXmlBySax(manifestFile,"com.sakuqi.steady.SteadyApplication")
131 | }
132 |
133 | /**
134 | * 修改 AndroidManifest
135 | */
136 | fun changeAndroidManifest(apkUnzipDir:File){
137 | val aManifest = apkUnzipDir.listFiles { _, name ->
138 | name?.equals("AndroidManifest.xml") == true
139 | }
140 | val file = if (aManifest != null && aManifest.isNotEmpty()) {
141 | aManifest[0]
142 | }else{null}
143 | file?.let {
144 | //将模版插入 AndroidManifest 中
145 | val process2 = Runtime.getRuntime()
146 | .exec("java -jar outlibs/AXMLEditor.jar -tag -i tool/src/main/assets/ApplicationName.xml " +
147 | file.absolutePath+" "+file.absolutePath)
148 | process2.waitFor()
149 | if(process2.exitValue() != 0) {
150 | println("2")
151 | FileUtils.printStream(process2.errorStream)
152 | }
153 | process2.destroy()
154 |
155 | //解析出原来的 Application 类名
156 | var process0 = Runtime.getRuntime()
157 | .exec("java -jar tool/libs/AXMLPrinter2.jar "+file.absolutePath)
158 | process0.waitFor()
159 | val applicationPath = XmlParseUtils.sax2xml(process0.inputStream)
160 | if(process0.exitValue() != 0){
161 | println("0")
162 | FileUtils.printStream(process0.errorStream)
163 | }
164 | process0.destroy()
165 |
166 | //参考 https://github.com/fourbrother/AXMLEditor
167 | //修改 Application 下 插入标签的值
168 | val process1 = Runtime.getRuntime()
169 | .exec("java -jar tool/libs/AXMLEditor.jar -attr -i meta-data package value "+applicationPath
170 | + " " + file.absolutePath+" "+file.absolutePath)
171 | process1.waitFor()
172 | if(process1.exitValue() != 0){
173 | println("1")
174 | FileUtils.printStream(process1.errorStream)
175 | }
176 | process1.destroy()
177 |
178 | //参考 https://github.com/fourbrother/AXMLEditor
179 | //修改 Application 下 name 标签
180 | val process3 = Runtime.getRuntime()
181 | .exec("java -jar tool/libs/AXMLEditor.jar -attr -m application package name com.sakuqi.shell.NewApplication"
182 | + " " + file.absolutePath+" "+file.absolutePath)
183 | process3.waitFor()
184 | if(process3.exitValue() != 0){
185 | println("3")
186 | FileUtils.printStream(process3.errorStream)
187 | }
188 | process3.destroy()
189 |
190 | //解析出原来的 Application 类名
191 | var process4 = Runtime.getRuntime()
192 | .exec("java -jar tool/libs/AXMLPrinter2.jar "+file.absolutePath)
193 | process4.waitFor()
194 | FileUtils.printStream(process4.inputStream)
195 | process4.destroy()
196 |
197 | }
198 | }
199 |
200 | /**
201 | * 解压壳aar 并转化jar 为dex
202 | */
203 | fun makeDecodeDex(){
204 | println("解压壳 AAR")
205 | var shellUnzipDir = File("output/unzip/shell")
206 | if(!shellUnzipDir.exists()){
207 | shellUnzipDir.mkdirs()
208 | }
209 | FileUtils.delete(shellUnzipDir)
210 | //解压 AAR
211 | ZipUtils.unZip(shellAAR,shellUnzipDir)
212 | //将 jar 转成 dex
213 | println("将 jar 转成 dex")
214 | var shellJar = File(shellUnzipDir,"classes.jar")
215 | var shellDex = File("output/unzip/apk","classes.dex")
216 | DexUtils.dxCommand(shellJar,shellDex)
217 | moveLibSoToApk()
218 | //打包
219 | println("打包 APK")
220 | var unsignedApk = File("output/unsigned_$orginApkName")
221 | ZipUtils.zip(File("output/unzip/apk"),unsignedApk)
222 | }
223 |
224 | /**
225 | * 将壳中的lib文件移到apk 中
226 | */
227 | fun moveLibSoToApk(){
228 | var shellUnzipLibDir = File("output/unzip/shell/jni")
229 | var apkUnzipLibDir = File("output/unzip/apk/lib")
230 | if(!apkUnzipLibDir.exists()){
231 | apkUnzipLibDir.mkdirs()
232 | }
233 |
234 | FileUtils.copy(shellUnzipLibDir,apkUnzipLibDir)
235 | }
236 |
237 | /**
238 | * 对齐
239 | */
240 | fun zipalign(){
241 | println("将打包的 apk 对齐")
242 | var unsignedApk = File("output/unsigned_$orginApkName")
243 | val alignedApk = File("output/unsigned-aligned_$orginApkName")
244 | val process = Runtime.getRuntime().exec(
245 | "zipalign -p -f -v 4 " + unsignedApk.absolutePath + " " + alignedApk.absolutePath)
246 | process.waitFor(5,TimeUnit.SECONDS)
247 | try {
248 | if (process.exitValue() != 0) {
249 | println("zipalign 出错")
250 | FileUtils.printStream(process.errorStream)
251 | } else {
252 | FileUtils.printStream(process.inputStream)
253 | }
254 | println("完成 apk 的对齐")
255 | process.destroy()
256 | }catch (e:Exception){
257 | println("对齐超时...")
258 | }
259 | }
260 |
261 | /**
262 | * 对 APK 签名
263 | */
264 | fun jksToApk(){
265 | println("签名 APK")
266 | var signedApk = File("output/signed_$orginApkName")
267 | val alignedApk = File("output/unsigned-aligned_$orginApkName")
268 | SignUtils.signature(alignedApk,signedApk,signFile.absolutePath)
269 | }
270 |
271 |
272 | fun main() {
273 | //清空输出目录
274 | var output = File("output/")
275 | if (output.exists()) {
276 | FileUtils.delete(output)
277 | }
278 | println("清空工作空间")
279 | findOriginApk()
280 | findOriginShell()
281 | apkDecode()
282 | changeAndroidManifest()
283 | apkBuild()
284 | //解压需要加固的 apk
285 | unZipApkAndEncrypt()
286 | //解压壳aar
287 | makeDecodeDex()
288 | //对齐打包apk
289 | zipalign()
290 | //删除解压目录
291 | //FileUtils.delete("output/unzip")
292 | jksToApk()
293 | println("Finished!!!")
294 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/DexUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import java.io.ByteArrayOutputStream
4 | import java.io.File
5 | import java.io.IOException
6 | import java.io.RandomAccessFile
7 | import java.nio.charset.Charset
8 |
9 | object DexUtils {
10 | @Throws(IOException::class,InterruptedException::class)
11 | fun dxCommand(jar:File,dex:File){
12 | var runtime = Runtime.getRuntime()
13 | var process = runtime.exec("dx --dex --output "+dex.absolutePath+" "+jar.absolutePath)
14 | try {
15 | process.waitFor()
16 | }catch (e:InterruptedException){
17 | e.printStackTrace()
18 | throw e
19 | }
20 | if(process.exitValue() != 0){
21 | val inputStream = process.errorStream
22 | var buffer = ByteArray(1024)
23 | val bos = ByteArrayOutputStream()
24 | var len = inputStream.read(buffer)
25 | while (len != -1){
26 | bos.write(buffer,0,len)
27 | len = inputStream.read(buffer)
28 | }
29 | System.out.println(String(bos.toByteArray(), Charset.forName("GBK")))
30 | throw RuntimeException("dx run failed")
31 | }else{
32 | System.out.println("执行成功:"+process.exitValue())
33 | }
34 | process.destroy()
35 | }
36 |
37 | /**
38 | * 读取文件
39 | * @param file
40 | * @return
41 | * @throws Exception
42 | */
43 | @Throws(Exception::class)
44 | fun getBytes(file: File?): ByteArray {
45 | val r = RandomAccessFile(file, "r")
46 | val buffer = ByteArray(r.length().toInt())
47 | r.readFully(buffer)
48 | r.close()
49 | return buffer
50 | }
51 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/EncryptUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import java.io.*
4 | import java.security.InvalidKeyException
5 | import java.security.NoSuchAlgorithmException
6 | import javax.crypto.BadPaddingException
7 | import javax.crypto.Cipher
8 | import javax.crypto.IllegalBlockSizeException
9 | import javax.crypto.NoSuchPaddingException
10 | import javax.crypto.spec.SecretKeySpec
11 |
12 |
13 | object EncryptUtils {
14 | /**
15 | * 加密算法
16 | */
17 | private val ENCRY_ALGORITHM = "AES"
18 |
19 |
20 | /**
21 | * 加密算法/加密模式/填充类型
22 | * 本例采用AES加密,ECB加密模式,PKCS5Padding填充
23 | */
24 | private val CIPHER_MODE = "AES/ECB/PKCS5Padding"
25 |
26 | val ivBytes = "huangdh'l,.AMWK;".toByteArray()
27 |
28 | /**
29 | * 设置iv偏移量
30 | * 本例采用ECB加密模式,不需要设置iv偏移量
31 | */
32 | private val IV_: String? = null
33 |
34 | /**
35 | * 设置加密字符集
36 | * 本例采用 UTF-8 字符集
37 | */
38 | private val CHARACTER = Charsets.UTF_8
39 |
40 | /**
41 | * 设置加密密码处理长度。
42 | * 不足此长度补0;
43 | */
44 | private val PWD_SIZE = 16
45 |
46 | /**
47 | * 密码处理方法
48 | * 如果加解密出问题,
49 | * 请先查看本方法,排除密码长度不足补"0",导致密码不一致
50 | * @param password 待处理的密码
51 | * @return
52 | * @throws UnsupportedEncodingException
53 | */
54 | @Throws(UnsupportedEncodingException::class)
55 | private fun pwdHandler(password: String?): ByteArray {
56 | var password = password
57 | var data: ByteArray? = null
58 | if (password == null) {
59 | password = ""
60 | }
61 | val sb = StringBuffer(PWD_SIZE)
62 | sb.append(password)
63 | while (sb.length < PWD_SIZE) {
64 | sb.append("0")
65 | }
66 | if (sb.length > PWD_SIZE) {
67 | sb.setLength(PWD_SIZE)
68 | }
69 |
70 | data = sb.toString().toByteArray(charset("UTF-8"))
71 |
72 | return data
73 | }
74 |
75 | //======================>原始加密<======================
76 |
77 | /**
78 | * 原始加密
79 | * @param clearTextBytes 明文字节数组,待加密的字节数组
80 | * @param pwdBytes 加密密码字节数组
81 | * @return 返回加密后的密文字节数组,加密错误返回null
82 | */
83 | fun encrypt(clearTextBytes: ByteArray, pwdBytes: ByteArray): ByteArray? {
84 | try {
85 | // 1 获取加密密钥
86 | val keySpec = SecretKeySpec(pwdBytes, ENCRY_ALGORITHM)
87 |
88 | // 2 获取Cipher实例
89 | val cipher = Cipher.getInstance(CIPHER_MODE)
90 |
91 | // 查看数据块位数 默认为16(byte) * 8 =128 bit
92 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize());
93 |
94 | // 3 初始化Cipher实例。设置执行模式以及加密密钥
95 | cipher.init(Cipher.ENCRYPT_MODE, keySpec)
96 |
97 | // 4 执行
98 |
99 | // 5 返回密文字符集
100 | return cipher.doFinal(clearTextBytes)
101 |
102 | } catch (e: NoSuchPaddingException) {
103 | e.printStackTrace()
104 | } catch (e: NoSuchAlgorithmException) {
105 | e.printStackTrace()
106 | } catch (e: BadPaddingException) {
107 | e.printStackTrace()
108 | } catch (e: IllegalBlockSizeException) {
109 | e.printStackTrace()
110 | } catch (e: InvalidKeyException) {
111 | e.printStackTrace()
112 | } catch (e: Exception) {
113 | e.printStackTrace()
114 | }
115 |
116 | return null
117 | }
118 |
119 | /**
120 | * 原始解密
121 | * @param cipherTextBytes 密文字节数组,待解密的字节数组
122 | * @param pwdBytes 解密密码字节数组
123 | * @return 返回解密后的明文字节数组,解密错误返回null
124 | */
125 | fun decrypt(cipherTextBytes: ByteArray, pwdBytes: ByteArray): ByteArray? {
126 |
127 | try {
128 | // 1 获取解密密钥
129 | val keySpec = SecretKeySpec(pwdBytes, ENCRY_ALGORITHM)
130 |
131 | // 2 获取Cipher实例
132 | val cipher = Cipher.getInstance(CIPHER_MODE)
133 |
134 | // 查看数据块位数 默认为16(byte) * 8 =128 bit
135 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize());
136 |
137 | // 3 初始化Cipher实例。设置执行模式以及加密密钥
138 | cipher.init(Cipher.DECRYPT_MODE, keySpec)
139 |
140 | // 4 执行
141 |
142 | // 5 返回明文字符集
143 | return cipher.doFinal(cipherTextBytes)
144 |
145 | } catch (e: NoSuchAlgorithmException) {
146 | e.printStackTrace()
147 | } catch (e: InvalidKeyException) {
148 | e.printStackTrace()
149 | } catch (e: NoSuchPaddingException) {
150 | e.printStackTrace()
151 | } catch (e: BadPaddingException) {
152 | e.printStackTrace()
153 | } catch (e: IllegalBlockSizeException) {
154 | e.printStackTrace()
155 | } catch (e: Exception) {
156 | e.printStackTrace()
157 | }
158 |
159 | // 解密错误 返回null
160 | return null
161 | }
162 |
163 |
164 | //======================>HEX<======================
165 |
166 | /**
167 | * HEX加密
168 | * @param clearText 明文,待加密的内容
169 | * @param password 密码,加密的密码
170 | * @return 返回密文,加密后得到的内容。加密错误返回null
171 | */
172 | fun encryptHex(clearText: String, password: String): String? {
173 | try {
174 | // 1 获取加密密文字节数组
175 | val cipherTextBytes = encrypt(clearText.toByteArray(CHARACTER), pwdHandler(password))
176 |
177 | // 2 对密文字节数组进行 转换为 HEX输出密文
178 |
179 | // 3 返回 HEX输出密文
180 | return byte2hex(cipherTextBytes!!)
181 | } catch (e: UnsupportedEncodingException) {
182 | e.printStackTrace()
183 | } catch (e: Exception) {
184 | e.printStackTrace()
185 | }
186 |
187 | // 加密错误返回null
188 | return null
189 | }
190 |
191 | /**
192 | * HEX解密
193 | * @param cipherText 密文,带解密的内容
194 | * @param password 密码,解密的密码
195 | * @return 返回明文,解密后得到的内容。解密错误返回null
196 | */
197 | fun decryptHex(cipherText: String?, password: String): String? {
198 | try {
199 | // 1 将HEX输出密文 转为密文字节数组
200 | val cipherTextBytes = hex2byte(cipherText)
201 |
202 | // 2 将密文字节数组进行解密 得到明文字节数组
203 | val clearTextBytes = decrypt(cipherTextBytes, pwdHandler(password))
204 |
205 | // 3 根据 CHARACTER 转码,返回明文字符串
206 | return String(clearTextBytes!!, CHARACTER)
207 | } catch (e: UnsupportedEncodingException) {
208 | e.printStackTrace()
209 | } catch (e: Exception) {
210 | e.printStackTrace()
211 | }
212 |
213 | // 解密错误返回null
214 | return null
215 | }
216 |
217 | /*字节数组转成16进制字符串 */
218 | fun byte2hex(bytes: ByteArray): String { // 一个字节的数,
219 | val sb = StringBuffer(bytes.size * 2)
220 | var tmp = ""
221 | for (n in bytes.indices) {
222 | // 整数转成十六进制表示
223 | tmp = Integer.toHexString(bytes[n].toInt() and 0XFF)
224 | if (tmp.length == 1) {
225 | sb.append("0")
226 | }
227 | sb.append(tmp)
228 | }
229 | return sb.toString().toUpperCase() // 转成大写
230 | }
231 |
232 | /*将hex字符串转换成字节数组 */
233 | private fun hex2byte(str: String?): ByteArray {
234 | var str = str
235 | if (str == null || str.length < 2) {
236 | return ByteArray(0)
237 | }
238 | str = str.toLowerCase()
239 | val l = str.length / 2
240 | val result = ByteArray(l)
241 | for (i in 0 until l) {
242 | val tmp = str.substring(2 * i, 2 * i + 2)
243 | result[i] = (Integer.parseInt(tmp, 16) and 0xFF).toByte()
244 | }
245 | return result
246 | }
247 |
248 | @JvmStatic
249 | fun main(args: Array) {
250 | val data = encrypt("123456".toByteArray(), EncryptUtils.ivBytes)
251 | data?.forEach {
252 | print("$it ")
253 | }
254 |
255 | }
256 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.close
4 | import java.io.*
5 | import java.net.URL
6 | import java.nio.charset.Charset
7 | import java.security.NoSuchAlgorithmException
8 | import java.security.DigestInputStream
9 | import java.security.MessageDigest
10 | import java.util.*
11 | import java.util.Collections.addAll
12 | import javax.net.ssl.HttpsURLConnection
13 | import kotlin.reflect.jvm.internal.impl.descriptors.ModuleDescriptor.DefaultImpls.accept
14 | import kotlin.experimental.and
15 |
16 |
17 | object FileUtils{
18 | private val LINE_SEP = System.getProperty("line.separator")
19 |
20 |
21 | /**
22 | * Return the file by path.
23 | *
24 | * @param filePath The path of file.
25 | * @return the file
26 | */
27 | fun getFileByPath(filePath: String): File? {
28 | return if (isSpace(filePath)) null else File(filePath)
29 | }
30 |
31 |
32 | /**
33 | * Return whether the file exists.
34 | *
35 | * @param file The file.
36 | * @return `true`: yes
`false`: no
37 | */
38 | fun isFileExists(file: File?): Boolean {
39 | return file != null && file.exists()
40 | }
41 |
42 | /**
43 | * Rename the file.
44 | *
45 | * @param filePath The path of file.
46 | * @param newName The new name of file.
47 | * @return `true`: success
`false`: fail
48 | */
49 | fun rename(filePath: String, newName: String): Boolean {
50 | return rename(getFileByPath(filePath), newName)
51 | }
52 |
53 | /**
54 | * Rename the file.
55 | *
56 | * @param file The file.
57 | * @param newName The new name of file.
58 | * @return `true`: success
`false`: fail
59 | */
60 | fun rename(file: File?, newName: String): Boolean {
61 | // file is null then return false
62 | if (file == null) return false
63 | // file doesn't exist then return false
64 | if (!file.exists()) return false
65 | // the new name is space then return false
66 | if (isSpace(newName)) return false
67 | // the new name equals old name then return true
68 | if (newName == file.name) return true
69 | val newFile = File(file.parent + File.separator + newName)
70 | // the new name of file exists then return false
71 | return !newFile.exists() && file.renameTo(newFile)
72 | }
73 |
74 | /**
75 | * Return whether it is a directory.
76 | *
77 | * @param dirPath The path of directory.
78 | * @return `true`: yes
`false`: no
79 | */
80 | fun isDir(dirPath: String): Boolean {
81 | return isDir(getFileByPath(dirPath))
82 | }
83 |
84 | /**
85 | * Return whether it is a directory.
86 | *
87 | * @param file The file.
88 | * @return `true`: yes
`false`: no
89 | */
90 | fun isDir(file: File?): Boolean {
91 | return file != null && file.exists() && file.isDirectory
92 | }
93 |
94 | /**
95 | * Return whether it is a file.
96 | *
97 | * @param filePath The path of file.
98 | * @return `true`: yes
`false`: no
99 | */
100 | fun isFile(filePath: String): Boolean {
101 | return isFile(getFileByPath(filePath))
102 | }
103 |
104 | /**
105 | * Return whether it is a file.
106 | *
107 | * @param file The file.
108 | * @return `true`: yes
`false`: no
109 | */
110 | fun isFile(file: File?): Boolean {
111 | return file != null && file.exists() && file.isFile
112 | }
113 |
114 | /**
115 | * Create a directory if it doesn't exist, otherwise do nothing.
116 | *
117 | * @param dirPath The path of directory.
118 | * @return `true`: exists or creates successfully
`false`: otherwise
119 | */
120 | fun createOrExistsDir(dirPath: String): Boolean {
121 | return createOrExistsDir(getFileByPath(dirPath))
122 | }
123 |
124 | /**
125 | * Create a directory if it doesn't exist, otherwise do nothing.
126 | *
127 | * @param file The file.
128 | * @return `true`: exists or creates successfully
`false`: otherwise
129 | */
130 | fun createOrExistsDir(file: File?): Boolean {
131 | return file != null && if (file.exists()) file.isDirectory else file.mkdirs()
132 | }
133 |
134 | /**
135 | * Create a file if it doesn't exist, otherwise do nothing.
136 | *
137 | * @param filePath The path of file.
138 | * @return `true`: exists or creates successfully
`false`: otherwise
139 | */
140 | fun createOrExistsFile(filePath: String): Boolean {
141 | return createOrExistsFile(getFileByPath(filePath))
142 | }
143 |
144 | /**
145 | * Create a file if it doesn't exist, otherwise do nothing.
146 | *
147 | * @param file The file.
148 | * @return `true`: exists or creates successfully
`false`: otherwise
149 | */
150 | fun createOrExistsFile(file: File?): Boolean {
151 | if (file == null) return false
152 | if (file.exists()) return file.isFile
153 | if (!createOrExistsDir(file.parentFile)) return false
154 | try {
155 | return file.createNewFile()
156 | } catch (e: IOException) {
157 | e.printStackTrace()
158 | return false
159 | }
160 |
161 | }
162 |
163 | /**
164 | * Create a file if it doesn't exist, otherwise delete old file before creating.
165 | *
166 | * @param filePath The path of file.
167 | * @return `true`: success
`false`: fail
168 | */
169 | fun createFileByDeleteOldFile(filePath: String): Boolean {
170 | return createFileByDeleteOldFile(getFileByPath(filePath))
171 | }
172 |
173 | /**
174 | * Create a file if it doesn't exist, otherwise delete old file before creating.
175 | *
176 | * @param file The file.
177 | * @return `true`: success
`false`: fail
178 | */
179 | fun createFileByDeleteOldFile(file: File?): Boolean {
180 | if (file == null) return false
181 | // file exists and unsuccessfully delete then return false
182 | if (file.exists() && !file.delete()) return false
183 | if (!createOrExistsDir(file.parentFile)) return false
184 | try {
185 | return file.createNewFile()
186 | } catch (e: IOException) {
187 | e.printStackTrace()
188 | return false
189 | }
190 |
191 | }
192 |
193 | /**
194 | * Copy the directory or file.
195 | *
196 | * @param srcPath The path of source.
197 | * @param destPath The path of destination.
198 | * @return `true`: success
`false`: fail
199 | */
200 | fun copy(
201 | srcPath: String,
202 | destPath: String
203 | ): Boolean {
204 | return copy(getFileByPath(srcPath), getFileByPath(destPath), null)
205 | }
206 |
207 | /**
208 | * Copy the directory or file.
209 | *
210 | * @param srcPath The path of source.
211 | * @param destPath The path of destination.
212 | * @param listener The replace listener.
213 | * @return `true`: success
`false`: fail
214 | */
215 | fun copy(
216 | srcPath: String,
217 | destPath: String,
218 | listener: OnReplaceListener
219 | ): Boolean {
220 | return copy(getFileByPath(srcPath), getFileByPath(destPath), listener)
221 | }
222 |
223 | /**
224 | * Copy the directory or file.
225 | *
226 | * @param src The source.
227 | * @param dest The destination.
228 | * @return `true`: success
`false`: fail
229 | */
230 | fun copy(
231 | src: File,
232 | dest: File
233 | ): Boolean {
234 | return copy(src, dest, null)
235 | }
236 |
237 | /**
238 | * Copy the directory or file.
239 | *
240 | * @param src The source.
241 | * @param dest The destination.
242 | * @param listener The replace listener.
243 | * @return `true`: success
`false`: fail
244 | */
245 | fun copy(
246 | src: File?,
247 | dest: File?,
248 | listener: OnReplaceListener?
249 | ): Boolean {
250 | if (src == null) return false
251 | return if (src.isDirectory) {
252 | copyDir(src, dest, listener)
253 | } else copyFile(src, dest, listener)
254 | }
255 |
256 | /**
257 | * Copy the directory.
258 | *
259 | * @param srcDir The source directory.
260 | * @param destDir The destination directory.
261 | * @param listener The replace listener.
262 | * @return `true`: success
`false`: fail
263 | */
264 | private fun copyDir(
265 | srcDir: File,
266 | destDir: File?,
267 | listener: OnReplaceListener?
268 | ): Boolean {
269 | return copyOrMoveDir(srcDir, destDir, listener, false)
270 | }
271 |
272 | /**
273 | * Copy the file.
274 | *
275 | * @param srcFile The source file.
276 | * @param destFile The destination file.
277 | * @param listener The replace listener.
278 | * @return `true`: success
`false`: fail
279 | */
280 | private fun copyFile(
281 | srcFile: File,
282 | destFile: File?,
283 | listener: OnReplaceListener?
284 | ): Boolean {
285 | return copyOrMoveFile(srcFile, destFile, listener, false)
286 | }
287 |
288 | /**
289 | * Move the directory or file.
290 | *
291 | * @param srcPath The path of source.
292 | * @param destPath The path of destination.
293 | * @return `true`: success
`false`: fail
294 | */
295 | fun move(
296 | srcPath: String,
297 | destPath: String
298 | ): Boolean {
299 | return move(getFileByPath(srcPath), getFileByPath(destPath), null)
300 | }
301 |
302 | /**
303 | * Move the directory or file.
304 | *
305 | * @param srcPath The path of source.
306 | * @param destPath The path of destination.
307 | * @param listener The replace listener.
308 | * @return `true`: success
`false`: fail
309 | */
310 | fun move(
311 | srcPath: String,
312 | destPath: String,
313 | listener: OnReplaceListener
314 | ): Boolean {
315 | return move(getFileByPath(srcPath), getFileByPath(destPath), listener)
316 | }
317 |
318 | /**
319 | * Move the directory or file.
320 | *
321 | * @param src The source.
322 | * @param dest The destination.
323 | * @return `true`: success
`false`: fail
324 | */
325 | fun move(
326 | src: File,
327 | dest: File
328 | ): Boolean {
329 | return move(src, dest, null)
330 | }
331 |
332 | /**
333 | * Move the directory or file.
334 | *
335 | * @param src The source.
336 | * @param dest The destination.
337 | * @param listener The replace listener.
338 | * @return `true`: success
`false`: fail
339 | */
340 | fun move(
341 | src: File?,
342 | dest: File?,
343 | listener: OnReplaceListener?
344 | ): Boolean {
345 | if (src == null) return false
346 | return if (src.isDirectory) {
347 | moveDir(src, dest, listener)
348 | } else moveFile(src, dest, listener)
349 | }
350 |
351 | /**
352 | * Move the directory.
353 | *
354 | * @param srcDir The source directory.
355 | * @param destDir The destination directory.
356 | * @param listener The replace listener.
357 | * @return `true`: success
`false`: fail
358 | */
359 | fun moveDir(
360 | srcDir: File,
361 | destDir: File?,
362 | listener: OnReplaceListener?
363 | ): Boolean {
364 | return copyOrMoveDir(srcDir, destDir, listener, true)
365 | }
366 |
367 | /**
368 | * Move the file.
369 | *
370 | * @param srcFile The source file.
371 | * @param destFile The destination file.
372 | * @param listener The replace listener.
373 | * @return `true`: success
`false`: fail
374 | */
375 | fun moveFile(
376 | srcFile: File,
377 | destFile: File?,
378 | listener: OnReplaceListener?
379 | ): Boolean {
380 | return copyOrMoveFile(srcFile, destFile, listener, true)
381 | }
382 |
383 | private fun copyOrMoveDir(
384 | srcDir: File?,
385 | destDir: File?,
386 | listener: OnReplaceListener?,
387 | isMove: Boolean
388 | ): Boolean {
389 | if (srcDir == null || destDir == null) return false
390 | // destDir's path locate in srcDir's path then return false
391 | val srcPath = srcDir.path + File.separator
392 | val destPath = destDir.path + File.separator
393 | if (destPath.contains(srcPath)) return false
394 | if (!srcDir.exists() || !srcDir.isDirectory) return false
395 | if (!createOrExistsDir(destDir)) return false
396 | val files = srcDir.listFiles()
397 | for (file in files!!) {
398 | val oneDestFile = File(destPath + file.name)
399 | if (file.isFile) {
400 | if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false
401 | } else if (file.isDirectory) {
402 | if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false
403 | }
404 | }
405 | return !isMove || deleteDir(srcDir)
406 | }
407 |
408 | private fun copyOrMoveFile(
409 | srcFile: File?,
410 | destFile: File?,
411 | listener: OnReplaceListener?,
412 | isMove: Boolean
413 | ): Boolean {
414 | if (srcFile == null || destFile == null) return false
415 | // srcFile equals destFile then return false
416 | if (srcFile == destFile) return false
417 | // srcFile doesn't exist or isn't a file then return false
418 | if (!srcFile.exists() || !srcFile.isFile) return false
419 | if (destFile.exists()) {
420 | if (listener == null || listener.onReplace(srcFile, destFile)) {// require delete the old file
421 | if (!destFile.delete()) {// unsuccessfully delete then return false
422 | return false
423 | }
424 | } else {
425 | return true
426 | }
427 | }
428 | if (!createOrExistsDir(destFile.parentFile)) return false
429 | try {
430 | return writeFileFromIS(destFile, FileInputStream(srcFile)) && !(isMove && !deleteFile(srcFile))
431 | } catch (e: FileNotFoundException) {
432 | e.printStackTrace()
433 | return false
434 | }
435 |
436 | }
437 |
438 | /**
439 | * Delete the directory.
440 | *
441 | * @param filePath The path of file.
442 | * @return `true`: success
`false`: fail
443 | */
444 | fun delete(filePath: String): Boolean {
445 | return delete(getFileByPath(filePath))
446 | }
447 |
448 | /**
449 | * Delete the directory.
450 | *
451 | * @param file The file.
452 | * @return `true`: success
`false`: fail
453 | */
454 | fun delete(file: File?): Boolean {
455 | if (file == null) return false
456 | return if (file.isDirectory) {
457 | deleteDir(file)
458 | } else deleteFile(file)
459 | }
460 |
461 | /**
462 | * Delete the directory.
463 | *
464 | * @param dir The directory.
465 | * @return `true`: success
`false`: fail
466 | */
467 | private fun deleteDir(dir: File?): Boolean {
468 | if (dir == null) return false
469 | // dir doesn't exist then return true
470 | if (!dir.exists()) return true
471 | // dir isn't a directory then return false
472 | if (!dir.isDirectory) return false
473 | val files = dir.listFiles()
474 | if (files != null && files.size != 0) {
475 | for (file in files) {
476 | if (file.isFile) {
477 | if (!file.delete()) return false
478 | } else if (file.isDirectory) {
479 | if (!deleteDir(file)) return false
480 | }
481 | }
482 | }
483 | return dir.delete()
484 | }
485 |
486 | /**
487 | * Delete the file.
488 | *
489 | * @param file The file.
490 | * @return `true`: success
`false`: fail
491 | */
492 | private fun deleteFile(file: File?): Boolean {
493 | return file != null && (!file.exists() || file.isFile && file.delete())
494 | }
495 |
496 | /**
497 | * Delete the all in directory.
498 | *
499 | * @param dirPath The path of directory.
500 | * @return `true`: success
`false`: fail
501 | */
502 | fun deleteAllInDir(dirPath: String): Boolean {
503 | return deleteAllInDir(getFileByPath(dirPath))
504 | }
505 |
506 | /**
507 | * Delete the all in directory.
508 | *
509 | * @param dir The directory.
510 | * @return `true`: success
`false`: fail
511 | */
512 | fun deleteAllInDir(dir: File?): Boolean {
513 | return deleteFilesInDirWithFilter(dir, object : FileFilter {
514 | override fun accept(pathname: File): Boolean {
515 | return true
516 | }
517 | })
518 | }
519 |
520 | /**
521 | * Delete all files in directory.
522 | *
523 | * @param dirPath The path of directory.
524 | * @return `true`: success
`false`: fail
525 | */
526 | fun deleteFilesInDir(dirPath: String): Boolean {
527 | return deleteFilesInDir(getFileByPath(dirPath))
528 | }
529 |
530 | /**
531 | * Delete all files in directory.
532 | *
533 | * @param dir The directory.
534 | * @return `true`: success
`false`: fail
535 | */
536 | fun deleteFilesInDir(dir: File?): Boolean {
537 | return deleteFilesInDirWithFilter(dir, object : FileFilter {
538 | override fun accept(pathname: File): Boolean {
539 | return pathname.isFile
540 | }
541 | })
542 | }
543 |
544 | /**
545 | * Delete all files that satisfy the filter in directory.
546 | *
547 | * @param dirPath The path of directory.
548 | * @param filter The filter.
549 | * @return `true`: success
`false`: fail
550 | */
551 | fun deleteFilesInDirWithFilter(
552 | dirPath: String,
553 | filter: FileFilter
554 | ): Boolean {
555 | return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter)
556 | }
557 |
558 | /**
559 | * Delete all files that satisfy the filter in directory.
560 | *
561 | * @param dir The directory.
562 | * @param filter The filter.
563 | * @return `true`: success
`false`: fail
564 | */
565 | fun deleteFilesInDirWithFilter(dir: File?, filter: FileFilter?): Boolean {
566 | if (dir == null || filter == null) return false
567 | // dir doesn't exist then return true
568 | if (!dir.exists()) return true
569 | // dir isn't a directory then return false
570 | if (!dir.isDirectory) return false
571 | val files = dir.listFiles()
572 | if (files != null && files.size != 0) {
573 | for (file in files) {
574 | if (filter!!.accept(file)) {
575 | if (file.isFile) {
576 | if (!file.delete()) return false
577 | } else if (file.isDirectory) {
578 | if (!deleteDir(file)) return false
579 | }
580 | }
581 | }
582 | }
583 | return true
584 | }
585 |
586 | /**
587 | * Return the files in directory.
588 | *
589 | * Doesn't traverse subdirectories
590 | *
591 | * @param dirPath The path of directory.
592 | * @return the files in directory
593 | */
594 | fun listFilesInDir(dirPath: String): List {
595 | return listFilesInDir(dirPath, null)
596 | }
597 |
598 | /**
599 | * Return the files in directory.
600 | *
601 | * Doesn't traverse subdirectories
602 | *
603 | * @param dir The directory.
604 | * @return the files in directory
605 | */
606 | fun listFilesInDir(dir: File): List {
607 | return listFilesInDir(dir, null)
608 | }
609 |
610 | /**
611 | * Return the files in directory.
612 | *
613 | * Doesn't traverse subdirectories
614 | *
615 | * @param dirPath The path of directory.
616 | * @param comparator The comparator to determine the order of the list.
617 | * @return the files in directory
618 | */
619 | fun listFilesInDir(dirPath: String, comparator: Comparator?): List {
620 | return listFilesInDir(getFileByPath(dirPath), false)
621 | }
622 |
623 | /**
624 | * Return the files in directory.
625 | *
626 | * Doesn't traverse subdirectories
627 | *
628 | * @param dir The directory.
629 | * @param comparator The comparator to determine the order of the list.
630 | * @return the files in directory
631 | */
632 | fun listFilesInDir(dir: File, comparator: Comparator?): List {
633 | return listFilesInDir(dir, false, comparator)
634 | }
635 |
636 | /**
637 | * Return the files in directory.
638 | *
639 | * @param dirPath The path of directory.
640 | * @param isRecursive True to traverse subdirectories, false otherwise.
641 | * @return the files in directory
642 | */
643 | fun listFilesInDir(dirPath: String, isRecursive: Boolean): List {
644 | return listFilesInDir(getFileByPath(dirPath), isRecursive)
645 | }
646 |
647 | /**
648 | * Return the files in directory.
649 | *
650 | * @param dir The directory.
651 | * @param isRecursive True to traverse subdirectories, false otherwise.
652 | * @return the files in directory
653 | */
654 | fun listFilesInDir(dir: File?, isRecursive: Boolean): List {
655 | return listFilesInDir(dir, isRecursive, null)
656 | }
657 |
658 | /**
659 | * Return the files in directory.
660 | *
661 | * @param dirPath The path of directory.
662 | * @param isRecursive True to traverse subdirectories, false otherwise.
663 | * @param comparator The comparator to determine the order of the list.
664 | * @return the files in directory
665 | */
666 | fun listFilesInDir(
667 | dirPath: String,
668 | isRecursive: Boolean,
669 | comparator: Comparator
670 | ): List {
671 | return listFilesInDir(getFileByPath(dirPath), isRecursive, comparator)
672 | }
673 |
674 | /**
675 | * Return the files in directory.
676 | *
677 | * @param dir The directory.
678 | * @param isRecursive True to traverse subdirectories, false otherwise.
679 | * @param comparator The comparator to determine the order of the list.
680 | * @return the files in directory
681 | */
682 | fun listFilesInDir(
683 | dir: File?,
684 | isRecursive: Boolean,
685 | comparator: Comparator?
686 | ): List {
687 | return listFilesInDirWithFilter(dir, object : FileFilter {
688 | override fun accept(pathname: File): Boolean {
689 | return true
690 | }
691 | }, isRecursive, comparator)
692 | }
693 |
694 | /**
695 | * Return the files that satisfy the filter in directory.
696 | *
697 | * Doesn't traverse subdirectories
698 | *
699 | * @param dirPath The path of directory.
700 | * @param filter The filter.
701 | * @return the files that satisfy the filter in directory
702 | */
703 | fun listFilesInDirWithFilter(
704 | dirPath: String,
705 | filter: FileFilter
706 | ): List {
707 | return listFilesInDirWithFilter(getFileByPath(dirPath), filter)
708 | }
709 |
710 | /**
711 | * Return the files that satisfy the filter in directory.
712 | *
713 | * Doesn't traverse subdirectories
714 | *
715 | * @param dir The directory.
716 | * @param filter The filter.
717 | * @return the files that satisfy the filter in directory
718 | */
719 | fun listFilesInDirWithFilter(
720 | dir: File?,
721 | filter: FileFilter
722 | ): List {
723 | return listFilesInDirWithFilter(dir, filter, false, null)
724 | }
725 |
726 | /**
727 | * Return the files that satisfy the filter in directory.
728 | *
729 | * Doesn't traverse subdirectories
730 | *
731 | * @param dirPath The path of directory.
732 | * @param filter The filter.
733 | * @param comparator The comparator to determine the order of the list.
734 | * @return the files that satisfy the filter in directory
735 | */
736 | fun listFilesInDirWithFilter(
737 | dirPath: String,
738 | filter: FileFilter,
739 | comparator: Comparator
740 | ): List {
741 | return listFilesInDirWithFilter(getFileByPath(dirPath), filter, comparator)
742 | }
743 |
744 | /**
745 | * Return the files that satisfy the filter in directory.
746 | *
747 | * Doesn't traverse subdirectories
748 | *
749 | * @param dir The directory.
750 | * @param filter The filter.
751 | * @param comparator The comparator to determine the order of the list.
752 | * @return the files that satisfy the filter in directory
753 | */
754 | fun listFilesInDirWithFilter(
755 | dir: File?,
756 | filter: FileFilter,
757 | comparator: Comparator
758 | ): List {
759 | return listFilesInDirWithFilter(dir, filter, false, comparator)
760 | }
761 |
762 | /**
763 | * Return the files that satisfy the filter in directory.
764 | *
765 | * @param dirPath The path of directory.
766 | * @param filter The filter.
767 | * @param isRecursive True to traverse subdirectories, false otherwise.
768 | * @return the files that satisfy the filter in directory
769 | */
770 | fun listFilesInDirWithFilter(
771 | dirPath: String,
772 | filter: FileFilter,
773 | isRecursive: Boolean
774 | ): List {
775 | return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive)
776 | }
777 |
778 | /**
779 | * Return the files that satisfy the filter in directory.
780 | *
781 | * @param dir The directory.
782 | * @param filter The filter.
783 | * @param isRecursive True to traverse subdirectories, false otherwise.
784 | * @return the files that satisfy the filter in directory
785 | */
786 | fun listFilesInDirWithFilter(
787 | dir: File?,
788 | filter: FileFilter,
789 | isRecursive: Boolean
790 | ): List {
791 | return listFilesInDirWithFilter(dir, filter, isRecursive, null)
792 | }
793 |
794 |
795 | /**
796 | * Return the files that satisfy the filter in directory.
797 | *
798 | * @param dirPath The path of directory.
799 | * @param filter The filter.
800 | * @param isRecursive True to traverse subdirectories, false otherwise.
801 | * @param comparator The comparator to determine the order of the list.
802 | * @return the files that satisfy the filter in directory
803 | */
804 | fun listFilesInDirWithFilter(
805 | dirPath: String,
806 | filter: FileFilter,
807 | isRecursive: Boolean,
808 | comparator: Comparator
809 | ): List {
810 | return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive, comparator)
811 | }
812 |
813 | /**
814 | * Return the files that satisfy the filter in directory.
815 | *
816 | * @param dir The directory.
817 | * @param filter The filter.
818 | * @param isRecursive True to traverse subdirectories, false otherwise.
819 | * @param comparator The comparator to determine the order of the list.
820 | * @return the files that satisfy the filter in directory
821 | */
822 | fun listFilesInDirWithFilter(
823 | dir: File?,
824 | filter: FileFilter,
825 | isRecursive: Boolean,
826 | comparator: Comparator?
827 | ): List {
828 | val files = listFilesInDirWithFilterInner(dir, filter, isRecursive)
829 | if (comparator != null) {
830 | Collections.sort(files, comparator)
831 | }
832 | return files
833 | }
834 |
835 | private fun listFilesInDirWithFilterInner(
836 | dir: File?,
837 | filter: FileFilter,
838 | isRecursive: Boolean
839 | ): List {
840 | val list = ArrayList()
841 | if (!isDir(dir)) return list
842 | val files = dir!!.listFiles()
843 | if (files != null && files.size != 0) {
844 | for (file in files) {
845 | if (filter.accept(file)) {
846 | list.add(file)
847 | }
848 | if (isRecursive && file.isDirectory) {
849 | list.addAll(listFilesInDirWithFilterInner(file, filter, true))
850 | }
851 | }
852 | }
853 | return list
854 | }
855 |
856 | /**
857 | * Return the time that the file was last modified.
858 | *
859 | * @param filePath The path of file.
860 | * @return the time that the file was last modified
861 | */
862 |
863 | fun getFileLastModified(filePath: String): Long {
864 | return getFileLastModified(getFileByPath(filePath))
865 | }
866 |
867 | /**
868 | * Return the time that the file was last modified.
869 | *
870 | * @param file The file.
871 | * @return the time that the file was last modified
872 | */
873 | fun getFileLastModified(file: File?): Long {
874 | return file?.lastModified() ?: -1
875 | }
876 |
877 | /**
878 | * Return the charset of file simply.
879 | *
880 | * @param filePath The path of file.
881 | * @return the charset of file simply
882 | */
883 | fun getFileCharsetSimple(filePath: String): String {
884 | return getFileCharsetSimple(getFileByPath(filePath))
885 | }
886 |
887 | /**
888 | * Return the charset of file simply.
889 | *
890 | * @param file The file.
891 | * @return the charset of file simply
892 | */
893 | fun getFileCharsetSimple(file: File?): String {
894 | if (file == null) return ""
895 | if (isUtf8(file)) return "UTF-8"
896 | var p = 0
897 | var `is`: InputStream? = null
898 | try {
899 | `is` = BufferedInputStream(FileInputStream(file))
900 | p = (`is`!!.read() shl 8) + `is`!!.read()
901 | } catch (e: IOException) {
902 | e.printStackTrace()
903 | } finally {
904 | try {
905 | if (`is` != null) {
906 | `is`!!.close()
907 | }
908 | } catch (e: IOException) {
909 | e.printStackTrace()
910 | }
911 |
912 | }
913 | when (p) {
914 | 0xfffe -> return "Unicode"
915 | 0xfeff -> return "UTF-16BE"
916 | else -> return "GBK"
917 | }
918 | }
919 |
920 | /**
921 | * Return whether the charset of file is utf8.
922 | *
923 | * @param filePath The path of file.
924 | * @return `true`: yes
`false`: no
925 | */
926 | fun isUtf8(filePath: String): Boolean {
927 | return isUtf8(getFileByPath(filePath))
928 | }
929 |
930 | /**
931 | * Return whether the charset of file is utf8.
932 | *
933 | * @param file The file.
934 | * @return `true`: yes
`false`: no
935 | */
936 | fun isUtf8(file: File?): Boolean {
937 | if (file == null) return false
938 | var `is`: InputStream? = null
939 | try {
940 | val bytes = ByteArray(24)
941 | `is` = BufferedInputStream(FileInputStream(file))
942 | val read = `is`!!.read(bytes)
943 | if (read != -1) {
944 | val readArr = ByteArray(read)
945 | System.arraycopy(bytes, 0, readArr, 0, read)
946 | return isUtf8(readArr) == 100
947 | } else {
948 | return false
949 | }
950 | } catch (e: IOException) {
951 | e.printStackTrace()
952 | } finally {
953 | try {
954 | if (`is` != null) {
955 | `is`!!.close()
956 | }
957 | } catch (e: IOException) {
958 | e.printStackTrace()
959 | }
960 |
961 | }
962 | return false
963 | }
964 |
965 | /**
966 | * UTF-8编码方式
967 | * ----------------------------------------------
968 | * 0xxxxxxx
969 | * 110xxxxx 10xxxxxx
970 | * 1110xxxx 10xxxxxx 10xxxxxx
971 | * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
972 | */
973 | private fun isUtf8(raw: ByteArray): Int {
974 | var i: Int
975 | val len: Int
976 | var utf8 = 0
977 | var ascii = 0
978 | if (raw.size > 3) {
979 | if (raw[0] == 0xEF.toByte() && raw[1] == 0xBB.toByte() && raw[2] == 0xBF.toByte()) {
980 | return 100
981 | }
982 | }
983 | len = raw.size
984 | var child = 0
985 | i = 0
986 | while (i < len) {
987 | // UTF-8 byte shouldn't be FF and FE
988 | if ((raw[i].and(0xFF.toByte()) == 0xFF.toByte()).or( raw[i].and(0xFE.toByte()) == 0xFE.toByte())) {
989 | return 0
990 | }
991 | if (child == 0) {
992 | // ASCII format is 0x0*******
993 | if (raw[i] and 0x7F.toByte() == raw[i] && raw[i].toInt() != 0) {
994 | ascii++
995 | } else if (raw[i] and 0xC0.toByte() == 0xC0.toByte()) {
996 | // 0x11****** maybe is UTF-8
997 | for (bit in 0..7) {
998 | if ((0x80 shr bit).toByte() and raw[i] == (0x80 shr bit).toByte()) {
999 | child = bit
1000 | } else {
1001 | break
1002 | }
1003 | }
1004 | utf8++
1005 | }
1006 | i++
1007 | } else {
1008 | child = if (raw.size - i > child) child else raw.size - i
1009 | var currentNotUtf8 = false
1010 | for (children in 0 until child) {
1011 | // format must is 0x10******
1012 | if (raw[i + children] and 0x80.toByte() != 0x80.toByte()) {
1013 | if (raw[i + children] and 0x7F.toByte() == raw[i + children] && raw[i].toInt() != 0) {
1014 | // ASCII format is 0x0*******
1015 | ascii++
1016 | }
1017 | currentNotUtf8 = true
1018 | }
1019 | }
1020 | if (currentNotUtf8) {
1021 | utf8--
1022 | i++
1023 | } else {
1024 | utf8 += child
1025 | i += child
1026 | }
1027 | child = 0
1028 | }
1029 | }
1030 | // UTF-8 contains ASCII
1031 | return if (ascii == len) {
1032 | 100
1033 | } else (100 * ((utf8 + ascii).toFloat() / len.toFloat())).toInt()
1034 | }
1035 |
1036 | /**
1037 | * Return the number of lines of file.
1038 | *
1039 | * @param filePath The path of file.
1040 | * @return the number of lines of file
1041 | */
1042 | fun getFileLines(filePath: String): Int {
1043 | return getFileLines(getFileByPath(filePath))
1044 | }
1045 |
1046 | /**
1047 | * Return the number of lines of file.
1048 | *
1049 | * @param file The file.
1050 | * @return the number of lines of file
1051 | */
1052 | fun getFileLines(file: File?): Int {
1053 | var count = 1
1054 | var `is`: InputStream? = null
1055 | try {
1056 | `is` = BufferedInputStream(FileInputStream(file!!))
1057 | val buffer = ByteArray(1024)
1058 | var readChars: Int
1059 | if (LINE_SEP.endsWith("\n")) {
1060 | readChars = `is`!!.read(buffer, 0, 1024)
1061 | while (readChars != -1) {
1062 | for (i in 0 until readChars) {
1063 | if (buffer[i] == '\n'.toByte()) ++count
1064 | }
1065 | readChars = `is`!!.read(buffer, 0, 1024)
1066 | }
1067 | } else {
1068 | readChars = `is`!!.read(buffer, 0, 1024)
1069 | while (readChars != -1) {
1070 | for (i in 0 until readChars) {
1071 | if (buffer[i] == '\r'.toByte()) ++count
1072 | }
1073 | readChars = `is`!!.read(buffer, 0, 1024)
1074 | }
1075 | }
1076 | } catch (e: IOException) {
1077 | e.printStackTrace()
1078 | } finally {
1079 | try {
1080 | if (`is` != null) {
1081 | `is`!!.close()
1082 | }
1083 | } catch (e: IOException) {
1084 | e.printStackTrace()
1085 | }
1086 |
1087 | }
1088 | return count
1089 | }
1090 |
1091 | /**
1092 | * Return the size.
1093 | *
1094 | * @param filePath The path of file.
1095 | * @return the size
1096 | */
1097 | fun getSize(filePath: String): String {
1098 | return getSize(getFileByPath(filePath))
1099 | }
1100 |
1101 | /**
1102 | * Return the size.
1103 | *
1104 | * @param file The directory.
1105 | * @return the size
1106 | */
1107 | fun getSize(file: File?): String {
1108 | if (file == null) return ""
1109 | return if (file.isDirectory) {
1110 | getDirSize(file)
1111 | } else getFileSize(file)
1112 | }
1113 |
1114 | /**
1115 | * Return the size of directory.
1116 | *
1117 | * @param dir The directory.
1118 | * @return the size of directory
1119 | */
1120 | private fun getDirSize(dir: File): String {
1121 | val len = getDirLength(dir)
1122 | return if (len == -1L) "" else byte2FitMemorySize(len)
1123 | }
1124 |
1125 | /**
1126 | * Return the size of file.
1127 | *
1128 | * @param file The file.
1129 | * @return the length of file
1130 | */
1131 | private fun getFileSize(file: File): String {
1132 | val len = getFileLength(file)
1133 | return if (len == -1L) "" else byte2FitMemorySize(len)
1134 | }
1135 |
1136 | /**
1137 | * Return the length.
1138 | *
1139 | * @param filePath The path of file.
1140 | * @return the length
1141 | */
1142 | fun getLength(filePath: String): Long {
1143 | return getLength(getFileByPath(filePath))
1144 | }
1145 |
1146 | /**
1147 | * Return the length.
1148 | *
1149 | * @param file The file.
1150 | * @return the length
1151 | */
1152 | fun getLength(file: File?): Long {
1153 | if (file == null) return 0
1154 | return if (file.isDirectory) {
1155 | getDirLength(file)
1156 | } else getFileLength(file)
1157 | }
1158 |
1159 | /**
1160 | * Return the length of directory.
1161 | *
1162 | * @param dir The directory.
1163 | * @return the length of directory
1164 | */
1165 | private fun getDirLength(dir: File): Long {
1166 | if (!isDir(dir)) return -1
1167 | var len: Long = 0
1168 | val files = dir.listFiles()
1169 | if (files != null && files.size != 0) {
1170 | for (file in files) {
1171 | if (file.isDirectory) {
1172 | len += getDirLength(file)
1173 | } else {
1174 | len += file.length()
1175 | }
1176 | }
1177 | }
1178 | return len
1179 | }
1180 |
1181 | /**
1182 | * Return the length of file.
1183 | *
1184 | * @param filePath The path of file.
1185 | * @return the length of file
1186 | */
1187 | fun getFileLength(filePath: String): Long {
1188 | val isURL = filePath.matches("[a-zA-z]+://[^\\s]*".toRegex())
1189 | if (isURL) {
1190 | try {
1191 | val conn = URL(filePath).openConnection() as HttpsURLConnection
1192 | conn.setRequestProperty("Accept-Encoding", "identity")
1193 | conn.connect()
1194 | return if (conn.getResponseCode() === 200) {
1195 | conn.getContentLength().toLong()
1196 | } else -1
1197 | } catch (e: IOException) {
1198 | e.printStackTrace()
1199 | }
1200 |
1201 | }
1202 | return getFileLength(getFileByPath(filePath))
1203 | }
1204 |
1205 | /**
1206 | * Return the length of file.
1207 | *
1208 | * @param file The file.
1209 | * @return the length of file
1210 | */
1211 | private fun getFileLength(file: File?): Long {
1212 | return if (!isFile(file)) -1 else file!!.length()
1213 | }
1214 |
1215 | /**
1216 | * Return the MD5 of file.
1217 | *
1218 | * @param filePath The path of file.
1219 | * @return the md5 of file
1220 | */
1221 | fun getFileMD5ToString(filePath: String): String {
1222 | val file = if (isSpace(filePath)) null else File(filePath)
1223 | return getFileMD5ToString(file)
1224 | }
1225 |
1226 | /**
1227 | * Return the MD5 of file.
1228 | *
1229 | * @param file The file.
1230 | * @return the md5 of file
1231 | */
1232 | fun getFileMD5ToString(file: File?): String {
1233 | return bytes2HexString(getFileMD5(file))
1234 | }
1235 |
1236 | /**
1237 | * Return the MD5 of file.
1238 | *
1239 | * @param filePath The path of file.
1240 | * @return the md5 of file
1241 | */
1242 | fun getFileMD5(filePath: String): ByteArray? {
1243 | return getFileMD5(getFileByPath(filePath))
1244 | }
1245 |
1246 | /**
1247 | * Return the MD5 of file.
1248 | *
1249 | * @param file The file.
1250 | * @return the md5 of file
1251 | */
1252 | fun getFileMD5(file: File?): ByteArray? {
1253 | if (file == null) return null
1254 | var dis: DigestInputStream? = null
1255 | try {
1256 | val fis = FileInputStream(file)
1257 | var md = MessageDigest.getInstance("MD5")
1258 | dis = DigestInputStream(fis, md)
1259 | val buffer = ByteArray(1024 * 256)
1260 | while (true) {
1261 | if (dis.read(buffer) <= 0) break
1262 | }
1263 | md = dis.messageDigest
1264 | return md.digest()
1265 | } catch (e: NoSuchAlgorithmException) {
1266 | e.printStackTrace()
1267 | } catch (e: IOException) {
1268 | e.printStackTrace()
1269 | } finally {
1270 | try {
1271 | dis?.close()
1272 | } catch (e: IOException) {
1273 | e.printStackTrace()
1274 | }
1275 |
1276 | }
1277 | return null
1278 | }
1279 |
1280 | /**
1281 | * Return the file's path of directory.
1282 | *
1283 | * @param file The file.
1284 | * @return the file's path of directory
1285 | */
1286 | fun getDirName(file: File?): String {
1287 | return if (file == null) "" else getDirName(file.absolutePath)
1288 | }
1289 |
1290 | /**
1291 | * Return the file's path of directory.
1292 | *
1293 | * @param filePath The path of file.
1294 | * @return the file's path of directory
1295 | */
1296 | fun getDirName(filePath: String): String {
1297 | if (isSpace(filePath)) return ""
1298 | val lastSep = filePath.lastIndexOf(File.separator)
1299 | return if (lastSep == -1) "" else filePath.substring(0, lastSep + 1)
1300 | }
1301 |
1302 | /**
1303 | * Return the name of file.
1304 | *
1305 | * @param file The file.
1306 | * @return the name of file
1307 | */
1308 | fun getFileName(file: File?): String {
1309 | return if (file == null) "" else getFileName(file.absolutePath)
1310 | }
1311 |
1312 | /**
1313 | * Return the name of file.
1314 | *
1315 | * @param filePath The path of file.
1316 | * @return the name of file
1317 | */
1318 | fun getFileName(filePath: String): String {
1319 | if (isSpace(filePath)) return ""
1320 | val lastSep = filePath.lastIndexOf(File.separator)
1321 | return if (lastSep == -1) filePath else filePath.substring(lastSep + 1)
1322 | }
1323 |
1324 | /**
1325 | * Return the name of file without extension.
1326 | *
1327 | * @param file The file.
1328 | * @return the name of file without extension
1329 | */
1330 | fun getFileNameNoExtension(file: File?): String {
1331 | return if (file == null) "" else getFileNameNoExtension(file.path)
1332 | }
1333 |
1334 | /**
1335 | * Return the name of file without extension.
1336 | *
1337 | * @param filePath The path of file.
1338 | * @return the name of file without extension
1339 | */
1340 | fun getFileNameNoExtension(filePath: String): String {
1341 | if (isSpace(filePath)) return ""
1342 | val lastPoi = filePath.lastIndexOf('.')
1343 | val lastSep = filePath.lastIndexOf(File.separator)
1344 | if (lastSep == -1) {
1345 | return if (lastPoi == -1) filePath else filePath.substring(0, lastPoi)
1346 | }
1347 | return if (lastPoi == -1 || lastSep > lastPoi) {
1348 | filePath.substring(lastSep + 1)
1349 | } else filePath.substring(lastSep + 1, lastPoi)
1350 | }
1351 |
1352 | /**
1353 | * Return the extension of file.
1354 | *
1355 | * @param file The file.
1356 | * @return the extension of file
1357 | */
1358 | fun getFileExtension(file: File?): String {
1359 | return if (file == null) "" else getFileExtension(file.path)
1360 | }
1361 |
1362 | /**
1363 | * Return the extension of file.
1364 | *
1365 | * @param filePath The path of file.
1366 | * @return the extension of file
1367 | */
1368 | fun getFileExtension(filePath: String): String {
1369 | if (isSpace(filePath)) return ""
1370 | val lastPoi = filePath.lastIndexOf('.')
1371 | val lastSep = filePath.lastIndexOf(File.separator)
1372 | return if (lastPoi == -1 || lastSep >= lastPoi) "" else filePath.substring(lastPoi + 1)
1373 | }
1374 |
1375 | ///////////////////////////////////////////////////////////////////////////
1376 | // interface
1377 | ///////////////////////////////////////////////////////////////////////////
1378 |
1379 | interface OnReplaceListener {
1380 | fun onReplace(srcFile: File, destFile: File): Boolean
1381 | }
1382 |
1383 | ///////////////////////////////////////////////////////////////////////////
1384 | // other utils methods
1385 | ///////////////////////////////////////////////////////////////////////////
1386 |
1387 | private val HEX_DIGITS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
1388 |
1389 | private fun bytes2HexString(bytes: ByteArray?): String {
1390 | if (bytes == null) return ""
1391 | val len = bytes.size
1392 | if (len <= 0) return ""
1393 | val ret = CharArray(len shl 1)
1394 | var i = 0
1395 | var j = 0
1396 | while (i < len) {
1397 | ret[j++] = HEX_DIGITS[bytes[i].toInt() shr 4 and 0x0f]
1398 | ret[j++] = HEX_DIGITS[bytes[i].toInt() and 0x0f]
1399 | i++
1400 | }
1401 | return String(ret)
1402 | }
1403 |
1404 | private fun byte2FitMemorySize(byteNum: Long): String {
1405 | return if (byteNum < 0) {
1406 | "shouldn't be less than zero!"
1407 | } else if (byteNum < 1024) {
1408 | String.format(Locale.getDefault(), "%.3fB", byteNum.toDouble())
1409 | } else if (byteNum < 1048576) {
1410 | String.format(Locale.getDefault(), "%.3fKB", byteNum.toDouble() / 1024)
1411 | } else if (byteNum < 1073741824) {
1412 | String.format(Locale.getDefault(), "%.3fMB", byteNum.toDouble() / 1048576)
1413 | } else {
1414 | String.format(Locale.getDefault(), "%.3fGB", byteNum.toDouble() / 1073741824)
1415 | }
1416 | }
1417 |
1418 | private fun isSpace(s: String?): Boolean {
1419 | if (s == null) return true
1420 | var i = 0
1421 | val len = s.length
1422 | while (i < len) {
1423 | if (!Character.isWhitespace(s[i])) {
1424 | return false
1425 | }
1426 | ++i
1427 | }
1428 | return true
1429 | }
1430 |
1431 | private fun writeFileFromIS(
1432 | file: File,
1433 | `is`: InputStream
1434 | ): Boolean {
1435 | var os: OutputStream? = null
1436 | try {
1437 | os = BufferedOutputStream(FileOutputStream(file))
1438 | val data = ByteArray(8192)
1439 | var len: Int
1440 | len = `is`.read(data, 0, 8192)
1441 | while (len != -1) {
1442 | os!!.write(data, 0, len)
1443 | len = `is`.read(data, 0, 8192)
1444 | }
1445 | return true
1446 | } catch (e: IOException) {
1447 | e.printStackTrace()
1448 | return false
1449 | } finally {
1450 | try {
1451 | `is`.close()
1452 | } catch (e: IOException) {
1453 | e.printStackTrace()
1454 | }
1455 |
1456 | try {
1457 | if (os != null) {
1458 | os!!.close()
1459 | }
1460 | } catch (e: IOException) {
1461 | e.printStackTrace()
1462 | }
1463 |
1464 | }
1465 | }
1466 |
1467 | fun printStream(inputStream: InputStream){
1468 | var buffer = ByteArray(1024)
1469 | val bos = ByteArrayOutputStream()
1470 | var len = inputStream.read(buffer)
1471 | while (len != -1){
1472 | bos.write(buffer,0,len)
1473 | len = inputStream.read(buffer)
1474 | }
1475 | System.out.println(String(bos.toByteArray(), Charset.forName("UTF-8")))
1476 | inputStream.close()
1477 | }
1478 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/SignUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import java.io.ByteArrayOutputStream
4 | import java.io.File
5 | import java.io.IOException
6 | import java.nio.charset.Charset
7 |
8 |
9 | object SignUtils {
10 | @Throws(InterruptedException::class, IOException::class)
11 | fun signature(unsignedApk: File, signedApk: File, keyStore: String) {
12 | val cmd = arrayOf(
13 | "jarsigner",
14 | "-sigalg",
15 | "SHA1withRSA",
16 | "-digestalg",
17 | "SHA1",
18 | "-keystore",
19 | keyStore,
20 | "-storepass",
21 | "a66388487",
22 | "-keypass",
23 | "a66388487",
24 | "-signedjar",
25 | signedApk.absolutePath,
26 | unsignedApk.absolutePath,
27 | "wuse"
28 | )
29 | val process = Runtime.getRuntime().exec(cmd)
30 | println("start sign")
31 | try {
32 | val waitResult = process.waitFor()
33 | println("waitResult: $waitResult")
34 | } catch (e: InterruptedException) {
35 | e.printStackTrace()
36 | throw e
37 | }
38 |
39 | println("process.exitValue() " + process.exitValue())
40 | if (process.exitValue() != 0) {
41 | val inputStream = process.errorStream
42 | var len: Int
43 | val buffer = ByteArray(2048)
44 | val bos = ByteArrayOutputStream()
45 | len = inputStream.read(buffer)
46 | while (len != -1) {
47 | bos.write(buffer, 0, len)
48 | len = inputStream.read(buffer)
49 | }
50 | println(String(bos.toByteArray(), Charset.forName("gbk")))
51 | throw RuntimeException("签名执行失败")
52 | }
53 | println("finish signed")
54 | process.destroy()
55 | }
56 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/XmlParseUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import org.dom4j.Document
4 | import org.dom4j.Element
5 | import org.dom4j.io.OutputFormat
6 | import org.dom4j.io.SAXReader
7 | import org.dom4j.io.XMLWriter
8 | import org.xml.sax.Attributes
9 | import org.xml.sax.helpers.DefaultHandler
10 | import java.io.File
11 | import java.io.FileOutputStream
12 | import java.io.InputStream
13 | import java.io.OutputStreamWriter
14 | import javax.xml.parsers.SAXParserFactory
15 |
16 | object XmlParseUtils {
17 | fun sax2xml(ips: InputStream):String{
18 | val spf = SAXParserFactory.newInstance()
19 | //初始化Sax解析器
20 | val sp = spf.newSAXParser()
21 | val handler = MyHandler()
22 | sp.parse(ips,handler)
23 | return handler.originName?:""
24 | }
25 |
26 | /**
27 | * 修改xml文件
28 | */
29 | fun changeXmlBySax(fileXml:File,newApplicationName:String){
30 | var sax = SAXReader()
31 | var document = sax.read(fileXml)
32 | var root = document.rootElement
33 | var application = root.element("application")
34 | //原有的 application 名称
35 | var applicationName = application.attributeValue("name")
36 | var applicationAttr = application.attribute("name")
37 | //将壳中的 application 替换原来的 application
38 | applicationAttr.text = newApplicationName
39 |
40 | var element = application.addElement("meta-data")
41 | element.addAttribute("android:name","app_name")
42 | element.addAttribute("android:value",applicationName)
43 | saveDocument(document,fileXml)
44 |
45 | }
46 | fun saveDocument(document:Document,file:File){
47 | var osWrite = OutputStreamWriter(FileOutputStream(file))
48 | var format = OutputFormat.createPrettyPrint()// 获取输出的指定格式
49 | format.encoding = "UTF-8"
50 | var writer = XMLWriter(osWrite,format)
51 | writer.write(document)
52 | writer.flush()
53 | writer.close()
54 | }
55 |
56 |
57 | class MyHandler: DefaultHandler() {
58 | var originName:String?=null
59 | var isApplication = false
60 | override fun startDocument() {
61 | super.startDocument()
62 | }
63 |
64 | override fun endDocument() {
65 | super.endDocument()
66 | }
67 |
68 | override fun startElement(
69 | uri: String?,
70 | localName: String?,
71 | qName: String?,
72 | attributes: Attributes?
73 | ) {
74 | super.startElement(uri, localName, qName, attributes)
75 | isApplication = "application" == qName
76 | if(isApplication) {
77 | originName = attributes?.getValue("android:name")
78 | }
79 | }
80 |
81 |
82 |
83 | override fun endElement(uri: String?, localName: String?, qName: String?) {
84 | super.endElement(uri, localName, qName)
85 | }
86 |
87 | override fun characters(ch: CharArray, start: Int, length: Int) {
88 | super.characters(ch, start, length)
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/src/com/sakuqi/shell/utils/ZipUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sakuqi.shell.utils
2 |
3 | import java.io.BufferedInputStream
4 | import java.io.File
5 | import java.io.FileInputStream
6 | import java.io.FileOutputStream
7 | import java.lang.Exception
8 | import java.util.zip.*
9 |
10 | object ZipUtils{
11 | fun unZip(zip: File,dir:File){
12 | try{
13 | dir.delete()
14 | var zipFile = ZipFile(zip)
15 | var entries = zipFile.entries()
16 | while (entries.hasMoreElements()){
17 | var zipEntry = entries.nextElement()
18 | var name = zipEntry.name
19 | if(name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF")
20 | || name.equals("META-INF/MANIFEST.MF")){
21 | continue
22 | }
23 | if(!zipEntry.isDirectory){
24 | var file = File(dir,name)
25 | if(!file.parentFile.exists()){
26 | file.parentFile.mkdirs()
27 | }
28 | var fos = FileOutputStream(file)
29 | var ips = zipFile.getInputStream(zipEntry)
30 | var buffer = ByteArray(1024)
31 | var len = ips.read(buffer)
32 | while (len != -1){
33 | fos.write(buffer,0,len)
34 | len = ips.read(buffer)
35 | }
36 | ips.close()
37 | fos.close()
38 | }
39 | }
40 | zipFile.close()
41 | }catch (e:Exception){
42 | e.printStackTrace()
43 | }
44 | }
45 |
46 | fun zip(dir: File,zip: File){
47 | zip.delete()
48 | var cos = CheckedOutputStream(FileOutputStream(zip),CRC32())
49 | var zos = ZipOutputStream(cos)
50 | compress(dir,zos,"")
51 | zos.flush()
52 | zos.close()
53 | }
54 |
55 |
56 | private fun compress(dir: File, zos: ZipOutputStream, s: String) {
57 | if(dir.isDirectory){
58 | compressDir(dir,zos,s)
59 | }else{
60 | compressFile(dir,zos,s)
61 | }
62 | }
63 |
64 | private fun compressFile(file: File, zos: ZipOutputStream, dir: String) {
65 | var dirName = dir + file.name
66 | var dirNameNew = dirName.split("/")
67 | var buffer = StringBuffer()
68 | if(dirNameNew.size > 1){
69 | for (i in 1 until dirNameNew.size){
70 | buffer.append("/")
71 | buffer.append(dirNameNew[i])
72 | }
73 | }else{
74 | buffer.append("/")
75 | }
76 |
77 | var entry = ZipEntry(buffer.substring(1))
78 | zos.putNextEntry(entry)
79 | var bis = BufferedInputStream(FileInputStream(file))
80 | var data = ByteArray(1024)
81 | var len = bis.read(data,0,1024)
82 | while (len != -1){
83 | zos.write(data,0,len)
84 | len = bis.read(data,0,1024)
85 | }
86 | bis.close()
87 | zos.closeEntry()
88 | }
89 |
90 |
91 | private fun compressDir(dir: File, zos: ZipOutputStream, basePath: String) {
92 | var files = dir.listFiles()
93 | for (file in files){
94 | compress(file,zos,basePath+dir.name+"/")
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------