【Android安全】自带加密光环的SharedPreference
来源:互联网 发布:淘宝卖家上货软件 编辑:程序博客网 时间:2024/06/10 07:34
项目地址:https://github.com/afinal/SecuritySharedPreference
前言
安全问题长久以来就是Android系统的一大弊病,很多人也因此舍弃Android选择了苹果,作为一个Android Developer,我们需要对用户的隐私负责,需要对用户的数据安全倾尽全力,想到这里,我就热血沸腾,仿佛自己化身正义的天使(我编不下去了。。。)。
概述
现在,我们回归正题,SharedPreference是我们比较常用的保存数据到本地的方式,我们习惯在SharedPreference中保存用户信息,用户偏好设置,以及记住用户名密码等等。但是,SharedPreference存在一些安全隐患,我们都知道,SharedPreference是以“键值对”的形式把数据保存在data/data/packageName/shared_prefs文件夹中的xml文件中。
在正常的情况下,我们没办法访问data/data目录,我们也没办法拿到xml中的文件。但是,我们root手机之后,通过命令行可以获得读写data/data目录的权限,我们SharedPreference中保存的数据也就很容易泄漏,造成不可挽回的损失。那么我们今天就从SharedPreference开刀,为APP的安全尽一份绵薄之力。
代码
SecuritySharedPreference.java
我们实现了SharedPreference接口和SharedPreference.Editor接口,重写了保存数据和取出数据的方法,我们在保存数据和取出数据的时候加了加解密层,这样可以保证我们在操作自定义的SharedPreference时候像调用原生的一样简单。
package com.domain.securitysharedpreference;import android.annotation.TargetApi;import android.content.Context;import android.content.SharedPreferences;import android.os.Build;import android.preference.PreferenceManager;import android.support.annotation.Nullable;import android.text.TextUtils;import android.util.Log;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;/** * 自动加密SharedPreference * Created by Max on 2016/11/23. */public class SecuritySharedPreference implements SharedPreferences { private SharedPreferences mSharedPreferences; private static final String TAG = SecuritySharedPreference.class.getName(); private Context mContext; /** * constructor * @param context should be ApplicationContext not activity * @param name file name * @param mode context mode */ public SecuritySharedPreference(Context context, String name, int mode){ mContext = context; if (TextUtils.isEmpty(name)){ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); } else { mSharedPreferences = context.getSharedPreferences(name, mode); } } @Override public Map<String, String> getAll() { final Map<String, ?> encryptMap = mSharedPreferences.getAll(); final Map<String, String> decryptMap = new HashMap<>(); for (Map.Entry<String, ?> entry : encryptMap.entrySet()){ Object cipherText = entry.getValue(); if (cipherText != null){ decryptMap.put(entry.getKey(), entry.getValue().toString()); } } return decryptMap; } /** * encrypt function * @return cipherText base64 */ private String encryptPreference(String plainText){ return EncryptUtil.getInstance(mContext).encrypt(plainText); } /** * decrypt function * @return plainText */ private String decryptPreference(String cipherText){ return EncryptUtil.getInstance(mContext).decrypt(cipherText); } @Nullable @Override public String getString(String key, String defValue) { final String encryptValue = mSharedPreferences.getString(encryptPreference(key), null); return encryptValue == null ? defValue : decryptPreference(encryptValue); } @Nullable @Override public Set<String> getStringSet(String key, Set<String> defValues) { final Set<String> encryptSet = mSharedPreferences.getStringSet(encryptPreference(key), null); if (encryptSet == null){ return defValues; } final Set<String> decryptSet = new HashSet<>(); for (String encryptValue : encryptSet){ decryptSet.add(decryptPreference(encryptValue)); } return decryptSet; } @Override public int getInt(String key, int defValue) { final String encryptValue = mSharedPreferences.getString(encryptPreference(key), null); if (encryptValue == null) { return defValue; } return Integer.parseInt(decryptPreference(encryptValue)); } @Override public long getLong(String key, long defValue) { final String encryptValue = mSharedPreferences.getString(encryptPreference(key), null); if (encryptValue == null) { return defValue; } return Long.parseLong(decryptPreference(encryptValue)); } @Override public float getFloat(String key, float defValue) { final String encryptValue = mSharedPreferences.getString(encryptPreference(key), null); if (encryptValue == null) { return defValue; } return Float.parseFloat(decryptPreference(encryptValue)); } @Override public boolean getBoolean(String key, boolean defValue) { final String encryptValue = mSharedPreferences.getString(encryptPreference(key), null); if (encryptValue == null) { return defValue; } return Boolean.parseBoolean(decryptPreference(encryptValue)); } @Override public boolean contains(String key) { return mSharedPreferences.contains(encryptPreference(key)); } @Override public SecurityEditor edit() { return new SecurityEditor(); } @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { mSharedPreferences.registerOnSharedPreferenceChangeListener(listener); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { mSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener); } /** * 处理加密过渡 */ public void handleTransition(){ Map<String, ?> oldMap = mSharedPreferences.getAll(); Map<String, String> newMap = new HashMap<>(); for (Map.Entry<String, ?> entry : oldMap.entrySet()){ Log.i(TAG, "key:"+entry.getKey()+", value:"+ entry.getValue()); newMap.put(encryptPreference(entry.getKey()), encryptPreference(entry.getValue().toString())); } Editor editor = mSharedPreferences.edit(); editor.clear().commit(); for (Map.Entry<String, String> entry : newMap.entrySet()){ editor.putString(entry.getKey(), entry.getValue()); } editor.commit(); } /** * 自动加密Editor */ final class SecurityEditor implements Editor { private Editor mEditor; /** * constructor */ private SecurityEditor(){ mEditor = mSharedPreferences.edit(); } @Override public Editor putString(String key, String value) { mEditor.putString(encryptPreference(key), encryptPreference(value)); return this; } @Override public Editor putStringSet(String key, Set<String> values) { final Set<String> encryptSet = new HashSet<>(); for (String value : values){ encryptSet.add(encryptPreference(value)); } mEditor.putStringSet(encryptPreference(key), encryptSet); return this; } @Override public Editor putInt(String key, int value) { mEditor.putString(encryptPreference(key), encryptPreference(Integer.toString(value))); return this; } @Override public Editor putLong(String key, long value) { mEditor.putString(encryptPreference(key), encryptPreference(Long.toString(value))); return this; } @Override public Editor putFloat(String key, float value) { mEditor.putString(encryptPreference(key), encryptPreference(Float.toString(value))); return this; } @Override public Editor putBoolean(String key, boolean value) { mEditor.putString(encryptPreference(key), encryptPreference(Boolean.toString(value))); return this; } @Override public Editor remove(String key) { mEditor.remove(encryptPreference(key)); return this; } /** * Mark in the editor to remove all values from the preferences. * @return this */ @Override public Editor clear() { mEditor.clear(); return this; } /** * 提交数据到本地 * @return Boolean 判断是否提交成功 */ @Override public boolean commit() { return mEditor.commit(); } /** * Unlike commit(), which writes its preferences out to persistent storage synchronously, * apply() commits its changes to the in-memory SharedPreferences immediately but starts * an asynchronous commit to disk and you won't be notified of any failures. */ @Override @TargetApi(Build.VERSION_CODES.GINGERBREAD) public void apply() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { mEditor.apply(); } else { commit(); } } }}
EncryptUtil.java
现在我们实现自己的加解密工具类,加解密工具类大家可以根据自己的实际情况进行定制。我采用的是AES128的加密方式,首先获取当前设备的序列号,然后拼接一个随机字符串,生成hash值,作为AES加密的key,详细代码如下:
package com.domain.securitysharedpreference;import android.annotation.SuppressLint;import android.content.Context;import android.os.Build;import android.provider.Settings;import android.text.TextUtils;import android.util.Base64;import android.util.Log;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;/** * AES加密解密工具 * @author Max * 2016年11月25日15:25:17 */public class EncryptUtil { private String key; private static EncryptUtil instance; private static final String TAG = EncryptUtil.class.getSimpleName(); private EncryptUtil(Context context){ String serialNo = getDeviceSerialNumber(context); //加密随机字符串生成AES key key = SHA(serialNo + "#$ERDTS$D%F^Gojikbh").substring(0, 16); Log.e(TAG, key); } /** * 单例模式 * @param context context * @return */ public static EncryptUtil getInstance(Context context){ if (instance == null){ synchronized (EncryptUtil.class){ if (instance == null){ instance = new EncryptUtil(context); } } } return instance; } /** * Gets the hardware serial number of this device. * * @return serial number or Settings.Secure.ANDROID_ID if not available. */ @SuppressLint("HardwareIds") private String getDeviceSerialNumber(Context context) { // We're using the Reflection API because Build.SERIAL is only available // since API Level 9 (Gingerbread, Android 2.3). try { String deviceSerial = (String) Build.class.getField("SERIAL").get(null); if (TextUtils.isEmpty(deviceSerial)) { return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); } else { return deviceSerial; } } catch (Exception ignored) { // Fall back to Android_ID return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); } } /** * SHA加密 * @param strText 明文 * @return */ private String SHA(final String strText){ // 返回值 String strResult = null; // 是否是有效字符串 if (strText != null && strText.length() > 0){ try{ // SHA 加密开始 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); // 传入要加密的字符串 messageDigest.update(strText.getBytes()); byte byteBuffer[] = messageDigest.digest(); StringBuffer strHexString = new StringBuffer(); for (int i = 0; i < byteBuffer.length; i++){ String hex = Integer.toHexString(0xff & byteBuffer[i]); if (hex.length() == 1){ strHexString.append('0'); } strHexString.append(hex); } strResult = strHexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } return strResult; } /** * AES128加密 * @param plainText 明文 * @return */ public String encrypt(String plainText) { try { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, keyspec); byte[] encrypted = cipher.doFinal(plainText.getBytes()); return Base64.encodeToString(encrypted, Base64.NO_WRAP); } catch (Exception e) { e.printStackTrace(); return null; } } /** * AES128解密 * @param cipherText 密文 * @return */ public String decrypt(String cipherText) { try { byte[] encrypted1 = Base64.decode(cipherText, Base64.NO_WRAP); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, keyspec); byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original); return originalString; } catch (Exception e) { e.printStackTrace(); return null; } }}
这样我们基本上实现了SharedPreference的加解密存储,APP的数据安全进一步得到了保证,现在和大家说一下使用方式:首先我们看一下普通的SharedPreference:
/** * 以常规的SharedPreference保存数据 */private void saveInCommonPreference(){ SharedPreferences sharedPreferences = getSharedPreferences("common_prefs", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("username", mEmailView.getText().toString()); editor.putString("password", mPasswordView.getText().toString()); editor.apply();}
我们看一下本地保存的效果
<?xml version='1.0' encoding='utf-8' standalone='yes' ?><map> <string name="username">1136138123@qq.com</string> <string name="password">147258369</string></map>
其次,我们来看一下SecuritySharedPreference的使用方式:
/** * 以加密的SharedPreference保存数据 */private void saveInSecurityPreference(){ SecuritySharedPreference securitySharedPreference = new SecuritySharedPreference(getApplicationContext(), "security_prefs", Context.MODE_PRIVATE); SecuritySharedPreference.SecurityEditor securityEditor = securitySharedPreference.edit(); securityEditor.putString("username", mEmailView.getText().toString()); securityEditor.putString("password", mPasswordView.getText().toString()); securityEditor.apply();}
我们看一下本地保存的效果有什么区别
<?xml version='1.0' encoding='utf-8' standalone='yes' ?><map> <string name="BZFXj2GNc39n80SizhqRug==">Rnfpxffj9rNl29dsoQxlUzpSaR9m5K6myIYtqQOiIRU=</string> <string name="qF87qMi9YiXtVcIzaHOXrA==">HoHo+CFJrXK3CPMUpcTTow==</string></map>
效果非常棒!我们通过简简单单的两个类,实现了SharedPreference的加密。虽然只是个非常简单的小功能,但是给数据安全提供了护盾,O(∩_∩)O哈哈~
有的同学可能会说,我的项目中已经使用了SharedPreference,如何迁移到SecuritySharedPreference呢?如何进行不加密数据到加密数据的过渡呢?这一点其实我已经替大家做好了,我们在下一次升级应用的时候,在第一次使用SharedPreference时,调用handleTransition()方法进行数据加密的过渡。
- 【Android安全】自带加密光环的SharedPreference
- Android中自带的加密和解密
- .net 自带的md5 加密
- 利用Java自带的MD5加密
- 关于java中自带的加密
- 利用Java自带的MD5加密
- 利用Java自带的MD5加密
- IOS 自带的MD5加密
- 利用Java自带的MD5加密
- Django自带加密模块的使用
- 利用java自带的MD5加密
- 用Java自带的MD5加密
- Java自带的MD5加密
- Java 自带的MD5加密。
- 利用Java自带的MD5加密
- 利用Java自带的MD5加密
- 利用Java自带的MD5加密
- 利用Java自带的MD5加密
- 加密工具GPG入门教程
- 快速软件开发——快速开发策略(笔记)
- POI 公式
- iOS每日一记 之 视频 拍摄 与压缩 阿里云OSS断点续传
- IO多路复用之poll总结
- 【Android安全】自带加密光环的SharedPreference
- IO多路复用之epoll总结
- Eclipse 代码找回
- pycharm 快捷键安装包
- POI 超链接
- HDU5974 A Simple Math Problem
- select、poll、epoll之间的区别总结[整理]
- 提高工作效率的致胜法宝
- 6、50个关于人脸检测/识别的API、库和软件