Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android產品研發(二十三)--)android中保存靜態秘鑰實踐

android產品研發(二十三)--)android中保存靜態秘鑰實踐

編輯:關於Android編程

上一篇文章中我們講解了android中的實用調試技巧。講解了android中的原生Log API以及其使用方式,講解了自定義日志API、使用方式和實現原理,講解了通過gradle配置日志框架在正式環境中屏蔽日志信息等。最後我們還重點講解了android studio中的斷點調試技巧,主要包括:斷點調試功能、日志斷點、求值調試、異常斷點、方法斷點等。

本文我們將講解一個android產品研發中可能會碰到的一個問題:如何在App中保存靜態秘鑰以及保證其安全性。許多的移動app需要在app端保存一些靜態字符串常量,其可能是靜態秘鑰、第三方appId等。在保存這些字符串常量的時候就涉及到了如何保證秘鑰的安全性問題。如何保證在App中靜態秘鑰唯一且正確安全,這是一個很重要的問題,公司的產品中就存在著靜態字符串常量類型的秘鑰,所以一個明顯的問題就是如何生成秘鑰,保證秘鑰的安全性?

現今保存靜態秘鑰的幾種主流通用做法:

通過SharedPreferences保存靜態秘鑰;

通過java硬編碼的方式保存

通過NDK的方式,將靜態秘鑰保存在so文件中;

幾種保存靜態秘鑰方式的優劣勢:

密鑰直接明文存在sharedprefs文件中,這是最不安全的。

密鑰直接硬編碼在Java代碼中,這很不安全,dex文件很容易被逆向成java代碼。

將密鑰分成不同的幾段,有的存儲在文件中、有的存儲在代碼中,最後將他們拼接起來,可以將整個操作寫的很復雜,這因為還是在java層,逆向者只要花點時間,也很容易被逆向。

用ndk開發,將密鑰放在so文件,加密解密操作都在so文件裡,這從一定程度上提高了的安全性,擋住了一些逆向者,但是有經驗的逆向者還是會使用IDA破解的。

在so文件中不存儲密鑰,so文件中對密鑰進行加解密操作,將密鑰加密後的密鑰命名為其他普通文件,存放在assets目錄下或者其他目錄下,接著在so文件裡面添加無關代碼(花指令),雖然可以增加靜態分析難度,但是可以使用動態調式的方法,追蹤加密解密函數,也可以查找到密鑰內容。

可以說在設備上安全存儲密鑰這個基本無解,只能選擇增大逆向成本。而要是普通開發者的話,這需要耗費很大的心血,要評估你的app應用的重要程度來選擇相應的技術方案。

產品App中需要保存的秘鑰:

由於app需要與服務器交互所以這時候若用戶為登錄則客戶端需要一個默認的秘鑰來確認App的游客身份,因此需要在app中保存一個默認的請求秘鑰。

當用戶未登錄或者是退出登錄時,請求服務器使用默認的秘鑰字符串;

當用戶登錄時使用從服務器或其的秘鑰字符串;

這樣我們需要在App端保存一個默認的秘鑰字符串用於標識用戶的游客身份,而一個問題就是如何保證秘鑰字符串的安全性?

考慮到的其他幾種保存秘鑰方式:

通過保存文件的方式保存秘鑰信息;

通過數據庫的方式保存秘鑰信息;

通過配置gradle的方式保存秘鑰信息;

文件方式:顯而易見的通過保存文件的方式保存秘鑰信息,有一個問題就是,如果用戶惡意刪除App保存的秘鑰信息,那麼App就無法使用默認的秘鑰信息了,這樣秘鑰的唯一性也就無法保證。

數據庫方式:和通過文件的方式保存秘鑰信息一樣,通過數據庫保存也是無法保證惡意用戶刪除數據庫信息,這樣我們的App就丟失了默認的秘鑰信息。

gradle配置:我們通過下面的gradle配置變量的方式,來講解如何在gradle配置變量信息。

通過gradle配置變量:

在android gradle中我們不單可以引用依賴包,執行腳本還可以配置靜態變量:

