国内使用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的用处吧:
可以安全的加密本地数据,方便想安全存储但是不知道私钥怎么存放的用户(apk不管如何加固都存在被破解风险)
可以结合指纹进行指纹授权解密
在区块链行业加解密签名等尤为显著,此功能要比ios的安全区好用很多(据我所知,应用卸载,ios安全区的内容还在,这毫无安全可言)
在使用过程中,我也遇到了几个问题,这里需要注意一下,不然一不小心就被坑了:
keystore加解密是非线程安全的,所以一定要加锁(被网上的网友坑了一把,使用了别人提供的util工具,简直日了狗,毁人不倦)
不要轻易删除keystore,否则加密过的数据会面临无法解密的风险
私钥创建初始化耗时比较久,尽量一个私钥重复使用(此耗时和手机性能几乎无关,速度取决于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创建。
指纹授权解密下篇文章讨论。
Comments
comments powered by zero