前言
在Android都开发中,通常我们都会在本地存储一些数据,如果我们不对这些数据进行加密存储,很容易将一些敏感数据暴露给黑客,从而给我们的产品带来一些影响。
这里使用AES算法来加解密数据,在使用AES算法中,最主要的就是key的生成,如果我们直接硬编码在程序,程序被反编译后也很容易看到。那么如果保证key的安全以及在不同的手机上使用不同的key呢?这篇文章结合项目中的使用经验,分享key的安全生成并提供部分参考代码。
一、MD5介绍
MD5摘要算法广泛的应用中我们的程序当中,如用户密码的存储,文件来源的安全校验等。具体的介绍可以参考MD5百度百科。
任何数据经过MD5后产生的16位的byte,并且同一数据经过MD5后的内容不会变,这是后期key生成的保障。
private byte[] md5(String data) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(data.getBytes());
return messageDigest.digest();
}
二、位运算和生成key
为了增加破解key的难度,我们对md5后的数据做位运算,代码如下:
public class HashUtils {
private static final char[] TARCODE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' };
public static String getHash(String data, String type) {
String str = "";
if (data != null) {
try {
MessageDigest messageDigest = MessageDigest.getInstance(type);
messageDigest.update(data.getBytes());
str = handleBytes(messageDigest.digest());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
return str;
}
//生成key,这一步是重点
private static String handleBytes(byte[] data) {
int i = data.length;
StringBuilder sb = new StringBuilder(i * 2);
for (int j = 0; j < i; j++) {
//对每个字节位运算并且和16进制62做&运算,保证产生的索引在TARCODE的内
sb.append(TARCODE[(data[j] >> 4 & 0x3D)]);
sb.append(TARCODE[(data[j] & 0x3D)]);
}
return sb.toString();
}
}
有了上面key的以后,接下来使用AES算法进行加解密。
三、AES加解密代码
public class SafeAESTool {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
public static String encrypt(String key,String data){
// TODO Auto-generated method stub
String str = null;
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] ret = cipher.doFinal(data.getBytes());
str = Base64.encodeToString(ret, Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
public static String decrypt(String key,String data){
// TODO Auto-generated method stub
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), ALGORITHM);
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] retByte = cipher.doFinal(Base64.decode(data,Base64.NO_WRAP));
return new String(retByte);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
四、封装工具类
采用获取设备的IMEI号和应用的包名作为生产key的标识,主要是保证能够获取IMEI的情况下每个手机的AES Key都不一样,从而保证存储的安全。
public class SafeStorageUtil {
private static String AESKEY;
/**
* 获得应用程序包名
* @param context
* @return
*/
private static String getPackageName(Context context) {
return context.getPackageName();
}
/**
* 设备IMEI号
* @param mContext
* @return
*/
private static String getDeviceID(Context mContext) {
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
return tm.getDeviceId();
}
/**
* 获取唯一标识
* @param mContext
* @return
*/
private static String getAppUnique(Context mContext){
return getDeviceID(mContext)+getPackageName(mContext);
}
private static String getAESKey(Context context) {
if (!TextUtils.isEmpty(AESKEY))return AESKEY;
//取16位作为密钥key
AESKEY = HashUtils.getHash(getAppUnique(context), "MD5").substring(0, 16);
return AESKEY;
}
public static String encrypt(Context context,String data){
return SafeAESTool.encrypt(getAESKey(context), data);
}
public static String decrypt(Context context,String data){
return SafeAESTool.decrypt(getAESKey(context), data);
}
}
五、测试-使用SharedPreference存取数据
private static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences("test.properties", 0);
}
/**
* 安全存储数据
* @param context
* @param key
* @param value
*/
public static void saveData(Context context,String key,String value){
if(TextUtils.isEmpty(value))return;
SharedPreferences sp = getSharedPreferences(context);
Editor editor = sp.edit();
editor.putString(key, SafeStorageUtil.encrypt(context, value));
editor.commit();
}
/**
* 获取数据
* @param context
* @param key
* @return
*/
public static String getData(Context context,String key){
SharedPreferences sp = getSharedPreferences(context);
String tem = sp.getString(key, "");
if(!TextUtils.isEmpty(tem)){
tem = SafeStorageUtil.decrypt(context, tem);
}
return tem;
}
6、小结
平时做加解密的时候都是直接把key硬编码在代码里面,一直担心这种写法不安全,但是没有找到合适的解决方案。最近了解了阿里聚安全的一些实现思路后才脑洞大开,结合MD5的特性去生成DES key,这是一种比较好的解决办法,后期也会基于这个思路实现一套客户端和服务端的加解密组件。
生活从不缺少美,只是缺少发现美的眼睛。只要静下心里去思考问题,任何困难都会有解决办法的。