安全-Android敏感数据存储加密

图片源于网络.png

开发App的同时肯定会涉及到敏感数据存储,例如:token,身份证,用户名,密码等。
这些信息如果明文存储在本地是非常危险的,手机被Root之后无论你存储在什么地方都会被看到。
本篇文章介绍我自己写的一个开源框架 StorageEncrypt,对存储在本地的数据进行AES256加密。
目前只实现了SharedPreferences和序列化对象存储的加密,未来还会根据业务进行更新。
GitHub地址:请点击这里

目录.png

AES加密简介

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
AES分为AES128、AES192、AES256秘钥长度分别是128bit、192bit、256bit三种。
本篇文章用的是计算量最大且最安全的AES256,AES256比128大概需要多花40%的时间,用于多出的4轮round key生成以及对应的SPN操作。另外,产生256-bit的密钥可能也需要比128位密钥多些开销,不过这部分开销应该可以忽略。

这些概念性的东西我就不在这里复制粘贴了,想要了解的请自行Google把。
下面我就介绍重头戏,我写的开源库StorageEncrypt

StorageEncrypt简介

这个库主要的功能:

  1. 实现SharedPreferences接口对保存的keyvalue都进行AES256加密
  2. 序列化自定义的JavaBean将用注释EncryptString标注的字段进行AES256加密

1. AES256SharedPreferences 和 AES256Editor

AES256SharedPreferences和AES256Editor分别实现了SharedPreferences和Editor接口,其实就是SharedPreferences和Editor的包装器类。
如下代码就可以看出是包装器:

public class AES256SharedPreferences implements SharedPreferences {
    private SharedPreferences mSharedPreferences;
    public AES256SharedPreferences(SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }
    ......
}

public static class AES256Editor implements Editor {
        private Editor mEditor;
        public AES256Editor(Editor editor) {
            this.mEditor = editor;
        }
        ......
}
AES256Editor

AES256Editor类中的每个方法都会对keyvalue进行AES256加密然后进行保存到本地。
其中基本类型,intlongfloatboolean等也是可以加密的,只不过我将他们转换成String类型进行加密存储。
如下示例代码:

public static class AES256Editor implements Editor {
        private Editor mEditor;
        public AES256Editor(Editor editor) {
            this.mEditor = editor;
        }
        @Override
        public Editor putString(String key, @Nullable String value) {
            String aesKey = AES256Encrypt(key);
            String aesValue = AES256Encrypt(value);
            return mEditor.putString(aesKey, aesValue);
        }
        ......
        @Override
        public Editor putFloat(String key, float value) {
            //将float型转换成字符串进行加密
            String aesKey = AES256Encrypt(key);
            String aesValue = AES256Encrypt(String.valueOf(value));
            return mEditor.putString(aesKey, aesValue);
        }
        @Override
        public Editor putBoolean(String key, boolean value) {
            //将boolean型转换成字符串进行加密
            String aesKey = AES256Encrypt(key);
            String aesValue = AES256Encrypt(String.valueOf(value));
            return mEditor.putString(aesKey, aesValue);
        }
        ......
    }
AES256SharedPreferences

AES256SharedPreferences类中的每个方法首先对key进行加密,然后通过加密后的key获取本地的value,然后对value进行解密返回。
其中基本类型,intlongfloatboolean等也是可以解密的。
如下示例代码:

public class AES256SharedPreferences implements SharedPreferences {
    private SharedPreferences mSharedPreferences;
    public AES256SharedPreferences(SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }
   ......
    @Nullable
    @Override
    public String getString(String key, @Nullable String defValue) {
        String aesKey = AES256Encrypt(key);
        String value = mSharedPreferences.getString(aesKey, defValue);
        if(value.equals(defValue)){
            return defValue;
        }
        return AES256Decrypt(value);
    }
    ......
    @Override
    public float getFloat(String key, float defValue) {
        String aesKey = AES256Encrypt(key);
        String value = mSharedPreferences.getString(aesKey, String.valueOf(defValue));
        if(value.equals(String.valueOf(defValue))){
            return defValue;
        }
        return Float.parseFloat(AES256Decrypt(value));
    }
    @Override
    public boolean getBoolean(String key, boolean defValue) {
        String aesKey = AES256Encrypt(key);
        String value = mSharedPreferences.getString(aesKey, String.valueOf(defValue));
        if(value.equals(String.valueOf(defValue))){
            return defValue;
        }
        return Boolean.parseBoolean(AES256Decrypt(value));
    }
    ......
}
测试代码
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private AES256SharedPreferences mAES256SharedPreferences;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mAES256SharedPreferences = new AES256SharedPreferences(getSharedPreferences("test", Context.MODE_PRIVATE));
        String key = "key_SharedPreferences";
        String value = "value_SharedPreferences";
        mAES256SharedPreferences.edit().putString(key,value).commit();
        String getValue = mAES256SharedPreferences.getString(key,"");
        Log.e(TAG,"getValue : " + getValue);
    }
}
打印信息:
02-01 23:10:20.976 4156-4156/? E/MainActivity: getValue : value_SharedPreferences

