-
Notifications
You must be signed in to change notification settings - Fork 72
/
AesCipherFactory.java
143 lines (127 loc) · 4.74 KB
/
AesCipherFactory.java
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package co.infinum.goldfinger.crypto.impl;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.security.Key;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import co.infinum.goldfinger.crypto.CipherFactory;
/**
* AES Cipher implementation. By default the given Cipher is created with
* Key which requires user authentication.
* This implementation is used by default if other Factory is not provided.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public class AesCipherFactory implements CipherFactory {
private static final String CIPHER_TRANSFORMATION = String.format(
"%s/%s/%s",
KeyProperties.KEY_ALGORITHM_AES,
KeyProperties.BLOCK_MODE_CBC,
KeyProperties.ENCRYPTION_PADDING_PKCS7
);
private static final String KEY_KEYSTORE = "AndroidKeyStore";
private static final String KEY_SHARED_PREFS = "<Goldfinger IV>";
private KeyGenerator keyGenerator;
private KeyStore keyStore;
private final SharedPreferences sharedPrefs;
public AesCipherFactory(@NonNull Context context) {
this.sharedPrefs = context.getSharedPreferences(KEY_SHARED_PREFS, Context.MODE_PRIVATE);
try {
keyStore = KeyStore.getInstance(KEY_KEYSTORE);
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_KEYSTORE);
} catch (Exception ignored) {
/* Gracefully handle exception later when create method is invoked. */
}
}
@Nullable
@Override
public Cipher createDecryptionCrypter(String key) {
if (keyStore == null || keyGenerator == null) {
return null;
}
try {
Key secureKey = loadKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
byte[] iv = loadIv(key);
cipher.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(iv));
return cipher;
} catch (Exception e) {
return null;
}
}
@Nullable
@Override
public Cipher createEncryptionCrypter(@NonNull String key) {
if (keyStore == null || keyGenerator == null) {
return null;
}
try {
Key secureKey = createKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secureKey);
saveIv(key, cipher.getIV());
return cipher;
} catch (Exception e) {
return null;
}
}
/**
* Simple method created to be easily extendable and create cipher which
* does not require user authentication.
*/
protected boolean isUserAuthRequired() {
return true;
}
/**
* Create secure key used to create Cipher.
*
* @param key name of the keystore.
* @return created key, or null if something weird happens.
* @throws Exception if anything fails, it is handled gracefully.
*/
@Nullable
private Key createKey(@NonNull String key) throws Exception {
KeyGenParameterSpec.Builder keyGenParamsBuilder =
new KeyGenParameterSpec.Builder(key, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(isUserAuthRequired());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
keyGenParamsBuilder.setInvalidatedByBiometricEnrollment(isUserAuthRequired());
}
keyGenerator.init(keyGenParamsBuilder.build());
keyGenerator.generateKey();
return loadKey(key);
}
/**
* Load IV from Shared preferences. Decode from Base64.
*/
@NonNull
private byte[] loadIv(@NonNull String key) {
return Base64.decode(sharedPrefs.getString(key, ""), Base64.DEFAULT);
}
/**
* Load {@link Key} from {@link KeyStore}.
*
* @param key name of the {@link Key} to load.
*/
@Nullable
private Key loadKey(@NonNull String key) throws Exception {
keyStore.load(null);
return keyStore.getKey(key, null);
}
/**
* Save IV to Shared preferences. Before saving encode it to Base64.
*/
private void saveIv(@NonNull String key, @Nullable byte[] iv) {
sharedPrefs.edit().putString(key, Base64.encodeToString(iv, Base64.DEFAULT)).apply();
}
}