Android SharePreference 加密存储及 AndroidKeyStore密钥存储
前言
最近因为项目中对数据存储特别是SharePreference部分数据存储这块有所需求,在查询了一些资料后对这部分内容做了一些封装。框架主要实现了两方面的需求,一是密钥安全存储方面,二就是SharePreference加解密方面的使用。下面会从这两部分进行讲解。
简单粗暴上代码
如果大家不想看废话可以直接点这里GitHub
使用时候可以直接导入:compile ‘com.dongdong.animal:Toroise:0.0.2’
密钥篇(AndroidKeyStore)
技术思路
Android从4.0(api 14)开始支持Keystore,开始只支持RSA加密。从6.0(api 23)后引入AES,因而密钥生成思路如下。
- 通过随机获取随机字符串作为AES种子。
- 通过AndroidKeyStore生成RSA密钥,并通过公钥加密随机字符串进行保存。
- 使用时获取加密字符串,然后通过AndroidKeyStore进行解密获取原字符串使用。
关键代码
通过别名创建RSA密钥
private static KeyPair createKeyPair(String alias) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties
.KEY_ALGORITHM_RSA, AndroidKeyStore);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 30);
AlgorithmParameterSpec spec;
spec = new KeyPairGeneratorSpec.Builder(appContext)
//使用别名来检索的关键。这是一个关键的关键!
.setAlias(alias)
// 用于生成自签名证书的主题 X500Principal 接受 RFC 1779/2253的专有名词
.setSubject(new X500Principal("CN=" + alias))
//用于自签名证书的序列号生成的一对。
.setSerialNumber(BigInteger.TEN)
// 签名在有效日期范围内
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
keyPairGenerator.initialize(spec);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
加密随机种子
/**
* 进行RSA加密
* @param plainText 被加密数据
* @param key 公钥值
* @return
* @throws Exception
*/
private static String encryptRSA(String plainText, PublicKey key) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_MODE_OAEP);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedByte = cipher.doFinal(plainText.getBytes("UTF-8"));
return Base64.encodeToString(encryptedByte, Base64.NO_WRAP);
}
解密还原
/**
*
* @param alias Rsa别名
* @param enseed 加密的种子
* @return 解密数据
*/
private static String deSeed(String alias, String enseed) {
KeyStore.PrivateKeyEntry privateKeyEntry = null;
try {
Cipher cipher = Cipher.getInstance(RSA_MODE_OAEP);
privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore
.getEntry(alias, null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] encryptedByte = Base64.decode(enseed, Base64.NO_WRAP);
return new String(cipher.doFinal(encryptedByte));
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return enseed;
}
}
存储篇(SafeSpManager)
技术思路
封装SharePreference的存取过程
- 在保存数据时候,key进行md5加密,value进行Aes加密存储。
- 获取数据时候,通过md5后的key获取存储的value值,然后在通过AES解密后返回相应数据。
关键代码
初始化时候传入sp文件名称及Aes密钥或者种子。
public static void turnInit(Context context, String spname, SecretKey key) {
if (context == null) {
throw new NullPointerException("The context can not be Null!");
}
if (key == null) {
throw new NullPointerException("The key can not be Null!");
}
if (TextUtils.isEmpty(spname)) {
spname = context.getApplicationContext().getPackageName();
}
if (instanceMap == null) {
instanceMap = new HashMap<>();
SafeSpManager controller = new SafeSpManager(context, spname, key,null);
instanceMap.put(spname, controller);
} else {
if (!instanceMap.containsKey(spname)) {
instanceMap.put(spname, new SafeSpManager(context, spname, key,null));
}
}
}
protected SafeSpManager(Context context, String spname, SecretKey key, String strkey) {
appContext = context.getApplicationContext();
this.spName = spname;
this.aesKey = key;
this.aesKeyStr = strkey;
if (aesKey == null && TextUtils.isEmpty(aesKeyStr)) {
throw new RuntimeException("Key error, initialization failed");
}
if (aesKey == null && !TextUtils.isEmpty(aesKeyStr)) {
try {
this.aesKey = AESUtil.getRawKey(aesKeyStr.getBytes());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Key error, initialization failed");
}
}
String key_md5 = getShardPreferences().getString(KEY_AES_MD5, "");
if (!TextUtils.isEmpty(key_md5)) {
if (!key_md5.equals(MD5Util.bytes2Md5(aesKey.getEncoded()))) {
throw new RuntimeException("Key error, initialization failed");
}
} else {
if (isOldData()) {
upOldData();
} else {
//存的只是key的MD5值 用于识别是否是同一个Key
mSetSp.edit().putString(KEY_AES_MD5, MD5Util.bytes2Md5(aesKey.getEncoded())).commit();
}
}
}
初始化后根据spname获取对象进行即可存取操作
public static SafeSpManager getInstance(String spName) {
if (instanceMap == null) {
return null;
} else {
if (TextUtils.isEmpty(spName)) {
spName = appContext.getPackageName();
}
if (instanceMap.containsKey(spName)) {
return instanceMap.get(spName);
} else {
return null;
}
}
}