Android SharePreference 加密存儲(chǔ)及 AndroidKeyStore密鑰存儲(chǔ)

Android SharePreference 加密存儲(chǔ)及 AndroidKeyStore密鑰存儲(chǔ)

前言

最近因?yàn)轫?xiàng)目中對(duì)數(shù)據(jù)存儲(chǔ)特別是SharePreference部分?jǐn)?shù)據(jù)存儲(chǔ)這塊有所需求,在查詢了一些資料后對(duì)這部分內(nèi)容做了一些封裝??蚣苤饕獙?shí)現(xiàn)了兩方面的需求,一是密鑰安全存儲(chǔ)方面,二就是SharePreference加解密方面的使用。下面會(huì)從這兩部分進(jìn)行講解。

簡(jiǎn)單粗暴上代碼

如果大家不想看廢話可以直接點(diǎn)這里GitHub
使用時(shí)候可以直接導(dǎo)入:compile 'com.dongdong.animal:Toroise:0.0.2'

密鑰篇(AndroidKeyStore)

技術(shù)思路

Android從4.0(api 14)開始支持Keystore,開始只支持RSA加密。從6.0(api 23)后引入AES,因而密鑰生成思路如下。

  1. 通過隨機(jī)獲取隨機(jī)字符串作為AES種子。
  2. 通過AndroidKeyStore生成RSA密鑰,并通過公鑰加密隨機(jī)字符串進(jìn)行保存。
  3. 使用時(shí)獲取加密字符串,然后通過AndroidKeyStore進(jìn)行解密獲取原字符串使用。

關(guān)鍵代碼

通過別名創(chuàng)建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)
                    //使用別名來檢索的關(guān)鍵。這是一個(gè)關(guān)鍵的關(guān)鍵!
                    .setAlias(alias)
                    // 用于生成自簽名證書的主題 X500Principal 接受 RFC 1779/2253的專有名詞
                    .setSubject(new X500Principal("CN=" + alias))
                    //用于自簽名證書的序列號(hào)生成的一對(duì)。
                    .setSerialNumber(BigInteger.TEN)
                    // 簽名在有效日期范圍內(nèi)
                    .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;
    }

加密隨機(jī)種子

/**
     * 進(jìn)行RSA加密
     * @param plainText 被加密數(shù)據(jù)
     * @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 解密數(shù)據(jù)
     */
 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;
        }
    }

存儲(chǔ)篇(SafeSpManager)

技術(shù)思路

封裝SharePreference的存取過程

  1. 在保存數(shù)據(jù)時(shí)候,key進(jìn)行md5加密,value進(jìn)行Aes加密存儲(chǔ)。
  2. 獲取數(shù)據(jù)時(shí)候,通過md5后的key獲取存儲(chǔ)的value值,然后在通過AES解密后返回相應(yīng)數(shù)據(jù)。

關(guān)鍵代碼

初始化時(shí)候傳入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值 用于識(shí)別是否是同一個(gè)Key
                mSetSp.edit().putString(KEY_AES_MD5, MD5Util.bytes2Md5(aesKey.getEncoded())).commit();
            }

        }

    }

初始化后根據(jù)spname獲取對(duì)象進(jìn)行即可存取操作

 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;
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容