Skip to content

Commit 7d223b6

Browse files
authored
Merge pull request #118 from star-collector/keystore
Android <= 5 encryption support
2 parents 99d010b + 863d3e9 commit 7d223b6

1 file changed

Lines changed: 75 additions & 30 deletions

File tree

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

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import android.os.CancellationSignal;
88
import android.security.keystore.KeyGenParameterSpec;
99
import android.security.keystore.KeyInfo;
10-
import java.security.InvalidKeyException;
10+
import android.security.KeyPairGeneratorSpec;
1111
import android.security.keystore.KeyProperties;
1212
import android.support.annotation.NonNull;
1313
import android.util.Base64;
@@ -22,8 +22,14 @@
2222
import com.facebook.react.bridge.WritableNativeMap;
2323
import com.facebook.react.modules.core.DeviceEventManagerModule;
2424

25+
import java.math.BigInteger;
2526
import java.security.Key;
27+
import java.security.KeyPairGenerator;
2628
import java.security.KeyStore;
29+
import java.security.InvalidKeyException;
30+
import java.security.PrivateKey;
31+
import java.security.PublicKey;
32+
import java.util.Calendar;
2733
import java.util.HashMap;
2834
import java.util.Map;
2935

@@ -34,6 +40,8 @@
3440
import javax.crypto.spec.GCMParameterSpec;
3541
import javax.crypto.spec.IvParameterSpec;
3642

43+
import javax.security.auth.x500.X500Principal;
44+
3745
import br.com.classapp.RNSensitiveInfo.utils.AppConstants;
3846
import br.com.classapp.RNSensitiveInfo.view.Fragments.FingerprintAuthenticationDialogFragment;
3947
import br.com.classapp.RNSensitiveInfo.view.Fragments.FingerprintUiHelper;
@@ -50,7 +58,7 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
5058
KeyProperties.ENCRYPTION_PADDING_PKCS7;
5159

5260
private static final String AES_GCM = "AES/GCM/NoPadding";
53-
private static final String AES_ECB = "AES/ECB/PKCS7Padding";
61+
private static final String RSA_ECB = "RSA/ECB/PKCS1Padding";
5462
private static final String DELIMITER = "]";
5563
private static final byte[] FIXED_IV = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1};
5664
private static final String KEY_ALIAS = "MySharedPreferenceKeyAlias";
@@ -62,13 +70,28 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
6270

6371
public RNSensitiveInfoModule(ReactApplicationContext reactContext) {
6472
super(reactContext);
73+
74+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
75+
Exception cause = new RuntimeException("Keystore is not supported!");
76+
throw new RuntimeException("Android version is too low", cause);
77+
}
78+
79+
try {
80+
mKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
81+
mKeyStore.load(null);
82+
} catch (Exception e) {
83+
e.printStackTrace();
84+
}
85+
86+
initKeyStore();
87+
6588
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
6689
try {
6790
mFingerprintManager = (FingerprintManager) reactContext.getSystemService(Context.FINGERPRINT_SERVICE);
91+
initFingerprintKeyStore();
6892
} catch (Exception e) {
6993
Log.d("RNSensitiveInfo", "Fingerprint not supported");
7094
}
71-
initKeyStore();
7295
}
7396
}
7497