buildTypes {
        debug {
            // 顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            buildConfigField "String", "appKeyPre", "\"xxx\""
            //混淆
            minifyEnabled false
            //Zipalign優化
            zipAlignEnabled true
            // 移除無用的resource文件
            shrinkResources true
            //加載默認混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //簽名
            signingConfig signingConfigs.debug
        }
        release {
            // 不顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            buildConfigField "String", "appKeyPre", "\"xxx\""
            //混淆
            minifyEnabled true
            //Zipalign優化
            zipAlignEnabled true
            // 移除無用的resource文件
            shrinkResources true
            //加載默認混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //簽名
            signingConfig signingConfigs.relealse
        }
    }

如上面代碼所示我們在gradle中配置了一個名稱為appKey的字符串變量,編譯gradle則會在gradle的編譯類:BuildConfig中生成該靜態變量:

/**
 * gradle編譯後生成的編譯類
 */
public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.sample.renter";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "internal";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0.0";
  // Fields from build type: debug
  public static final boolean LOG_DEBUG = true;
  public static final String appKey = "xxx";
}

可以發現通過配置gradle的方式配置靜態秘鑰反編譯的時候無法找到秘鑰的實現,因此有很強的安全性,而同時避免了被用戶的惡意刪除,所以通過gradle配置字符串的方式保存app中的靜態秘鑰是一個不錯的選擇。

產品中保存靜態秘鑰實踐:

最終經過比對各種靜態秘鑰存儲方案,我們決定使用gradle配置 + 靜態代碼 + 字符串運算的方式實現靜態秘鑰的存儲。

首先將靜態秘鑰分為三部分:

第一部分通過gradle配置的方式存儲;

第二部分通過java硬編碼的方式存儲;

第三部分通過java字符串拼接運算的方式存儲;

獲取appKey第一部分字符串:
通過gradle配置的方式我們上面已經做了介紹,其就是在mudle中的gradle文件中再起buildType節點下定義字符串變量,這裡需要注意的是若是有正式環境和測試環境之分,需要分別定義字符串變量,這樣我們就可以通過BuildConfig獲取appKay第一部分的字符串了。

/**
 * 獲取AppKey part1
 */
public static String getBK1() {
        return BuildConfig.appKey;
    }

獲取appKey第二部分字符串:

第二部分的appKay字符串是通過運算的出來的,這裡的運算可以是任意運算方式,越復雜越好,越讓人看不懂越好,當然結果需要時唯一的。比如:

public static StringBuffer getBk2() {
        StringBuffer sb = new StringBuffer();
        sb.append(Config.getGBS(2, 5));
        return sb;
    }

而這裡的getGBS方法的實現:

public static int getGBS(int x, int y){
        for(int i = 1; i<= x * y; i++){
            if(i % x == 0 && i % y == 0)
                return i;
        }

        return x * y;
    }

最終的結果返回是:10,當然了不同的字符串需要不同的算法;

獲取appKey第三部分字符串:

這裡就只是使用了簡單的字符串硬編碼

public static String getBK3() {
    return "xhxh";
}

獲取最終的appKey字符串:

/**
 * 獲取最終的appKey字符串
 */
public static byte[] getDefaultKey() {
        StringBuffer sb = new StringBuffer();
        sb.append(getBK1()).append(getBK2()).append(getBk3());

        return sb.toString();
    }

這樣經過一系列的操作之後我們就獲取到了最終的靜態秘鑰。當然了產品中最好實現了代碼混淆的功能,這樣也能增大逆向的難度。

總結:

在App端保存靜態秘鑰可以通過SharedPreferences、java硬編碼,ndk中的so文件,文件,數據庫,gradle配置的方式實現;

為了保證秘鑰的安全性可以采用多種方式混合,這樣可以增加惡意反編譯的難度;

在App端保存秘鑰不能真正的保證秘鑰的安全性,只能增加反編譯的難易程度;

推薦使用gradle配置的方式配置靜態秘鑰,反編譯的時候無法追蹤到gradle的配置文件;

不推薦使用文件,數據庫的方式保存靜態秘鑰,容易被用戶惡意刪除,而出現不可預知的錯誤;



本文以同步至github中:https://github.com/yipianfengye/androidProject,歡迎star和follow


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved