美文网首页
Android Keystore加解密以及遇到的坑

Android Keystore加解密以及遇到的坑

作者: zero_4e7b | 来源:发表于2019-07-30 09:44 被阅读0次

    国内使用Android Keystore加解密的应该很少吧,搜出来也基本都是Android打包时的Keystore,其实谷歌在很早之前就已经为Android提供了类似IOS的KeyChain功能,私钥存储在trustzone系统中,这个trustzone系统独立于Android系统,能做到私钥安全。
    具体怎么安全,我们来了解一下加解密与签名的过程,本文不做复杂的深度解析,普通人也完全不需要了解这么透彻,想深入了解的可以google trustzon。
    keystore加解密与签名的安全性其实很好理解,因为不管是应用还是Android系统本身都无法访问私钥,只能选择创建、删除与使用,从根源角度来杜绝私钥泄露的可能。当然因为google设计问题,早期安全性还是有一些问题的,建议直接从android6.0开始使用,android4.4与5.0也能用只是需要使用早期的KeyPairGeneratorSpec来创建,此类在android6.0已经被废弃。
    说了那么多,可能有些人还是一头雾水,那么罗列一下android keystore的用处吧:

    1. 可以安全的加密本地数据,方便想安全存储但是不知道私钥怎么存放的用户(apk不管如何加固都存在被破解风险)
    2. 可以结合指纹进行指纹授权解密
    3. 在区块链行业加解密签名等尤为显著,此功能要比ios的安全区好用很多(据我所知,应用卸载,ios安全区的内容还在,这毫无安全可言)
      在使用过程中,我也遇到了几个问题,这里需要注意一下,不然一不小心就被坑了:
    4. keystore加解密是非线程安全的,所以一定要加锁(被网上的网友坑了一把,使用了别人提供的util工具,简直日了狗,毁人不倦)
    5. 不要轻易删除keystore,否则加密过的数据会面临无法解密的风险
    6. 私钥创建初始化耗时比较久,尽量一个私钥重复使用(此耗时和手机性能几乎无关,速度取决于trustzone的系统)
      好了,到了贴代码时间:
        private static final String TAG = "EncryptUtil";
        private static EncryptUtil encryptUtilInstance = new EncryptUtil();
    
        private KeyStore keyStore;
    
        private Context context;
        // 单位年
        private final int maxExpiredTime = 1000;
    
        private String x500PrincipalName = "CN=MyKey, O=Android Authority";
    
        // RSA有加密字符长度限制,所以需要分段加密
        private int rsaEncryptBlock = 244;
        private int rsaDecryptBlock = 256;
    
        private EncryptUtil() {
        }
    
        public static EncryptUtil getInstance() {
            return encryptUtilInstance;
        }
    
        public void init(Context context, String x500PrincipalName) {
            this.context = context;
            this.x500PrincipalName = x500PrincipalName;
        }
    
        public void initKeyStore(String alias) {
            synchronized (EncryptSafeUtil.class) {
                try {
                    if (null == keyStore) {
                        keyStore = KeyStore.getInstance("AndroidKeyStore");
                        keyStore.load(null);
                    }
                    createNewKeys(alias);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void createNewKeys(String alias) {
            if (TextUtils.isEmpty(alias)) {
                return;
            }
            try {
                if (keyStore.containsAlias(alias)) {
                    return;
                }
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    // Create new key
                    Calendar start = Calendar.getInstance();
                    Calendar end = Calendar.getInstance();
                    end.add(Calendar.YEAR, maxExpiredTime);
                    KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                            .setAlias(alias)
                            .setSubject(new X500Principal(x500PrincipalName))
                            .setSerialNumber(BigInteger.ONE)
                            .setStartDate(start.getTime())
                            .setEndDate(end.getTime())
                            .build();
                    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
                    generator.initialize(spec);
                    generator.generateKeyPair();
                } else {
                    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
                    keyPairGenerator.initialize(
                            new KeyGenParameterSpec.Builder(
                                    alias,
                                    KeyProperties.PURPOSE_DECRYPT)
                                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                                    .setUserAuthenticationRequired(false)
                                    .build());
                    keyPairGenerator.generateKeyPair();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void clearKeystor(String alias) {
            try {
                keyStore = KeyStore.getInstance("AndroidKeyStore");
                keyStore.load(null);
                keyStore.deleteEntry(alias);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 加密方法
         *
         * @param needEncryptWord  需要加密的字符串
         * @param alias            加密秘钥
         * @return
         */
        public String encryptString(String needEncryptWord, String alias) {
            if (TextUtils.isEmpty(needEncryptWord) || TextUtils.isEmpty(alias)) {
                return "";
            }
            String encryptStr = "";
            synchronized (EncryptSafeUtil.class) {
                initKeyStore(alias);
                try {
                    PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();
                    Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                    inCipher.init(Cipher.ENCRYPT_MODE, publicKey);
    
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    int offSet = 0;
                    int inputLen = needEncryptWord.length();
                    byte[] inputData = needEncryptWord.getBytes();
                    for (int i = 0; inputLen - offSet > 0; offSet = i * rsaEncryptBlock) {
                        byte[] cache;
                        if (inputLen - offSet > rsaEncryptBlock) {
                            cache = inCipher.doFinal(inputData, offSet, rsaEncryptBlock);
                        } else {
                            cache = inCipher.doFinal(inputData, offSet, inputLen - offSet);
                        }
                        out.write(cache, 0, cache.length);
                        ++i;
                    }
                    byte[] encryptedData = out.toByteArray();
                    out.close();
    
                    encryptStr = Base64.encodeToString(encryptedData, Base64.URL_SAFE);
                } catch (Exception e) {
                    e.printStackTrace();
                    LogUtil.e(TAG, "in encryptString error:" + e.getMessage());
                }
            }
            return encryptStr;
        }
    
    
        /**
         * 解密方法
         *
         * @param needDecryptWord 需要解密的字符串
         * @param alias           key的别称
         * @return
         */
        public String decryptString(String needDecryptWord, String alias) {
            if (TextUtils.isEmpty(needDecryptWord) || TextUtils.isEmpty(alias)) {
                return "";
            }
            String decryptStr = "";
            synchronized (EncryptSafeUtil.class) {
                initKeyStore(alias);
                try {
                    PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
                    Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                    outCipher.init(Cipher.DECRYPT_MODE, privateKey);
    
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    int offSet = 0;
                    byte[] encryptedData = Base64.decode(needDecryptWord, Base64.URL_SAFE);
                    int inputLen = encryptedData.length;
                    for (int i = 0; inputLen - offSet > 0; offSet = i * rsaDecryptBlock) {
                        byte[] cache;
                        if (inputLen - offSet > rsaDecryptBlock) {
                            cache = outCipher.doFinal(encryptedData, offSet, rsaDecryptBlock);
                        } else {
                            cache = outCipher.doFinal(encryptedData, offSet, inputLen - offSet);
                        }
                        out.write(cache, 0, cache.length);
                        ++i;
                    }
                    byte[] decryptedData = out.toByteArray();
                    out.close();
    
                    decryptStr = new String(decryptedData, 0, decryptedData.length, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                    LogUtil.e(TAG, "in decryptString error:" + e.getLocalizedMessage());
                }
            }
            return decryptStr;
        }
    

    创建私钥的时候判断了android系统版本,6.0之前使用KeyPairGeneratorSpec创建,6.0以及之后使用KeyGenParameterSpec创建。
    指纹授权解密下篇文章讨论。

    Android+GoLang+SprintBoot探讨群:186305789(疯狂的程序员),绝影大神在等你

    个人兴趣网站:zero接码平台

    个人兴趣网站:猿指

    相关文章

      网友评论

          本文标题:Android Keystore加解密以及遇到的坑

          本文链接:https://www.haomeiwen.com/subject/agebrctx.html