App安全性保障方案
之前晋级考核的时候认识到自己在app安全领域存在薄弱环节,所以这段时间研究了Android应用的安全防护,结合公司的项目特点做出记录和总结;主要包扩两个方面:http接口安全和App防逆向破解
一 Http接口安全
Http接口安全是为了保证客户端与服务端进行数据交互时的数据安全,防止数据库被爬取及数据被篡改
动态token
token是一个与用户身份关联的字符串,作为客户端与服务端交互时的令牌,可用于唯一确定客户端用户身份.
基于token的身份验证方法一般是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
token一般有个有效期,当token失效时,客户端会使用获取token的接口来更新token,这样能够保证客户端的token有效且是动态的,第三方截取到旧的token也无法发起有效请求,并且刷新token这个过程对于用户是无感知的.
签名验证
如果不对请求做签名验证,则可以简单通过抓包工具拿到请求参数,篡改后提交且大规模调用,导致我们的核心业务数据被篡改且系统资源被大量消耗.签名验证也是第三方开放平台确保接口安全的标准做法:
-
微信支付
-
支付宝支付
-
春雨医生开放平台
签名算法:signStr=signature(params&timetamp&SIGN_KEY)
- 客户端和服务端均保存有相同的key
- 客户端发起请求前,参数+时间戳+key使用签名算法生成签名
- 客户端发起请求,携带 参数+时间戳+signStr
- 服务端验证时间戳是否在有效期内(如2分钟)
- 根据同样的签名算法计算签名并验证是否一致
签名验证解决的问题:
- 避免请求数据被篡改;如果第三方修改其中任意参数,则会导致签名不同,后台验证后则返回调用失败
- 防止重放攻击,避免数据库被爬取;因为有时间戳验证,同一个签名在短时间内就会无效,服务器直接拦截掉了过期的请求
注意点:
- key的安全性很重要,建议采用服务端动态下发的方式(下发如何确保安全,也需要更多考虑),且事先约定好key的更新机制(用于key泄露时,服务端重新下发)
- 客户端时间戳的准确性如何确保?建议每次客户端打开时计算与服务端的差值,用于确保时间戳的一致性
对返回结果加密传输
因为我们的请求会被抓包工具轻易的抓取,所以请求返回的数据需要加密处理
- 客户端正常发起请求
- 服务端进行业务处理,对response加密
- 客户端对response解密
解决的问题:
- 对Response加密之后,即使第三方抓取到了我们的数据,也无法解密,保证了我们的数据安全
注意:
- 这里需要使用可逆算法,因为需要加解密,如AES、RSA等;
- 同样我们的秘钥也需要确保安全,且事先约定好更新机制,用于应对秘钥泄露;
- 可以对整个response加密,也可以只对真正的业务数据部分加密,可以跟业务特点自行选择;
- 因为存在一些接口是不能加密的(如初始化接口等),可以增加一个是否加密字段,便于客户端兼容所有接口
在Android下,可以这样简单实现
数据结构
public class CodeEntity {
public int code;
public String msg;
//业务数据
public Object data;
//当前是否需要解密,默认不需要
public boolean encrypted = false;
}
CommonGsonResponseBodyConverter对返回结果解密
public class CommonGsonResponseBodyConverter implements Converter {
...
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
...
/**
* AES解密
* 如果该接口有加密,则先解密
*/
if (entity.encrypted) {
response = AES.getInstance().decode("key",response);
}
...
}
}
AES加解密:
public class AES implements IDecode, IEncode {
private static final String CBC_PKCS5_PADDING = "AES/ECB/PKCS5Padding";//AES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
private static final String AES = "AES";//AES 加密
private static final Charset CHARSET = Charset.forName("UTF-8");
private volatile static AES sAES = null;
private AES() {
}
public static AES getInstance() {
if (sAES == null)
synchronized (AES.class) {
if (sAES == null)
sAES = new AES();
}
return sAES;
}
@Override
public String encode(String key, String plaintext) {
try {
byte[] raw = key.getBytes(CHARSET);
SecretKeySpec spec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, spec);
//解密
byte[] input = cipher.doFinal(plaintext.getBytes(CHARSET));
//Base64转码
return Base64.encodeToString(input, Base64.DEFAULT);
} catch (Exception e) {
Logger.e("加密出错,直接返回明文==>明文为:" + plaintext);
return plaintext;
}
}
@Override
public String decode(String key, String ciphetext) {
try {
byte[] raw = key.getBytes(CHARSET);
SecretKeySpec spec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, spec);
//将密文先使用Base64解码
byte[] decoder = Base64.decode(ciphetext.getBytes(CHARSET), Base64.DEFAULT);
//解密
byte[] bytes = cipher.doFinal(decoder);
return new String(bytes, CHARSET);
} catch (Exception e) {
Logger.e("解密出错,认为无需解密==>密文为:" + ciphetext);
return ciphetext;
}
}
}
二 App防逆向破解
逆向工程一直都是App安全的潜在威胁,如果App没有采取防逆向措施,则你的App能够被轻易破解,包括源代码,资源及本地数据;App逆向工程和反逆向一直都是与时俱进的,这里总结App开发中常用的反逆向方案,往往都是多重方案结合起来使用,以确保App安全
代码混淆
这是最基本的防护方法,也是使用最广泛最成熟的,一般App都会使用;开启混淆后,源代码中的类名、方法名、变量名会变成随机字母,使代码难以阅读但却不影响正常运行,这样App被反编译后代码逻辑也不会暴露,另外混淆还有减少包体积的作用
资源混淆
代码混淆只混淆了我们的代码,apk中的资源在解压之后一目了然,这样导致布局、样式、图片资源等完全处于暴露状态,第三方通过简单的操作就可以拿到,所以资源文件也采取混淆操作.
推荐使用腾讯开源的AndResGuard项目进行资源压缩,不仅可以最大程度保护我们的资源安全,且可以减小APK体积
加固加壳
我们在加固的过程中需要三个对象:
1、需要加密的Apk(源Apk)
2、壳程序Apk(负责解密Apk工作)
3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)
主要步骤:
我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。
Apk加固之后是比较安全的(相对),现在市场是有很多加固平台,可自行选择使用
本地数据安全
App有一些数据是存在设备中的,包括缓存、配置、数据库等,如果这些数据不加密,则会存在泄漏的风险,所以对于本地数据根据数据的重要性来决定是否需要加密存储
- 文件尽量存放在Internal Storage而不是External Storage,该目录下其他用户无法查看(root可查看)
- 数据库采用加密方案,例如Reaml数据可以便捷的加解密,基于Sqlite的数据库也有很多开源加密方案,可自行选择
- 采用通用加密方案,对sp、file、数据库均支持加解密,例如FaceBook的开源项目concel
- 秘钥尽量不要在本地保存(更不要直接写在代码中),如果一定要保存,需要多层加密,拆分保存
才疏学浅,还请大家及时指出博客中的问题,不慎感激
关于作者
- 简 书:uncochen
- github:ChenZhen