Android SharePreference 加密儲存及 AndroidKeyStore金鑰儲存
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; } } }