【Android 安全】DEX 加密 ( 代理 Application 開發 | 加密解密演算法 API | 編譯代理 Application 依賴庫 | 解壓依賴庫 aar 檔案 )

韓曙亮發表於2020-11-18



參考部落格 :


【Android 安全】DEX 加密 ( 支援多 DEX 的 Android 工程結構 ) 部落格中介紹了 DEX 加密工程的基本結構 ,

app 是主應用 , 其 Module 型別是 “Phone & Tablet Module” ,

multiple-dex-core 是 Android 依賴庫 , 其作用是解密並載入多 DEX 檔案 , 其 Module 型別是 “Android Library” ,

multiple-dex-tools 是 Java 依賴庫 , 其型別是 “Java or Kotlin Library” , 其作用是用於生成主 DEX ( 主 DEX 的作用就是用於解密與載入多 DEX ) , 並且還要為修改後的 APK 進行簽名 ;


【Android 安全】DEX 加密 ( 代理 Application 開發 | multiple-dex-core 依賴庫開發 | 配置後設資料 | 獲取 apk 檔案並準備相關目錄 ) 部落格中講解了 multiple-dex-core 依賴庫開發 , 每次啟動都要解密與載入 dex 檔案 , 在該部落格中講解到了 獲取 apk 檔案 , 並準備解壓目錄 ;

【Android 安全】DEX 加密 ( 代理 Application 開發 | 解壓 apk 檔案 | 判定是否是第一次啟動 | 遞迴刪除檔案操作 | 解壓 Zip 檔案操作 ) 部落格中講解了 apk 檔案解壓操作 ;

【Android 安全】DEX 加密 ( 代理 Application 開發 | 載入 dex 檔案 | 反射獲取系統的 Element[] dexElements )部落格中講解了 dex 檔案載入第一階段 , 獲取系統中的 Element[] dexElements ;

【Android 安全】DEX 加密 ( 代理 Application 開發 | 載入 dex 檔案 | 使用反射獲取方法建立本應用的 dexElements | 各版本建立 dex 陣列原始碼對比 ) 部落格中講解了講解 dex 檔案載入操作 第二階段 , 建立本應用的 dex 檔案陣列 Element[] dexElements ;

【Android 安全】DEX 加密 ( 代理 Application 開發 | 載入 dex 檔案 | 將系統的 dexElements 與 應用的 dexElements 合併 | 替換操作 ) 部落格中講解了剩餘的兩個操作 :

  • 系統載入的 Element[] dexElements 陣列 與 我們 自己的 Element[] dexElements 陣列 進行合併操作 ;
  • 替換 ClassLoader 載入過程中的 Element[] dexElements 陣列 ( 封裝在 DexPathList 中 )

本部落格中介紹 加密解密演算法 API , 編譯代理 Application 依賴庫 , 解壓依賴庫 aar 檔案 ;





一、加密解密演算法 API



先寫一個加密解密演算法 , 該演算法用於 dex 檔案的 加密 / 解密 操作 ;


初始化 加密 / 解密 演算法 : 根據演算法型別 , 初始化 加密 / 解密 演算法 ;

    /**
     * 加密解密演算法型別
     */
    val algorithm = "AES/ECB/PKCS5Padding"

    // 初始化加密演算法
    encryptCipher = Cipher.getInstance(algorithm)

設定金鑰 : 獲取金鑰的 Byte 陣列型別 , 並建立 AES 金鑰 ;

        // 將金鑰字串轉為位元組陣列
        var keyByte = pwd.toByteArray()
        // 建立金鑰
        val key = SecretKeySpec(keyByte, "AES")

設定演算法型別及金鑰 : 加密演算法 , 傳入 Cipher.ENCRYPT_MODE 引數 , 解密演算法 , 傳入 Cipher.DECRYPT_MODE 引數 ;

        // 設定演算法型別, 及金鑰
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);
        // 設定演算法型別, 及金鑰
        decryptCipher.init(Cipher.DECRYPT_MODE, key);

完整程式碼示例 :

class AES {

    /**
     * 加密金鑰, 16 位元組
     */
    val DEFAULT_PWD = "kimhslmultiplede"

    /**
     * 加密解密演算法型別
     */
    val algorithm = "AES/ECB/PKCS5Padding"

    /**
     * 加密演算法, 目前本應用中只需要加密, 不需要解密
     */
    lateinit var encryptCipher: Cipher;

    /**
     * 解密演算法
     */
    lateinit var decryptCipher: Cipher;

    @ExperimentalStdlibApi
    constructor(pwd: String){
        // 初始化加密演算法
        encryptCipher = Cipher.getInstance(algorithm)
        // 初始化解密演算法
        decryptCipher = Cipher.getInstance(algorithm)

        // 將金鑰字串轉為位元組陣列
        var keyByte = pwd.toByteArray()
        // 建立金鑰
        val key = SecretKeySpec(keyByte, "AES")

        // 初始化加密演算法
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);
        // 初始化解密演算法
        decryptCipher.init(Cipher.DECRYPT_MODE, key);
    }

    /**
     * 加密操作
     */
    fun encrypt(contet : ByteArray) : ByteArray{
        var result : ByteArray = encryptCipher.doFinal(contet)
        return  result
    }

    /**
     * 解密操作
     */
    fun decrypt(contet : ByteArray) : ByteArray{
        var result : ByteArray = decryptCipher.doFinal(contet)
        return  result
    }

}




二、編譯代理 Application 依賴庫



生成 dex 檔案 , 該 dex 檔案中只包含解密 其它 dex 的功能

編譯配置 : 在 選單欄 / File / Setting / Build, Execution, Deployment / Compiler 設定介面中 ,

