https://www.jianshu.com/p/06775ddf435f
国内使用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的系统)
好了,到了贴代码时间:
/**
* @date 2019/11/12
* @since 2.0
* description:
*/
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.security.auth.x500.X500Principal;
/**
* 使用KeyStore+AES对称加密的方式
*
* 通过AES加密方式,用KeyGenerator生成秘钥,保存在Android Keystore中
* 对数据进行加解密
* <p>
* 1、创建秘钥,保存在AndroidKeystore里面,秘钥别名为alias
* 2、创建并初始化cipher对象,获取秘钥,对数据进行加解密
*
* https://blog.youkuaiyun.com/hpp_1225/article/details/83786134
*/
public class AESKeystoreUtils {
// private static final String ALIAS = "123";
// 算法/模式/补码方式
private static String TRANSFORMATION = "AES/GCM/NoPadding";
private static byte[] encryptIv;
/**
* 创建秘钥
*/
private static void createKey(String alias) {
//获取Android KeyGenerator的实例
//设置使用KeyGenerator的生成的密钥加密算法是AES,在 AndroidKeyStore 中保存密钥/数据
final KeyGenerator keyGenerator;
AlgorithmParameterSpec spec = null;
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
//使用KeyGenParameterSpec.Builder 创建KeyGenParameterSpec ,传递给KeyGenerators的init方法
//KeyGenParameterSpec 是生成的密钥的参数
//setBlockMode保证了只有指定的block模式下可以加密,解密数据,如果使用其它的block模式,将会被拒绝。
//使用了“AES/GCM/NoPadding”变换算法,还需要设置KeyGenParameterSpec的padding类型
//创建一个开始和结束时间,有效范围内的密钥对才会生成。
Calendar start = new GregorianCalendar();
Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 10);//往后加十年
//todo 高于6.0才可以使用KeyGenParameterSpec 来生成秘钥,低版本呢?
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
spec = new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.build();
} else {
//android6.0以下谷歌还不支持keystore的对称加解密?
// spec = new ;
}
keyGenerator.init(spec);
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
public static String encryptData(String needEncrypt,String alias) {
if (!isHaveKeyStore(alias)) {
createKey(alias);
}
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null);
SecretKey secretKey = secretKeyEntry.getSecretKey();
//KeyGenParameterSpecs中设置的block模式是KeyProperties.BLOCK_MODE_GCM,所以这里只能使用这个模式解密数据。
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//ciphers initialization vector (IV)的引用,用于解密
encryptIv = cipher.getIV();
return Base64.encodeToString(cipher.doFinal(needEncrypt.getBytes()), Base64.NO_WRAP);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
return "空指针异常";
}
return "";
}
public static String decryptData(String needDecrypt,String alias) {
if (!isHaveKeyStore(alias)) {
createKey(alias);
}
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null);
SecretKey secretKey = secretKeyEntry.getSecretKey();
//KeyGenParameterSpecs中设置的block模式是KeyProperties.BLOCK_MODE_GCM,所以这里只能使用这个模式解密数据。
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
//需要为GCMParameterSpec 指定一个认证标签长度(可以是128、120、112、104、96这个例子中我们能使用最大的128),
// 并且用到之前的加密过程中用到的IV。
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, encryptIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
return new String(cipher.doFinal(Base64.decode(needDecrypt, Base64.NO_WRAP)));
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return "";
}
public static void clearKeystore(String alias) {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.deleteEntry(alias);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 是否创建过秘钥
*
* @return
*/
private static boolean isHaveKeyStore(String alias) {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.Entry keyentry = keyStore.getEntry(alias, null);
if (null != keyentry) {
return true;
}
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
}
return false;
}
}
import android.content.Context;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Calendar;
import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;
/**
* @date 2019/11/12
* @since 2.0
* description: 使用KeyStore+RSA非对称加密的方式
* https://www.jianshu.com/p/06775ddf435f
*/
public class EncryptSafeUtil {
private static final String TAG = "EncryptSafeUtil";
private static EncryptSafeUtil encryptSafeUtilInstance = new EncryptSafeUtil();
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 EncryptSafeUtil() {
}
public static EncryptSafeUtil getInstance() {
return encryptSafeUtilInstance;
}
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();//使用AndroidKeyStore生成密钥对
} 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();//使用AndroidKeyStore生成密钥对
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void clearKeystore(String alias) {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.deleteEntry(alias);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* RSA加密方法
*
* @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);//RSA每块加密后的密文长度都是256
}
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();
Log.e(TAG, "in encryptString error:" + e.getMessage());
}
}
return encryptStr;
}
/**
* RSA解密方法
*
* @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);//RSA密文一般都是256的倍数
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();
Log.e(TAG, "in decryptString error:" + e.getLocalizedMessage());
}
}
return decryptStr;
}
}
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
public static String getRandomString(int DataLen) {
int len=DataLen /2;
if(DataLen %2 !=0)
{
len=len+1;
}
byte [] buffer=new byte[len];
Random random=new Random();
random.nextBytes(buffer);
String line=byte2Hex(buffer);
if(DataLen %2 !=0)
{
line=line.substring(0, line.length()-1);
}
return line;
}
public static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF).toUpperCase();
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
@Test
public void testRSAEncrypt() {
Context appContext = InstrumentationRegistry.getTargetContext();
System.out.println("run ok");
{
String data = "from android";
String alias = "yunshouhu";
Log.i(MainActivity.TAG, "data=" + data);
String cipherText = EncryptSafeUtil.getInstance().encryptString(data, alias);
Log.i(MainActivity.TAG, "cipherText=" + cipherText);
String text = EncryptSafeUtil.getInstance().decryptString(cipherText, alias);
Log.i(MainActivity.TAG, "text=" + text);
EncryptSafeUtil.getInstance().clearKeystore(alias);
}
{
{
String data = getRandomString(550)+"中文";
Log.i(MainActivity.TAG, "data=" + data);
String alias = "yunshouhu";
String cipherText = EncryptSafeUtil.getInstance().encryptString(data, alias);
Log.i(MainActivity.TAG, "cipherText=" + cipherText);
String text = EncryptSafeUtil.getInstance().decryptString(cipherText, alias);
Log.i(MainActivity.TAG, "text=" + text);
EncryptSafeUtil.getInstance().clearKeystore(alias);
}
}
}
@Test
public void testAESEncrypt() {
Context appContext = InstrumentationRegistry.getTargetContext();
System.out.println("run ok");
{
String data="from android你好";
String alias="yunshouhu002";
String cipherText=AESKeystoreUtils.encryptData(data,alias);
Log.i(MainActivity.TAG,"cipherText="+cipherText);
String text=AESKeystoreUtils.decryptData(cipherText,alias);
Log.i(MainActivity.TAG,"text="+text);
AESKeystoreUtils.clearKeystore(alias);
}
{
{
String data="from android你好-test";
String alias="yunshouhu002";
String cipherText=AESKeystoreUtils.encryptData(data,alias);
Log.i(MainActivity.TAG,"cipherText="+cipherText);
String text=AESKeystoreUtils.decryptData(cipherText,alias);
Log.i(MainActivity.TAG,"text="+text);
AESKeystoreUtils.clearKeystore(alias);
}
}
}
}