Skip to content

Commit ac5298f

Browse files
committed
Merge remote-tracking branch 'origin/keystore' into feature/keystoreAsDefault
2 parents f911a12 + 2ba30fa commit ac5298f

File tree

1 file changed

+127
-23
lines changed

1 file changed

+127
-23
lines changed

android/src/main/java/br/com/classapp/RNSensitiveInfo/RNSensitiveInfoModule.java

Lines changed: 127 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
import java.security.InvalidKeyException;
1313

14+
import android.security.KeyPairGeneratorSpec;
1415
import android.security.keystore.KeyProperties;
1516
import android.util.Base64;
1617
import android.util.Log;
17-
1818
import androidx.annotation.NonNull;
1919
import androidx.biometric.BiometricConstants;
2020
import androidx.biometric.BiometricManager;
@@ -30,7 +30,14 @@
3030
import com.facebook.react.bridge.UiThreadUtil;
3131
import com.facebook.react.modules.core.DeviceEventManagerModule;
3232

33+
import java.math.BigInteger;
34+
import java.security.Key;
35+
import java.security.KeyPairGenerator;
3336
import java.security.KeyStore;
37+
import java.security.InvalidKeyException;
38+
import java.security.PrivateKey;
39+
import java.security.PublicKey;
40+
import java.util.Calendar;
3441
import java.util.HashMap;
3542
import java.util.Map;
3643
import java.util.concurrent.Executor;
@@ -40,9 +47,12 @@
4047
import javax.crypto.KeyGenerator;
4148
import javax.crypto.SecretKey;
4249
import javax.crypto.SecretKeyFactory;
50+
import javax.crypto.spec.GCMParameterSpec;
4351
import javax.crypto.spec.IvParameterSpec;
4452

4553
import androidx.fragment.app.FragmentActivity;
54+
import javax.security.auth.x500.X500Principal;
55+
4656
import br.com.classapp.RNSensitiveInfo.utils.AppConstants;
4757

4858
public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
@@ -56,8 +66,12 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
5666
KeyProperties.BLOCK_MODE_CBC + "/" +
5767
KeyProperties.ENCRYPTION_PADDING_PKCS7;
5868

59-
private static final String KEY_ALIAS_AES = "MyAesKeyAlias";
69+
private static final String AES_GCM = "AES/GCM/NoPadding";
70+
private static final String RSA_ECB = "RSA/ECB/PKCS1Padding";
6071
private static final String DELIMITER = "]";
72+
private static final byte[] FIXED_IV = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1};
73+
private static final String KEY_ALIAS = "MySharedPreferenceKeyAlias";
74+
private static final String KEY_ALIAS_AES = "MyAesKeyAlias";
6175

6276
private FingerprintManager mFingerprintManager;
6377
private KeyStore mKeyStore;
@@ -68,13 +82,28 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
6882

6983
public RNSensitiveInfoModule(ReactApplicationContext reactContext) {
7084
super(reactContext);
85+
86+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
87+
Exception cause = new RuntimeException("Keystore is not supported!");
88+
throw new RuntimeException("Android version is too low", cause);
89+
}
90+
91+
try {
92+
mKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
93+
mKeyStore.load(null);
94+
} catch (Exception e) {
95+
e.printStackTrace();
96+
}
97+
98+
initKeyStore();
99+
71100
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
72101
try {
73102
mFingerprintManager = (FingerprintManager) reactContext.getSystemService(Context.FINGERPRINT_SERVICE);
103+
initFingerprintKeyStore();
74104
} catch (Exception e) {
75105
Log.d("RNSensitiveInfo", "Fingerprint not supported");
76106
}
77-
initKeyStore();
78107
}
79108
}
80109

@@ -155,6 +184,12 @@ public void getItem(String key, ReadableMap options, Promise pm) {
155184
HashMap strings = options.hasKey("strings") ? options.getMap("strings").toHashMap() : new HashMap();
156185

157186
decryptWithAes(value, showModal, strings, pm, null);
187+
} else if (value != null) {
188+
try {
189+
pm.resolve(decrypt(value));
190+
} catch (Exception e) {
191+
pm.reject(e);
192+
}
158193
} else {
159194
pm.resolve(value);
160195
}
@@ -172,10 +207,10 @@ public void setItem(String key, String value, ReadableMap options, Promise pm) {
172207
putExtraWithAES(key, value, prefs(name), showModal, strings, pm, null);
173208
} else {
174209
try {
175-
putExtra(key, value, prefs(name));
210+
putExtra(key, encrypt(value), prefs(name));
176211
pm.resolve(value);
177212
} catch (Exception e) {
178-
Log.d("RNSensitiveInfo", e.getCause().getMessage());
213+
e.printStackTrace();
179214
pm.reject(e);
180215
}
181216
}
@@ -205,6 +240,11 @@ public void getAllItems(ReadableMap options, Promise pm) {
205240

206241
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
207242
String value = entry.getValue().toString();
243+
try {
244+
value = decrypt(value);
245+
} catch (Exception e) {
246+
Log.d("RNSensitiveInfo", Log.getStackTraceString(e));
247+
}
208248
resultData.putString(entry.getKey(), value);
209249
}
210250
pm.resolve(resultData);
@@ -231,18 +271,46 @@ private String sharedPreferences(ReadableMap options) {
231271
}
232272

233273

234-
private void putExtra(String key, Object value, SharedPreferences mSharedPreferences) {
274+
private void putExtra(String key, String value, SharedPreferences mSharedPreferences) {
235275
SharedPreferences.Editor editor = mSharedPreferences.edit();
236-
if (value instanceof String) {
237-
editor.putString(key, (String) value).apply();
238-
} else if (value instanceof Boolean) {
239-
editor.putBoolean(key, (Boolean) value).apply();
240-
} else if (value instanceof Integer) {
241-
editor.putInt(key, (Integer) value).apply();
242-
} else if (value instanceof Long) {
243-
editor.putLong(key, (Long) value).apply();
244-
} else if (value instanceof Float) {
245-
editor.putFloat(key, (Float) value).apply();
276+
editor.putString(key, value).apply();
277+
}
278+
279+
/**
280+
* Generates a new RSA key and stores it under the { @code KEY_ALIAS } in the
281+
* Android Keystore.
282+
*/
283+
private void initKeyStore() {
284+
try {
285+
if (!mKeyStore.containsAlias(KEY_ALIAS)) {
286+
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
287+
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_PROVIDER);
288+
keyGenerator.init(
289+
new KeyGenParameterSpec.Builder(KEY_ALIAS,
290+
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
291+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
292+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
293+
.setRandomizedEncryptionRequired(false)
294+
.build());
295+
keyGenerator.generateKey();
296+
} else {
297+
Calendar notBefore = Calendar.getInstance();
298+
Calendar notAfter = Calendar.getInstance();
299+
notAfter.add(Calendar.YEAR, 10);
300+
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getReactApplicationContext())
301+
.setAlias(KEY_ALIAS)
302+
.setSubject(new X500Principal("CN=" + KEY_ALIAS))
303+
.setSerialNumber(BigInteger.valueOf(1337))
304+
.setStartDate(notBefore.getTime())
305+
.setEndDate(notAfter.getTime())
306+
.build();
307+
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", ANDROID_KEYSTORE_PROVIDER);
308+
kpGenerator.initialize(spec);
309+
kpGenerator.generateKeyPair();
310+
}
311+
}
312+
} catch (Exception e) {
313+
e.printStackTrace();
246314
}
247315
}
248316

@@ -285,23 +353,19 @@ public void run() {
285353
* Generates a new AES key and stores it under the { @code KEY_ALIAS_AES } in the
286354
* Android Keystore.
287355
*/
288-
private void initKeyStore() {
356+
private void initFingerprintKeyStore() {
289357
try {
290-
mKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
291-
mKeyStore.load(null);
292-
293358
// Check if a generated key exists under the KEY_ALIAS_AES .
294359
if (!mKeyStore.containsAlias(KEY_ALIAS_AES)) {
295360
prepareKey();
296361
}
297362
} catch (Exception e) {
363+
e.printStackTrace();
298364
}
299365
}
300366

301367
private void prepareKey() throws Exception {
302-
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
303-
return;
304-
}
368+
305369
KeyGenerator keyGenerator = KeyGenerator.getInstance(
306370
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_PROVIDER);
307371

@@ -539,4 +603,44 @@ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult re
539603
pm.reject("Fingerprint not supported", "Fingerprint not supported");
540604
}
541605
}
606+
607+
public String encrypt(String input) throws Exception {
608+
byte[] bytes = input.getBytes();
609+
Cipher c;
610+
611+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
612+
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
613+
c = Cipher.getInstance(AES_GCM);
614+
c.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, FIXED_IV));
615+
} else {
616+
PublicKey publicKey = ((KeyStore.PrivateKeyEntry)mKeyStore.getEntry(KEY_ALIAS, null)).getCertificate().getPublicKey();
617+
c = Cipher.getInstance(RSA_ECB);
618+
c.init(Cipher.ENCRYPT_MODE, publicKey);
619+
}
620+
byte[] encodedBytes = c.doFinal(bytes);
621+
String encryptedBase64Encoded = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
622+
return encryptedBase64Encoded;
623+
}
624+
625+
626+
public String decrypt(String encrypted) throws Exception {
627+
if (encrypted == null) {
628+
Exception cause = new RuntimeException("Invalid argument at decrypt function");
629+
throw new RuntimeException("encrypted argument can't be null", cause);
630+
}
631+
632+
Cipher c;
633+
634+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
635+
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
636+
c = Cipher.getInstance(AES_GCM);
637+
c.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, FIXED_IV));
638+
} else {
639+
PrivateKey privateKey = ((KeyStore.PrivateKeyEntry)mKeyStore.getEntry(KEY_ALIAS, null)).getPrivateKey();
640+
c = Cipher.getInstance(RSA_ECB);
641+
c.init(Cipher.DECRYPT_MODE, privateKey);
642+
}
643+
byte[] decodedBytes = c.doFinal(Base64.decode(encrypted, Base64.DEFAULT));
644+
return new String(decodedBytes);
645+
}
542646
}

0 commit comments

Comments
 (0)