標籤:false java _id strong 公司 移除 開發人員 因此 assets
本文我們將講解一個Android產品研發中可能會碰到的一個問題:如何在App中儲存靜態秘鑰以及保證其安全性。許多的移動app需要在app端儲存一些靜態字串常量,其可能是靜態秘鑰、第三方appId等。在儲存這些字串常量的時候就涉及到了如何保證秘鑰的安全性問題。如何保證在App中靜態秘鑰唯一且正確安全,這是一個很重要的問題,公司的產品中就存在著靜態字串常量類型的秘鑰,所以一個明顯的問題就是如何產生秘鑰,保證秘鑰的安全性?
現今儲存靜態秘鑰的幾種主流通用做法:(參考:Android安全開發之淺談密鑰寫入程式碼)
幾種儲存靜態秘鑰方式的優劣勢:
密鑰直接明文存在sharedprefs檔案中,這是最不安全的。
密鑰直接寫入程式碼在Java代碼中,這很不安全,dex檔案很容易被逆向成java代碼。
將密鑰分成不同的幾段,有的儲存在檔案中、有的儲存在代碼中,最後將他們拼接起來,可以將整個操作寫的很複雜,這因為還是在java層,逆向者只要花點時間,也很容易被逆向。
用ndk開發,將密鑰放在so檔案,加密解密操作都在so檔案裡,這從一定程度上提高了的安全性,擋住了一些逆向者,但是有經驗的逆向者還是會使用IDA破解的。
在so檔案中不儲存體金鑰,so檔案中對密鑰進行加解密操作,將祕密金鑰加密後的密鑰命名為其他普通檔案,存放在assets目錄下或者其他目錄下,接著在so檔案裡面添加無關代碼(花指令),雖然可以增加靜態分析難度,但是可以使用動態調式的方法,追蹤加密解密函數,也可以尋找到金鑰產製原料。
可以說在裝置上安全儲存體金鑰這個基本無解,只能選擇增大逆向成本。而要是普通開發人員的話,這需要耗費很大的心血,要評估你的app應用的重要程度來選擇相應的技術方案。
產品App中需要儲存的秘鑰:
由於app需要與伺服器互動所以這時候若使用者為登入則用戶端需要一個預設的秘鑰來確認App的遊客身份,因此需要在app中儲存一個預設的請求秘鑰。
這樣我們需要在App端儲存一個預設的秘鑰字串用於標識使用者的遊客身份,而一個問題就是如何保證秘鑰字串的安全性?
考慮到的其他幾種儲存秘鑰方式:
通過儲存檔案的方式儲存秘鑰資訊;
通過資料庫的方式儲存秘鑰資訊;
通過配置gradle的方式儲存秘鑰資訊;
通過配置string.xml的方式儲存秘鑰資訊;
檔案方式:顯而易見的通過儲存檔案的方式儲存秘鑰資訊,有一個問題就是,如果使用者惡意刪除App儲存的秘鑰資訊,那麼App就無法使用預設的秘鑰資訊了,這樣秘鑰的唯一性也就無法保證。
資料庫方式:和通過檔案的方式儲存秘鑰資訊一樣,通過資料庫儲存也是無法保證惡意使用者刪除資料庫資訊,這樣我們的App就丟失了預設的秘鑰資訊。
gradle配置:我們通過下面的gradle組態變數的方式,來講解如何在gradle組態變數資訊。
string.xml:在下面我們做詳細的介紹。
通過gradle組態變數:
在Android gradle中我們不單可以引用依賴包,執行指令碼還可以配置靜態變數:
1 buildTypes { 2 debug { 3 // 顯示Log 4 buildConfigField "boolean", "LOG_DEBUG", "true" 5 buildConfigField "String", "appKeyPre", "\"xxx\"" 6 //混淆 7 minifyEnabled false 8 //Zipalign最佳化 9 zipAlignEnabled true10 // 移除無用的resource檔案11 shrinkResources true12 //載入預設混淆設定檔13 proguardFiles getDefaultProguardFile(‘proguard-Android.txt‘), ‘proguard-rules.pro‘14 //簽名15 signingConfig signingConfigs.debug16 }17 release {18 // 不顯示Log19 buildConfigField "boolean", "LOG_DEBUG", "false"20 buildConfigField "String", "appKeyPre", "\"xxx\""21 //混淆22 minifyEnabled true23 //Zipalign最佳化24 zipAlignEnabled true25 // 移除無用的resource檔案26 shrinkResources true27 //載入預設混淆設定檔28 proguardFiles getDefaultProguardFile(‘proguard-Android.txt‘), ‘proguard-rules.pro‘29 //簽名30 signingConfig signingConfigs.relealse31 }32 }
如上面代碼所示我們在gradle中配置了一個名稱為appKey的字串變數,編譯gradle則會在gradle的編譯類:BuildConfig中產生該靜態變數:
1 /** 2 * gradle編譯後產生的編譯類 3 */ 4 public final class BuildConfig { 5 public static final boolean DEBUG = Boolean.parseBoolean("true"); 6 public static final String APPLICATION_ID = "com.sample.renter"; 7 public static final String BUILD_TYPE = "debug"; 8 public static final String FLAVOR = "internal"; 9 public static final int VERSION_CODE = 1;10 public static final String VERSION_NAME = "1.0.0";11 // Fields from build type: debug12 public static final boolean LOG_DEBUG = true;13 public static final String appKey = "xxx";14 }
可以發現通過配置gradle的方式配置靜態秘鑰反編譯的時候也是找到秘鑰的,但是增加了逆向的難度,而且避免了被使用者的惡意刪除,所以通過gradle配置字串的方式儲存app中的靜態秘鑰是一個不錯的選擇。
通過string.xml配置秘鑰資訊
通過string.xml配置秘鑰資訊也是一個不錯的選擇。在string.xml中定義字串值之後,通過代碼擷取反編譯apk效果如下:
可以發現這裡無法顯式的展示出string字串值,但是這裡出現了string字串的ID值,但是需要說明的是惡意攻擊是可以通過string的ID之在R.java檔案中尋找到相應的string名稱,進而在string.xml中找到字串值。但是這樣也是增加了反編譯的難度,相對來說也是一個比較不錯的選擇。
產品中儲存靜態秘鑰實踐:
最終經過比對各種靜態秘鑰儲存方案,我們決定使用gradle配置 + 靜態代碼 + 字串運算 + string.xml值的方式實現靜態秘鑰的儲存。
首先將靜態秘鑰分為四部分:
第一部分通過gradle配置的方式儲存;
第二部分通過java硬式編碼方式儲存;
第三部分通過java字串拼接運算的方式儲存;
第四部分通過string.xml儲存;
擷取appKey第一部分字串:
通過gradle配置的方式我們上面已經做了介紹,其就是在mudle中的gradle檔案中再起buildType節點下定義字串變數,這裡需要注意的是若是有正式環境和測試環境之分,需要分別定義字串變數,這樣我們就可以通過BuildConfig擷取appKay第一部分的字串了。
1 /**2 * 擷取AppKey part13 */4 public static String getBK1() {5 return BuildConfig.appKey;6 }
擷取appKey第二部分字串:
第二部分的appKay字串是通過運算的出來的,這裡的運算可以是任意運算方式,越複雜越好,越讓人看不懂越好,當然結果需要時唯一的。比如:
1 public static StringBuffer getBk2() {2 StringBuffer sb = new StringBuffer();3 sb.append(Config.getGBS(2, 5));4 return sb;5 }
而這裡的getGBS方法的實現:
1 public static int getGBS(int x, int y){2 for(int i = 1; i<= x * y; i++){3 if(i % x == 0 && i % y == 0)4 return i;5 }6 7 return x * y;8 }
最終的結果返回是:10,當然了不同的字串需要不同的演算法;
擷取appKey第三部分字串:
這裡就只是使用了簡單的字串寫入程式碼
1 public static String getBK3() {2 return "xhxh";3 }
擷取appKey第四部分字串
- 在string.xml中定義appKey的第四部分
<string name="bk4">chs</string>
1 public static String getBk4() {2 mContext().getResources().getString(R.string.bk4);3 }
擷取最終的appKey字串:
1 /**2 * 擷取最終的appKey字串3 */4 public static byte[] getDefaultKey() {5 StringBuffer sb = new StringBuffer();6 sb.append(getBK1()).append(getBK2()).append(getBk3()).append(getBk4());7 8 return sb.toString();9 }
這樣經過一系列的操作之後我們就擷取到了最終的靜態秘鑰。當然了產品中最好實現了代碼混淆的功能,這樣也能增大逆向的難度。
總結:
在App端儲存靜態秘鑰可以通過SharedPreferences、java寫入程式碼,ndk中的so檔案,檔案,資料庫,gradle配置的方式實現;
為了保證秘鑰的安全性可以採用多種方式混合,這樣可以增加惡意反編譯的難度;
在App端儲存秘鑰不能真正的保證秘鑰的安全性,只能增加反編譯的難易程度;
可以使用gradle配置的方式配置靜態秘鑰,使用string.xml配置秘鑰增加逆向反編譯的難度;
不推薦使用檔案,資料庫的方式儲存靜態秘鑰,容易被使用者惡意刪除,而出現不可預知的錯誤;
另外對產品研發技術,技巧,實踐方面感興趣的同學可以參考我的:
Android產品研發(十二)–>App長串連實現
Android產品研發(十三)–>App輪訓操作
Android產品研發(十四)–>App升級與更新
Android產品研發(十五)–>記憶體對象序列化
Android產品研發(十六)–>開發人員選項
Android產品研發(十七)–>Hybrid開發
Android產品研發(十八)–>webview問題集錦
Android產品研發(十九)–>Android studio中的單元測試
Android產品研發(二十)–>代碼Review
Android產品研發(二十一)–>Android中的UI最佳化
Android產品研發(二十二)–>Android實用調試技巧
轉自:http://blog.csdn.net/qq_23547831/article/details/51953926
Android中儲存靜態秘鑰實踐(轉)