上一篇文章中我們講解了Android中的實用調試技巧。講解了Android中的原生Log API以及其使用方式,講解了自定義日志API、使用方式和實現原理,講解了通過gradle配置日志框架在正式環境中屏蔽日志信息等。最后我們還重點講解了Android studio中的斷點調試技巧,主要包括:斷點調試功能、日志斷點、求值調試、異常斷點、方法斷點等。更多關于Android中實用調試技巧的知識,可以參考我的:[Android產品研發(二十二)–>Android實用調試技巧](http://blog.csdn.net/qq_23547831/article/details/51868496)
本文我們將講解一個Android產品研發中可能會碰到的一個問題:如何在App中保存靜態秘鑰以及保證其安全性。許多的移動app需要在app端保存一些靜態字符串常量,其可能是靜態秘鑰、第三方appId等。在保存這些字符串常量的時候就涉及到了如何保證秘鑰的安全性問題。如何保證在App中靜態秘鑰唯一且正確安全,這是一個很重要的問題,公司的產品中就存在著靜態字符串常量類型的秘鑰,所以一個明顯的問題就是如何生成秘鑰,保證秘鑰的安全性?
**現今保存靜態秘鑰的幾種主流通用做法**:(參考:[Android安全開發之淺談密鑰硬編碼](http://www.cnblogs.com/alisecurity/archive/2016/05/16/5498539.html))
* 通過SharedPreferences保存靜態秘鑰;
* 通過java硬編碼的方式保存
* 通過NDK的方式,將靜態秘鑰保存在so文件中;
**幾種保存靜態秘鑰方式的優劣勢**:
* 密鑰直接明文存在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中我們不單可以引用依賴包,執行腳本還可以配置靜態變量:
~~~
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中的靜態秘鑰是一個不錯的選擇。
**通過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第一部分的字符串了。
~~~
/**
* 獲取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第四部分字符串**
- 在string.xml中定義appKey的第四部分
~~~
<string name="bk4">chs</string>
~~~
- 在代碼中獲取string中定義的字符串值
~~~
public static String getBk4() {
mContext().getResources().getString(R.string.bk4);
}
~~~
獲取最終的appKey字符串:
~~~
/**
* 獲取最終的appKey字符串
*/
public static byte[] getDefaultKey() {
StringBuffer sb = new StringBuffer();
sb.append(getBK1()).append(getBK2()).append(getBk3()).append(getBk4());
return sb.toString();
}
~~~
這樣經過一系列的操作之后我們就獲取到了最終的靜態秘鑰。當然了產品中最好實現了代碼混淆的功能,這樣也能增大逆向的難度。
**總結**:
* 在App端保存靜態秘鑰可以通過SharedPreferences、java硬編碼,ndk中的so文件,文件,數據庫,gradle配置的方式實現;
* 為了保證秘鑰的安全性可以采用多種方式混合,這樣可以增加惡意反編譯的難度;
* 在App端保存秘鑰不能真正的保證秘鑰的安全性,只能增加反編譯的難易程度;
* 可以使用gradle配置的方式配置靜態秘鑰,使用string.xml配置秘鑰增加逆向反編譯的難度;
* 不推薦使用文件,數據庫的方式保存靜態秘鑰,容易被用戶惡意刪除,而出現不可預知的錯誤;
另外對產品研發技術,技巧,實踐方面感興趣的同學可以參考我的:
[Android產品研發(十一)–>應用內跳轉scheme協議](http://blog.csdn.net/qq_23547831/article/details/51685310)
[Android產品研發(十二)–>App長連接實現](http://blog.csdn.net/qq_23547831/article/details/51719389)
[Android產品研發(十三)–>App輪詢操作](http://blog.csdn.net/qq_23547831/article/details/51764773)
[Android產品研發(十四)–>App升級與更新](http://blog.csdn.net/qq_23547831/article/details/51764773)
[Android產品研發(十五)–>內存對象序列化](http://blog.csdn.net/qq_23547831/article/details/51779528)
[Android產品研發(十六)–>開發者選項](http://blog.csdn.net/qq_23547831/article/details/51809497)
[Android產品研發(十七)–>hybrid開發](http://blog.csdn.net/qq_23547831/article/details/51812985)
[Android產品研發(十八)–>webview問題集錦](http://blog.csdn.net/qq_23547831/article/details/51820139)
[Android產品研發(十九)–>Android studio中的單元測試](http://blog.csdn.net/qq_23547831/article/details/51868451)
[Android產品研發(二十)–>代碼Review](http://blog.csdn.net/qq_23547831/article/details/51833080)
[Android產品研發(二十一)–>Android中的UI優化](http://blog.csdn.net/qq_23547831/article/details/51868453)
[Android產品研發(二十二)–>Android實用調試技巧](http://blog.csdn.net/qq_23547831/article/details/51868496)
- 前言
- (一)–>實用開發規范
- (二)-->啟動頁優化
- (三)-->基類Activity
- (四)-->減小Apk大小
- (五)-->多渠道打包
- (六)-->Apk混淆
- (七)-->Apk熱修復
- (八)-->App數據統計
- (九)-->App網絡數據解析
- (十)-->盡量不使用靜態變量保存數據
- (十一)-->應用內跳轉Scheme協議
- (十二)-->App長連接實現
- (十三)-->App輪詢操作
- (十四)-->App升級與更新
- (十五)-->內存對象序列化
- (十六)-->開發者選項
- (十七)-->Hybrid開發
- (十八)-->webview問題集錦
- (十九)-->Android studio中的單元測試
- (二十)-->代碼Review
- (二十一)-->Android中的UI優化
- (二十二)-->Android實用調試技巧
- (二十三)-->Android中保存靜態秘鑰實踐
- (二十四)-->內存泄露場景與檢測
- (二十五)-->MVC/MVVM/MVP簡單理解