为了账号安全,请及时绑定邮箱和手机立即绑定

Android SharePreference 加密存储及 AndroidKeyStore密钥存储

标签:
Java

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,因而密钥生成思路如下。

  1. 通过随机获取随机字符串作为AES种子。

  2. 通过AndroidKeyStore生成RSA密钥,并通过公钥加密随机字符串进行保存。

  3. 使用时获取加密字符串,然后通过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的存取过程

  1. 在保存数据时候,key进行md5加密,value进行Aes加密存储。

  2. 获取数据时候,通过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;
            }
        }
    }



作者:優僽
链接:https://www.jianshu.com/p/f1830a2b29b9


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消