1111
1212import java .security .InvalidKeyException ;
1313
14+ import android .security .KeyPairGeneratorSpec ;
1415import android .security .keystore .KeyProperties ;
1516import android .util .Base64 ;
1617import android .util .Log ;
17-
1818import androidx .annotation .NonNull ;
1919import androidx .biometric .BiometricConstants ;
2020import androidx .biometric .BiometricManager ;
3030import com .facebook .react .bridge .UiThreadUtil ;
3131import com .facebook .react .modules .core .DeviceEventManagerModule ;
3232
33+ import java .math .BigInteger ;
34+ import java .security .Key ;
35+ import java .security .KeyPairGenerator ;
3336import java .security .KeyStore ;
37+ import java .security .InvalidKeyException ;
38+ import java .security .PrivateKey ;
39+ import java .security .PublicKey ;
40+ import java .util .Calendar ;
3441import java .util .HashMap ;
3542import java .util .Map ;
3643import java .util .concurrent .Executor ;
4047import javax .crypto .KeyGenerator ;
4148import javax .crypto .SecretKey ;
4249import javax .crypto .SecretKeyFactory ;
50+ import javax .crypto .spec .GCMParameterSpec ;
4351import javax .crypto .spec .IvParameterSpec ;
4452
4553import androidx .fragment .app .FragmentActivity ;
54+ import javax .security .auth .x500 .X500Principal ;
55+
4656import br .com .classapp .RNSensitiveInfo .utils .AppConstants ;
4757
4858public 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