Android的加密缓存(下)--字符串加密解密实战


想必看了Android的加密缓存(上)–JCA基础的小伙伴们,对于JCA已经有了一定的了解。那么下面我们结合具体代码,完成一次简单的字符串缓存加密和解密。如果你对Key、KeyStore、KeyGenerator、Cipher这些基础的JCA类还不太熟悉,建议你可以回顾下上一篇文章。

首先我们设计一个SecurityCache,封装SharedPreferences和基本的加密解密操作,设计成单例的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

class SecurityCache {

private volatile static SecurityCache securityCache;

private KeyStore mKeyStore;
private SharedPreferences mSharedPreferences;

private SecurityCache(Context context) {
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
initKey();
}

public static SecurityCache getInstance(Context context) {
if (securityCache == null) {
synchronized (SecurityCache.class) {
if (securityCache == null) {
securityCache = new SecurityCache(context);
}
}
}
return securityCache;
}

}

在initKey()中,我们需要做一些初始化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static final String KEY_NAME = "demo_key";

private KeyStore mKeyStore;

private void initKey() {
try {
mKeyStore = KeyStore.getInstance("AndroidKeyStore");
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
mKeyStore.load(null);
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) //表示生成的key用于加密和解密
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException | NoSuchProviderException e) {
throw new RuntimeException("Fail to init key " + e);
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to get an instance of KeyGenerator ", e);
}
}

字符串加密

初始化完成,接着来看字符串的加密,通过生成Cipher,调用cipher.doFinal()方法,将字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 加密存储
* <p>
*/
public String encryptContent(String content) {
try {
Cipher cipher = createEncruptCipher();
if (cipher == null) return "";
byte[] encryptedBytes = cipher.doFinal(content.getBytes("utf-8"));
byte[] cipherIv = cipher.getIV();
String encodedContent = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
String encodeCipherIv = Base64.encodeToString(cipherIv, Base64.DEFAULT);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(ENCRYTED_CONTENT, encodedContent);
editor.putString(ENCRYTED_CONTENT_IV, encodeCipherIv);
editor.apply();
return encodedContent;
} catch (IOException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("Failed to encrypt ", e);
}
}

这里createEncruptCipher()方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Nullable
public Cipher createEncruptCipher() {
Cipher mCipher;
try {
mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
mCipher.init(Cipher.ENCRYPT_MODE, key, mCipher.getParameters());
return mCipher;
} catch (KeyPermanentlyInvalidatedException e) {
Log.e(TAG, "initCipher error " + e.getMessage());
return null;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException |
CertificateException | UnrecoverableKeyException | InvalidKeyException |
KeyStoreException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("Failed to encrypt pin ", e);
}
}

字符串解密

同理来看解密的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 解密还原
* <p>
*/
public String decryptContent() {
String encryptedContent = mSharedPreferences.getString(ENCRYTED_CONTENT, "");
String encodedIv = mSharedPreferences.getString(ENCRYTED_CONTENT_IV, "");
if (TextUtils.isEmpty(encryptedContent) || TextUtils.isEmpty(encodedIv)) {
return "";
}
try {
byte[] cipherIv = Base64.decode(encodedIv, Base64.DEFAULT);
Cipher cipher = createDecruptCipher(new IvParameterSpec(cipherIv));
if (cipher == null) return "";
byte[] decodedBytes = Base64.decode(encryptedContent, Base64.DEFAULT);
byte[] decryptBytes = cipher.doFinal(decodedBytes);
return new String(decryptBytes, Charset.forName("UTF8"));
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
Log.e(TAG, "Failed to decrypt the data with the generated key." + e.getMessage());
return "";
}
}

解密中createDecruptCipher(IvParameterSpec ivParameterSpec)需要的IvParameterSpec正是加密时创建的Cipher的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Nullable
private Cipher createDecruptCipher(IvParameterSpec ivParameterSpec) {
Cipher mCipher;
try {
mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
mCipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
return mCipher;
} catch (KeyPermanentlyInvalidatedException e) {
Log.e(TAG, "initCipher error " + e.getMessage());
return null;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException |
CertificateException | UnrecoverableKeyException | InvalidKeyException |
KeyStoreException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("Failed to encrypt pin ", e);
}
}

Demo代码可以从这里获取


参考资料