本文我们将讲解一个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. Content portal for Pocketables Tasker articles

    http://www.pocketables.com/2013/03/overview-of-pocketables-tasker-articles.html I write a lot about ...

  2. XPROG-m编程器

    XPROG-m编程器是为取代较早版本的XPROG编程器而设计的. XPROG-m编程器硬件完全与XPROG编程器向上兼容,还具有其它许多功能. 该XPROG - M支持摩托罗拉68HC05,68HC0 ...

  3. kgtp linux内核调试

    作者:朱辉 开源网址:https://github.com/teawater http://teawater.github.io/kgtp/      有中文版说明 内核编绎: General set ...

  4. eclipse鼠标变成十字架

    不知道按到什么或者点到什么button了,在eclipse里面鼠标就变成了十字架形式.解决的方法是按:alt+shift+a 原来alt+shift+a是框选代码的.长见识了!

  5. 用swift开发仪表盘控件(一)

    苹果swift刚刚推出不久,接触到这个语言是一个偶然的机会,无聊之余随便看了下它的语法: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveW5tYW95b2 ...

  6. lol匹配算法

    这是Riot的Design Director Tom Cadwell专门为中国玩家写的解说匹配系统工作原理的帖子. 同一时候为了让大家更好的理解匹配系统,假设您认为您遇到了特别不公平的匹配,请回复游戏 ...

  7. How to Make Portable Class Libraries Work for You

    A Portable Class Library is a .NET library that can be used (in binary form, without recompiling) on ...

  8. iOS 调用短信、电话、邮件、浏览器等

    1.调用 自带mail[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://admin@hzl ...

  9. 如何开启解决android studio的模拟器的问题

    来自:http://jingyan.baidu.com/article/03b2f78c0a19e75ea237ae24.html 有的时候因为电脑系统或者是安装的一些问题我们可能需要对症下药的解决模 ...

  10. DevExpress Components16.2.6 Source Code 编译

    DevExpress 是一个比较有名的界面控件套件,提供了一系列优秀的界面控件.这篇文章将展示如何在拥有源代码的情况下,对 DevExpress 的程序集进行重新编译. 特别提示:重编译后,已安装好的 ...