如上代码我们发现putStringgetString之后的value都是value_SharedPreferences,如果系统目录中保存的是加密过的那么证明这个代码是没问题的,如下代码:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="ViJcid0p0zXvBc3hLvlSF3hOBMh/bP6dXgS/cfA6XjA=">6XwxdVncAcl5oa3Ja8JCkyqPVjaEDchSSJzVy9pg5c0=</string>
</map>

这段代码是系统目录中AES256SharedPreferences保存的xml文件,我们发现keyvalue都是加密过的。

2. AES256SerializableObject

这个类是序列化对象的工具类,他可以将自定义的JavaBean中需要加密的字段进行AES256加密之后序列化到本地。

  1. 可以用注解EncryptString来标注String字段需要加密。
  2. 如果这个类中的字段是自定义的JavaBean,我们需要将这个JavaBean中的String字段进行加密,需要用EncryptPojo对自定义的JavaBean字段进行标注,然后用EncryptString来标注String字段,用语言表述太拗口了难以理解,看下面代码。

用代码进行讲解如下:

public class User implements Serializable {
    //表示序列化时需要将id进行AES256加密
    @EncryptString
    private String id;
    private String name;
    private String age;
    //表示自定义的Login类中有需要序列化时进行加密的字段
    @EncryptPojo
    private Login login;
    ......
}
public class Login implements Serializable {
    private String id;
    //这个字段序列化时需要加密
    @EncryptString
    private String token;
    @EncryptPojo
    private Member member;
    ......
}
public class Member implements Serializable {
    private String name;
    @EncryptString
    private String id;
    private String age;
    ......
}
测试代码
User user = new User();
user.setId("1234567890");
user.setName("jiahongfei");
user.setAge("20");

Login login = new Login();
login.setId("29292929");
login.setToken("token_5050505");

Member member = new Member();
member.setId("10101010");
member.setAge("25");
member.setName("gaogaogao");
login.setMember(member);
user.setLogin(login);

long start = System.currentTimeMillis();
                AES256SerializableObject.saveObject(MainActivity.this,user,"User");
Log.e(TAG, "AES save User : " + user.toString());
Log.e(TAG, "加密保存时间 : " + (System.currentTimeMillis()-start) + "毫秒");

start = System.currentTimeMillis();
user = (User) AES256SerializableObject.readObject(MainActivity.this,"User");
Log.e(TAG, "AES read User : " + user.toString());
Log.e(TAG, "解密读取时间 : " + (System.currentTimeMillis()-start) + "毫秒");

start = System.currentTimeMillis();
user = (User) SerializableObject.readObject(MainActivity.this,"User");
Log.e(TAG, "Ser read User : " + user.toString());
Log.e(TAG, "普通读取时间 : " + (System.currentTimeMillis()-start) + "毫秒");
//打印日志
02-01 21:12:07.177 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: AES save User : User{id='1234567890', name='jiahongfei', age='20', login=Login{id='29292929', token='token_5050505', member=Member{name='gaogaogao', id='10101010', age='25'}}}
02-01 21:12:07.177 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: 加密保存时间 : 6毫秒
02-01 21:12:07.178 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: AES read User : User{id='1234567890', name='jiahongfei', age='20', login=Login{id='29292929', token='token_5050505', member=Member{name='gaogaogao', id='10101010', age='25'}}}
02-01 21:12:07.178 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: 解密读取时间 : 1毫秒
02-01 21:12:07.179 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: Ser read User : User{id='YZ/uTPj54LnoewKLYMNeLg==', name='jiahongfei', age='20', login=Login{id='29292929', token='vu9wwwDUCMTJ8g9u2KIQ5A==', member=Member{name='gaogaogao', id='5M0yOju1oB28J5KiUVjfug==', age='25'}}}
02-01 21:12:07.179 9009-9009/com.andrjhf.storage.encrypt.demo E/MainActivity: 普通读取时间 : 1毫秒

我们看如上测试代码和打印日志,可以证明用EncryptString注释的字段都被AES256加密之后序列化到本地了。

总结:

目前对称加密方式AES、DES等都是公开的算法,加密最重要的就是秘钥的生成以及秘钥存储的位置,这才是根本,关于这点不在我们本篇文章的讨论范围后续在介绍。

StorageEncrypt github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 摘要:本文是一例一般心理问题的咨询案例报告。结合求助者的相关资料和有关的心理测试结果,从生物原因、社会原因、心理原...
    蓝果东阅读 947评论 1 1
  • 父亲虽然瘦小但身体健康,我以为他应该一直是这样的。 直到上个星期四中午姐姐打电话轻描淡写地说了句“爸爸说,他...
    水木的碎碎念阅读 633评论 0 0
  • 2018.1.16 意外的国内单 1.16 平淡无奇的周二。熟悉我的客户都知道我是只做国际线的,国内基本不碰,一来...
    陆娴1983阅读 161评论 0 0