问题背景
在我们App开发过程中,可能会涉及到一些敏感和安全数据需要加密的情况,比如登录token的存储。我们往往会使用一些加密算法将这些敏感数据加密之后再保存起来,需要取出来的时候再进行解密。
此时就会有一个问题:用于加解密的Key该如何存储?
为了保证安全性,Android提供了KeyStore系统来保存Key,本文就浅探一下KeyStore及其使用方法。
问题分析
话不多说,直接上代码:
1、测试加解密过程
(1)com.baorant.databindingdemo.EncryptUtil
package com.baorant.databindingdemo;
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 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;
/**
* 基于KeyStore的本地加解密方法
*/
public class EncryptUtil {
private static final String TAG = "EncryptUtil";
private static EncryptUtil encryptUtilInstance;
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() {
if (encryptUtilInstance == null) {
synchronized (EncryptUtil.class) {
if (encryptUtilInstance == null) {
encryptUtilInstance = new EncryptUtil();
}
}
}
return encryptUtilInstance;
}
public void init(Context context, String x500PrincipalName) {
this.context = context;
this.x500PrincipalName = x500PrincipalName;
}
public void initKeyStore(String alias) {
synchronized (EncryptUtil.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 clearKeystore(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 (EncryptUtil.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();
}
}
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 (EncryptUtil.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();
}
}
return decryptStr;
}
}
(2)测试代码,获取目标内容的keystore加密结果存储本地:
private void testEncrypt() {
// 需要加密的字符串
String src = "baorant";
// 自定义alias别名
String alias = "myKey";
EncryptUtil.getInstance().initKeyStore(alias);
// 加密后的字符串,可以本地存储
String encryptedString = EncryptUtil.getInstance().encryptString(src, alias);
Log.d("baorant", encryptedString);
String decryptedString = EncryptUtil.getInstance().decryptString(encryptedString, alias);
Log.d("baorant", decryptedString);
}
运行结果如下:
(3)本地存储keystore加密后的内容,运行时进行解密拿出来使用即可。
private void testDecrypt() {
String alias = "myKey";
String encryptedString = "s633gcoXu4fcyS6IsMg5Gi5brsvULgvF5-UIh8RFXJhpbb1rQpeaIDSVUCSQ-Qf5ErkrH9lTO2wi\n" +
"_mo4IZo4ibk65DMw7TvGKgpDj_0YhD070EouMKExF3u2bLk2X6yt20WD4He1cxfmtYv42gM869zh\n" +
"SRtYUy4JVe3W7I9r3i68Q3HWUPZu7381yxvStgEPIvwx49EpSXPCNJuC6MYFQNarUFkEDmLii31U\n" +
"VRK0zIY1TVZSH27nP4qsB9ujbVZbCeKXlaPxPfY67G6BYmdXVFmk5c0CKIo0mZDYQ5NS7rz7gmM0\n" +
"PBCt-Rdm4Xt5C5k0nAMojmQqq8o2mJBOYWVjBg==";
String decryptedString = EncryptUtil.getInstance().decryptString(encryptedString, alias);
Log.d("baorant", decryptedString);
}
运行结果如下:
问题解决
本文初步介绍了Android基于KeyStore对数据进行加解密的基本过程和用法,用兴趣的同学可以进一步深入研究。