@@ -162,7 +185,7 @@ public void setItem(String key, String value, ReadableMap options, Promise pm) {
162185
putExtra(key, encrypt(value), prefs(name));
163186
pm.resolve(value);
164187
} catch (Exception e) {
165-
Log.d("RNSensitiveInfo", e.getCause().getMessage());
188+
e.printStackTrace();
166189
pm.reject(e);
167190
}
168191
}
@@ -227,40 +250,61 @@ private void putExtra(String key, String value, SharedPreferences mSharedPrefere
227250
SharedPreferences.Editor editor = mSharedPreferences.edit();
228251
editor.putString(key, value).apply();
229252
}
230-
253+
231254
/**
232-
* Generates a new AES key and stores it under the { @code KEY_ALIAS_AES } in the
255+
* Generates a new AES key and stores it under the { @code KEY_ALIAS } in the
233256
* Android Keystore.
234257
*/
235258
private void initKeyStore() {
236259
try {
237-
mKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
238-
mKeyStore.load(null);
239-
240-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !mKeyStore.containsAlias(KEY_ALIAS)) {
241-
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_PROVIDER);
242-
keyGenerator.init(
243-
new KeyGenParameterSpec.Builder(KEY_ALIAS,
244-
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
245-
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
246-
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
247-
.setRandomizedEncryptionRequired(false)
248-
.build());
249-
keyGenerator.generateKey();
260+
if (!mKeyStore.containsAlias(KEY_ALIAS)) {
261+
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
262+
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE_PROVIDER);
263+
keyGenerator.init(
264+
new KeyGenParameterSpec.Builder(KEY_ALIAS,
265+
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
266+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
267+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
268+
.setRandomizedEncryptionRequired(false)
269+
.build());
270+
keyGenerator.generateKey();
271+
} else {
272+
Calendar notBefore = Calendar.getInstance();
273+
Calendar notAfter = Calendar.getInstance();
274+
notAfter.add(Calendar.YEAR, 10);
275+
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getReactApplicationContext())
276+
.setAlias(KEY_ALIAS)
277+
.setSubject(new X500Principal("CN=" + KEY_ALIAS))
278+
.setSerialNumber(BigInteger.valueOf(1337))
279+
.setStartDate(notBefore.getTime())
280+
.setEndDate(notAfter.getTime())
281+
.build();
282+
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", ANDROID_KEYSTORE_PROVIDER);
283+
kpGenerator.initialize(spec);
284+
kpGenerator.generateKeyPair();
285+
}
250286
}
251-
287+
} catch (Exception e) {
288+
e.printStackTrace();
289+
}
290+
}
291+
292+
/**
293+
* Generates a new AES key and stores it under the { @code KEY_ALIAS_AES } in the
294+
* Android Keystore.
295+
*/
296+
private void initFingerprintKeyStore() {
297+
try {
252298
// Check if a generated key exists under the KEY_ALIAS_AES .
253299
if (!mKeyStore.containsAlias(KEY_ALIAS_AES)) {
254300
prepareKey();
255301
}
256302
} catch (Exception e) {
303+
e.printStackTrace();
257304
}
258305
}
259306

260307
private void prepareKey() throws Exception {
261-
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
262-
return;
263-
}
264308

265309
KeyGenerator keyGenerator = KeyGenerator.getInstance(
266310
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_PROVIDER);
@@ -497,17 +541,17 @@ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult re
497541
}
498542

499543
public String encrypt(String input) throws Exception {
500-
501-
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
502544
byte[] bytes = input.getBytes();
503545

504546
Cipher c;
505547
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
548+
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
506549
c = Cipher.getInstance(AES_GCM);
507550
c.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, FIXED_IV));
508551
} else {
509-
c = Cipher.getInstance(AES_ECB, "BC");
510-
c.init(Cipher.ENCRYPT_MODE, secretKey);
552+
PublicKey publicKey = ((KeyStore.PrivateKeyEntry)mKeyStore.getEntry(KEY_ALIAS, null)).getCertificate().getPublicKey();
553+
c = Cipher.getInstance(RSA_ECB);
554+
c.init(Cipher.ENCRYPT_MODE, publicKey);
511555
}
512556
byte[] encodedBytes = c.doFinal(bytes);
513557
String encryptedBase64Encoded = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
@@ -522,14 +566,15 @@ public String decrypt(String encrypted) throws Exception {
522566
}
523567

524568
Cipher c;
525-
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
526569

527570
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
571+
Key secretKey = ((KeyStore.SecretKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
528572
c = Cipher.getInstance(AES_GCM);
529573
c.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, FIXED_IV));
530574
} else {
531-
c = Cipher.getInstance(AES_ECB, "BC");
532-
c.init(Cipher.DECRYPT_MODE, secretKey);
575+
PrivateKey privateKey = ((KeyStore.PrivateKeyEntry)mKeyStore.getEntry(KEY_ALIAS, null)).getPrivateKey();
576+
c = Cipher.getInstance(RSA_ECB);
577+
c.init(Cipher.DECRYPT_MODE, privateKey);
533578
}
534579
byte[] decodedBytes = c.doFinal(Base64.decode(encrypted, Base64.DEFAULT));
535580
return new String(decodedBytes);

0 commit comments

Comments
 (0)