Android数据本地安全存储

前言

  在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,这是一种比较好的解决办法,后期也会基于这个思路实现一套客户端和服务端的加解密组件。
生活从不缺少美,只是缺少发现美的眼睛。只要静下心里去思考问题,任何困难都会有解决办法的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值