本文我们将讲解一个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中我们不单可以引用依赖包,执行脚本还可以配置静态变量:

 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产品研发(十二)–>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中保存静态秘钥实践(转)的更多相关文章

  1. Android中实现静态的默认安装和卸载应用

    近期好长时间都没有写blog了,主要是由于近期工作上的事以及下载Android源代码的事耽误的(下载源代码这件事会在兴许的blog中写道.这个真的非常有意义呀~~),那么今天来写点什么呢?基本的灵感来 ...

  2. Kotlin 第二弹:Android 中 PDF 创建与渲染实践

    这是 Kotlin 练习的的第二篇.这一篇的由来是因为刚刚在 Android 开发者官网查看 API 的时候,偶然看到了角落里面的 pdf 相关. 我仔细看看了详细文档,发现这个还蛮有意思的,关键是编 ...

  3. 关于Android中Fragment静态和动态加载的方法

    一.静态加载 1.首先创建一个layout布局fragment.xml,里面放要显示和操作的控件 2.创建一个layout布局main1.xml,用来实现页面的跳转(跳转为要实现静态加载的界面) 3. ...

  4. android中保存Bitmap图片到指定文件夹中的方法

    /** 保存方法 */  public void saveBitmap() {   Log.e(TAG, "保存图片");   File f = new File("/s ...

  5. Android 中保存全局变量

    作者:silence鼬.2015年6月28日 在学习Android的时候一直未context的问题认为头疼,由于想做一些工具类,可是又不是四大组件.总要来回传递context.认为非常麻烦,就想全局获 ...

  6. Android 中保存数据到文件中

    1.在安卓开发中,会遇到保存数据到手机中以及从手机中获取数据的情况 /** * 把数据存放到手机内存中 * * @param number * @param password * @return */ ...

  7. android中保存一个ArrayList到SharedPreferences的方法

    保存: public static boolean saveArray() { SharedPrefernces sp=SharedPrefernces.getDefaultSharedPrefern ...

  8. 在JAVA中生成RSA秘钥对实现SSH互信

    https://blog.csdn.net/u014196729/article/details/51496262 https://blog.csdn.net/u013066244/article/d ...

  9. android中使用jni对字符串加解密实现分析

    android中使用jni对字符串加解密实现分析 近期项目有个需求.就是要对用户的敏感信息进行加密处理,比方用户的账户password,手机号等私密信息.在java中,就对字符串的加解密我们能够使用A ...

随机推荐

  1. 如何设置Android手机的sqlite3命令环境

    1.在PC环境下启动一个模拟器(不是手机) 2.查看模拟器 /systen/xbin是否有sqlite3命令 adb shell cd /system/xbin ls 3.把模拟器 /system/x ...

  2. TPS70345 (ACTIVE) 双路输出低压降 (LDO) 稳压器

    The TPS703xx family of devices is designed to provide a complete power management solution for TI DS ...

  3. list C++实现

    模仿STL中list,实现了其大部分功能.list可以高效地利用内存资源,常数时间的插入删除操作.并且,list除了erase外,不怎么存在迭代器失效的现象. #include<iostream ...

  4. C++中模板单例的跨SO(DLL)问题:RTTI,typeid,static,单例

    (转载请注明原创于潘多拉盒子) C++的模板可以帮助我们编写适合不同类型的模板类,给代码的复用性提供了极大的方便.近来写了一个涉及单例的C++模板类,简化下来可以归结为以下的代码: template ...

  5. Android之等比例显示图片

    在android中,由于密度的影响,如果想得到图片的宽高是不行的,具体为什么我就大概说一下,具体的请搜索度娘或者古哥吧. 原因是如果你把图片放在drawable-mdpi里,而手机是属于drawabl ...

  6. Android之Bean属性通知类

    调用: import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import android. ...

  7. Mysql中与时间相关的统计分析

    最近项目需要统计一段日期范围内,根据每分钟.几分钟.每天分别统计汇总某些事件/指标的发生总次数,平均发生次数,因此总结了Mysql中与时间处理.统计相关的资料. 按分钟统计某一时间段内的数据 SELE ...

  8. OpenCV学习(40) 人脸识别(4)

    在人脸识别模式类中,还实现了一种基于LBP直方图的人脸识别方法.LBP图的原理参照:http://www.cnblogs.com/mikewolf2002/p/3438698.html       在 ...

  9. go语言基础之map赋值、遍历、删除 、做函数参数

    1.map赋值 示例: package main //必须有个main包 import "fmt" func main() { m1 := map[int]string{1: &q ...

  10. spring cloud 报错Error creating bean with name 'hystrixCommandAspect' ,解决方案

    spring cloud 升级到最新版 后,报错: org.springframework.beans.factory.BeanCreationException: Error creating be ...