勾選 Compile independent modules in parallel (may require larger ) 選項 ;

在這裡插入圖片描述


編譯工程 : 編譯工程時會生成 Android 依賴庫的 aar 檔案 , 生成目錄是 module/build/outputs/aar/ 目錄下

在這裡插入圖片描述


獲取 multiple-dex-core-debug.aar 檔案的另外一種方法 : 執行 Gradle 任務中的 Tasks/other/assembleDebug 任務 , 即可生成 multiple-dex-core-debug.aar 檔案 ;

在這裡插入圖片描述


解壓獲取檔案 ( 僅做參考 ) :

D:\002_Project\002_Android_Learn\DexEncryption\multiple-dex-core\build\outputs\aar

路徑下的 multiple-dex-core-debug.aar 檔案字尾修改為 .zip

解壓上述檔案 , 拿到 classes.jar 檔案即可 ;

在這裡插入圖片描述

該 classes.jar 就是 multiple-dex-core 的 Android 依賴庫中的 ProxyApplication.kt Kotlin 檔案 編譯出的 jar 包 ;

上述 解壓檔案僅做 參考 , 實際使用時 , 在程式中使用程式碼解壓 ;





三、解壓代理 Application 依賴庫 aar 檔案



獲取 multiple-dex-core-debug.aar 檔案物件

    // 獲取 multiple-dex-core-debug.aar 檔案物件
    var aarFile = File("multiple-dex-core/build/outputs/aar/multiple-dex-core-debug.aar")

解壓上述 multiple-dex-core-debug.aar 檔案到 aarUnzip 目錄中 , 先建立解壓目錄 ;

    // 解壓上述 multiple-dex-core-debug.aar 檔案到 aarUnzip 目錄中
    // 建立解壓目錄
    var aarUnzip = File("multiple-dex-tools/aarUnzip")

正式解壓 , unZipAar 方法在下面完整程式碼中 ;

    // 解壓操作
    unZipAar(aarFile, aarUnzip)

拿到 multiple-dex-core-debug.aar 中解壓出來的 classes.jar 檔案

    // 拿到 multiple-dex-core-debug.aar 中解壓出來的 classes.jar 檔案
    var classesJarFile = File(aarUnzip, "classes.jar")

完整程式碼示例 :

fun main() {
    /*
        1 . 生成 dex 檔案 , 該 dex 檔案中只包含解密 其它 dex 的功能

        編譯工程
        會生成 Android 依賴庫的 aar 檔案
        生成目錄是 module/build/outputs/aar/ 目錄下

        前提是需要在 選單欄 / File / Setting / Build, Execution, Deployment / Compiler
        設定介面中 , 勾選 Compile independent modules in parallel (may require larger )

        將 D:\002_Project\002_Android_Learn\DexEncryption\multiple-dex-core\build\outputs\aar
        路徑下的 multiple-dex-core-debug.aar 檔案字尾修改為 .zip
        解壓上述檔案
        拿到 classes.jar 檔案即可 ;
     */

    // 獲取 multiple-dex-core-debug.aar 檔案物件
    var aarFile = File("multiple-dex-core/build/outputs/aar/multiple-dex-core-debug.aar")

    // 解壓上述 multiple-dex-core-debug.aar 檔案到 aarUnzip 目錄中
    // 建立解壓目錄
    var aarUnzip = File("multiple-dex-tools/aarUnzip")
    // 解壓操作
    unZipAar(aarFile, aarUnzip)

    // 拿到 multiple-dex-core-debug.aar 中解壓出來的 classes.jar 檔案
    var classesJarFile = File(aarUnzip, "classes.jar")
}

/**
 * 刪除檔案, 如果有目錄, 則遞迴刪除
 */
private fun deleteFile(file: File) {
    if (file.isDirectory) {
        val files = file.listFiles()
        for (f in files) {
            deleteFile(f)
        }
    } else {
        file.delete()
    }
}

/**
 * 解壓檔案
 * @param zip 被解壓的壓縮包檔案
 * @param dir 解壓後的檔案存放目錄
 */
fun unZipAar(zip: File, dir: File) {
    try {
        // 如果存放檔案目錄存在, 刪除該目錄
        deleteFile(dir)
        // 獲取 zip 壓縮包檔案
        val zipFile = ZipFile(zip)
        // 獲取 zip 壓縮包中每一個檔案條目
        val entries = zipFile.entries()
        // 遍歷壓縮包中的檔案
        while (entries.hasMoreElements()) {
            val zipEntry = entries.nextElement()
            // zip 壓縮包中的檔名稱 或 目錄名稱
            val name = zipEntry.name
            // 如果 apk 壓縮包中含有以下檔案 , 這些檔案是 V1 簽名檔案儲存目錄 , 不需要解壓 , 跳過即可
            if (name == "META-INF/CERT.RSA" || name == "META-INF/CERT.SF" || (name
                        == "META-INF/MANIFEST.MF")
            ) {
                continue
            }
            // 如果該檔案條目 , 不是目錄 , 說明就是檔案
            if (!zipEntry.isDirectory) {
                val file = File(dir, name)
                // 建立目錄
                if (!file.parentFile.exists()) {
                    file.parentFile.mkdirs()
                }
                // 向剛才建立的目錄中寫出檔案
                val fileOutputStream = FileOutputStream(file)
                val inputStream = zipFile.getInputStream(zipEntry)
                val buffer = ByteArray(1024)
                var len: Int
                while (inputStream.read(buffer).also { len = it } != -1) {
                    fileOutputStream.write(buffer, 0, len)
                }
                inputStream.close()
                fileOutputStream.close()
            }
        }

        // 關閉 zip 檔案
        zipFile.close()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

相關文章