本文我们将讲解一个 Android 产品研发中可能会碰到的一个问题:如何在 App 中保存静态秘钥以及保证其安全性.许多的移动 app 需要在 app 端保存一些静态字符串常量,其可能是静态秘钥,第三方 appId 等.在保存这些字符串常量的时候就涉及到了如何保证秘钥的安全性问题.如何保证在 App 中静态秘钥唯一且正确安全,这是一个很重要的问题,公司的产品中就存在着静态字符串常量类型的秘钥,所以一个明显的问题就是如何生成秘钥,保证秘钥的安全性?
现今保存静态秘钥的几种主流通用做法:(参考: Android 安全开发之浅谈密钥硬编码 )
通过 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 中我们不单可以引用依赖包,执行脚本还可以配置静态变量:
如上面代码所示我们在 gradle 中配置了一个名称为 appKey 的字符串变量,编译 gradle 则会在 gradle 的编译类:BuildConfig 中生成该静态变量:
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 的方式配置静态秘钥反编译的时候也是找到秘钥的,但是增加了逆向的难度,而且避免了被用户的恶意删除,所以通过 gradle 配置字符串的方式保存 app 中的静态秘钥是一个不错的选择.
/**
* 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";
}
通过 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 第二部分字符串:
/**
* 获取AppKey part1
*/
public static String getBK1() {
return BuildConfig.appKey;
}
第二部分的 appKay 字符串是通过运算的出来的,这里的运算可以是任意运算方式,越复杂越好,越让人看不懂越好,当然结果需要时唯一的.比如:
而这里的 getGBS 方法的实现:
public static StringBuffer getBk2() {
StringBuffer sb = new StringBuffer();
sb.append(Config.getGBS(2, 5));
return sb;
}
最终的结果返回是:10,当然了不同的字符串需要不同的算法;
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;
}
获取 appKey 第三部分字符串:
这里就只是使用了简单的字符串硬编码
获取 appKey 第四部分字符串
public static String getBK3() {
return "xhxh";
}
在 string.xml 中定义 appKey 的第四部分
<string name="bk4">chs</string>
在代码中获取 string 中定义的字符串值
获取最终的 appKey 字符串:
public static String getBk4() {
mContext().getResources().getString(R.string.bk4);
}
这样经过一系列的操作之后我们就获取到了最终的静态秘钥.当然了产品中最好实现了代码混淆的功能,这样也能增大逆向的难度.
/**
* 获取最终的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 产品研发(十二)->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
来源: http://www.bubuko.com/infodetail-2462249.html