6
6
7
7
package com .reactlibrary .securekeystore ;
8
8
9
- import android .content .Context ;
9
+ import android .content .SharedPreferences ;
10
10
import android .os .Build ;
11
- import android .security .KeyPairGeneratorSpec ;
12
11
import android .util .Log ;
13
- import java .util .Locale ;
12
+
13
+ import androidx .annotation .Nullable ;
14
+ import androidx .security .crypto .EncryptedSharedPreferences ;
15
+ import androidx .security .crypto .MasterKeys ;
14
16
15
17
import com .facebook .react .bridge .Promise ;
16
18
import com .facebook .react .bridge .ReactApplicationContext ;
17
19
import com .facebook .react .bridge .ReactContextBaseJavaModule ;
18
20
import com .facebook .react .bridge .ReactMethod ;
21
+ import com .facebook .react .bridge .ReadableMap ;
19
22
20
- import java .io .ByteArrayInputStream ;
21
- import java .io .ByteArrayOutputStream ;
22
23
import java .io .FileNotFoundException ;
23
24
import java .io .IOException ;
24
- import java .math .BigInteger ;
25
25
import java .security .GeneralSecurityException ;
26
- import java .security .KeyPairGenerator ;
27
- import java .security .KeyStore ;
28
- import java .security .PrivateKey ;
29
- import java .security .PublicKey ;
30
- import java .util .Calendar ;
31
- import androidx .annotation .Nullable ;
32
- import com .facebook .react .bridge .ReadableMap ;
33
-
34
- import javax .crypto .Cipher ;
35
- import javax .crypto .CipherInputStream ;
36
- import javax .crypto .CipherOutputStream ;
37
- import javax .crypto .KeyGenerator ;
38
- import javax .crypto .SecretKey ;
39
- import javax .crypto .spec .SecretKeySpec ;
40
- import javax .security .auth .x500 .X500Principal ;
41
26
42
27
public class RNSecureKeyStoreModule extends ReactContextBaseJavaModule {
43
28
@@ -56,7 +41,7 @@ public String getName() {
56
41
@ ReactMethod
57
42
public void set (String alias , String input , @ Nullable ReadableMap options , Promise promise ) {
58
43
try {
59
- setCipherText ( alias , input );
44
+ getSecureSharedPreferences (). edit (). putString ( alias , input ). commit ( );
60
45
promise .resolve ("stored ciphertext in app storage" );
61
46
} catch (Exception e ) {
62
47
e .printStackTrace ();
@@ -65,89 +50,26 @@ public void set(String alias, String input, @Nullable ReadableMap options, Promi
65
50
}
66
51
}
67
52
68
- private PublicKey getOrCreatePublicKey (String alias ) throws GeneralSecurityException , IOException {
69
- Locale currentLocale = Locale .getDefault ();
70
- Locale .setDefault (Locale .ENGLISH );
71
- KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
72
- keyStore .load (null );
73
-
74
- if (!keyStore .containsAlias (alias ) || keyStore .getCertificate (alias ) == null ) {
75
- Log .i (Constants .TAG , "no existing asymmetric keys for alias" );
76
-
77
- Calendar start = Calendar .getInstance ();
78
- Calendar end = Calendar .getInstance ();
79
- end .add (Calendar .YEAR , 50 );
80
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec .Builder (getContext ())
81
- .setAlias (alias )
82
- .setSubject (new X500Principal ("CN=" + alias ))
83
- .setSerialNumber (BigInteger .ONE )
84
- .setStartDate (start .getTime ())
85
- .setEndDate (end .getTime ())
86
- .build ();
87
-
88
- KeyPairGenerator generator = KeyPairGenerator .getInstance ("RSA" , getKeyStore ());
89
- generator .initialize (spec );
90
- generator .generateKeyPair ();
91
-
92
- Locale .setDefault (currentLocale );
93
- Log .i (Constants .TAG , "created new asymmetric keys for alias" );
94
- }
95
-
96
- return keyStore .getCertificate (alias ).getPublicKey ();
97
- }
98
-
99
- private byte [] encryptRsaPlainText (PublicKey publicKey , byte [] plainTextBytes ) throws GeneralSecurityException , IOException {
100
- Cipher cipher = Cipher .getInstance (Constants .RSA_ALGORITHM );
101
- cipher .init (Cipher .ENCRYPT_MODE , publicKey );
102
- return encryptCipherText (cipher , plainTextBytes );
103
- }
104
-
105
- private byte [] encryptAesPlainText (SecretKey secretKey , String plainText ) throws GeneralSecurityException , IOException {
106
- Cipher cipher = Cipher .getInstance (Constants .AES_ALGORITHM );
107
- cipher .init (Cipher .ENCRYPT_MODE , secretKey );
108
- return encryptCipherText (cipher , plainText );
109
- }
110
-
111
- private byte [] encryptCipherText (Cipher cipher , String plainText ) throws GeneralSecurityException , IOException {
112
- return encryptCipherText (cipher , plainText .getBytes ("UTF-8" ));
113
- }
114
-
115
- private byte [] encryptCipherText (Cipher cipher , byte [] plainTextBytes ) throws GeneralSecurityException , IOException {
116
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
117
- CipherOutputStream cipherOutputStream = new CipherOutputStream (outputStream , cipher );
118
- cipherOutputStream .write (plainTextBytes );
119
- cipherOutputStream .close ();
120
- return outputStream .toByteArray ();
121
- }
122
-
123
- private SecretKey getOrCreateSecretKey (String alias ) throws GeneralSecurityException , IOException {
124
- try {
125
- return getSymmetricKey (alias );
126
- } catch (FileNotFoundException fnfe ) {
127
- Log .i (Constants .TAG , "no existing symmetric key for alias" );
128
-
129
- KeyGenerator keyGenerator = KeyGenerator .getInstance ("AES" );
130
- //32bytes / 256bits AES key
131
- keyGenerator .init (256 );
132
- SecretKey secretKey = keyGenerator .generateKey ();
133
- PublicKey publicKey = getOrCreatePublicKey (alias );
134
- Storage .writeValues (getContext (), Constants .SKS_KEY_FILENAME + alias ,
135
- encryptRsaPlainText (publicKey , secretKey .getEncoded ()));
136
-
137
- Log .i (Constants .TAG , "created new symmetric keys for alias" );
138
- return secretKey ;
139
- }
140
- }
141
-
142
- private void setCipherText (String alias , String input ) throws GeneralSecurityException , IOException {
143
- Storage .writeValues (getContext (), Constants .SKS_DATA_FILENAME + alias ,
144
- encryptAesPlainText (getOrCreateSecretKey (alias ), input ));
53
+ private SharedPreferences getSecureSharedPreferences () throws GeneralSecurityException , IOException {
54
+ return EncryptedSharedPreferences .create (
55
+ "secret_shared_prefs" ,
56
+ MasterKeys .getOrCreate (MasterKeys .AES256_GCM_SPEC ),
57
+ reactContext ,
58
+ EncryptedSharedPreferences .PrefKeyEncryptionScheme .AES256_SIV ,
59
+ EncryptedSharedPreferences .PrefValueEncryptionScheme .AES256_GCM
60
+ );
145
61
}
146
62
147
63
@ ReactMethod
148
64
public void get (String alias , Promise promise ) {
149
65
try {
150
- promise .resolve (getPlainText (alias ));
66
+ String value = getSecureSharedPreferences ().getString (alias , null );
67
+ if (value == null ) {
68
+ //throw FileNotFoundException to keep match old behaviour when a value is missing
69
+ throw new FileNotFoundException (alias + " has not been set" );
70
+ } else {
71
+ promise .resolve (value );
72
+ }
151
73
} catch (FileNotFoundException fnfe ) {
152
74
fnfe .printStackTrace ();
153
75
promise .reject ("404" , "{\" code\" :404,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + fnfe .getMessage () + "}" , fnfe );
@@ -158,73 +80,15 @@ public void get(String alias, Promise promise) {
158
80
}
159
81
}
160
82
161
- private PrivateKey getPrivateKey (String alias ) throws GeneralSecurityException , IOException {
162
- KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
163
- keyStore .load (null );
164
- return (PrivateKey ) keyStore .getKey (alias , null );
165
- }
166
-
167
- private byte [] decryptRsaCipherText (PrivateKey privateKey , byte [] cipherTextBytes ) throws GeneralSecurityException , IOException {
168
- Cipher cipher = Cipher .getInstance (Constants .RSA_ALGORITHM );
169
- cipher .init (Cipher .DECRYPT_MODE , privateKey );
170
- return decryptCipherText (cipher , cipherTextBytes );
171
- }
172
-
173
- private byte [] decryptAesCipherText (SecretKey secretKey , byte [] cipherTextBytes ) throws GeneralSecurityException , IOException {
174
- Cipher cipher = Cipher .getInstance (Constants .AES_ALGORITHM );
175
- cipher .init (Cipher .DECRYPT_MODE , secretKey );
176
- return decryptCipherText (cipher , cipherTextBytes );
177
- }
178
-
179
- private byte [] decryptCipherText (Cipher cipher , byte [] cipherTextBytes ) throws IOException {
180
- ByteArrayInputStream bais = new ByteArrayInputStream (cipherTextBytes );
181
- CipherInputStream cipherInputStream = new CipherInputStream (bais , cipher );
182
- ByteArrayOutputStream baos = new ByteArrayOutputStream ();
183
- byte [] buffer = new byte [256 ];
184
- int bytesRead = cipherInputStream .read (buffer );
185
- while (bytesRead != -1 ) {
186
- baos .write (buffer , 0 , bytesRead );
187
- bytesRead = cipherInputStream .read (buffer );
188
- }
189
- return baos .toByteArray ();
190
- }
191
-
192
- private SecretKey getSymmetricKey (String alias ) throws GeneralSecurityException , IOException {
193
- byte [] cipherTextBytes = Storage .readValues (getContext (), Constants .SKS_KEY_FILENAME + alias );
194
- return new SecretKeySpec (decryptRsaCipherText (getPrivateKey (alias ), cipherTextBytes ), Constants .AES_ALGORITHM );
195
- }
196
-
197
- public String getPlainText (String alias ) throws GeneralSecurityException , IOException {
198
- SecretKey secretKey = getSymmetricKey (alias );
199
- byte [] cipherTextBytes = Storage .readValues (getContext (), Constants .SKS_DATA_FILENAME + alias );
200
- return new String (decryptAesCipherText (secretKey , cipherTextBytes ), "UTF-8" );
201
- }
202
-
203
83
@ ReactMethod
204
84
public void remove (String alias , Promise promise ) {
205
- Storage .resetValues (getContext (), new String [] {
206
- Constants .SKS_DATA_FILENAME + alias ,
207
- Constants .SKS_KEY_FILENAME + alias ,
208
- });
209
- promise .resolve ("cleared alias" );
210
- }
211
-
212
- private Context getContext () {
213
- return getReactApplicationContext ();
214
- }
215
-
216
- private String getKeyStore () {
217
85
try {
218
- KeyStore .getInstance (Constants .KEYSTORE_PROVIDER_1 );
219
- return Constants .KEYSTORE_PROVIDER_1 ;
220
- } catch (Exception err ) {
221
- try {
222
- KeyStore .getInstance (Constants .KEYSTORE_PROVIDER_2 );
223
- return Constants .KEYSTORE_PROVIDER_2 ;
224
- } catch (Exception e ) {
225
- return Constants .KEYSTORE_PROVIDER_3 ;
226
- }
86
+ getSecureSharedPreferences ().edit ().remove (alias ).commit ();
87
+ promise .resolve ("cleared alias" );
88
+ } catch (Exception e ) {
89
+ e .printStackTrace ();
90
+ Log .e (Constants .TAG , "Exception: " + e .getMessage ());
91
+ promise .reject ("{\" code\" :6,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
227
92
}
228
93
}
229
-
230
94
}
0 commit comments