6
6
7
7
package com .reactlibrary .securekeystore ;
8
8
9
- import com .facebook .react .bridge .NativeModule ;
9
+ import android .content .Context ;
10
+ import android .os .Build ;
11
+ import android .security .KeyPairGeneratorSpec ;
12
+ import android .util .Log ;
13
+
14
+ import com .facebook .react .bridge .Promise ;
10
15
import com .facebook .react .bridge .ReactApplicationContext ;
11
- import com .facebook .react .bridge .ReactContext ;
12
16
import com .facebook .react .bridge .ReactContextBaseJavaModule ;
13
17
import com .facebook .react .bridge .ReactMethod ;
14
- import com .facebook .react .bridge .Promise ;
15
18
16
- import android .content .Context ;
17
- import android .util .Log ;
18
- import android .util .Base64 ;
19
- import android .security .KeyPairGeneratorSpec ;
20
- import android .os .Build ;
21
-
22
- import java .security .*;
23
- import java .math .BigInteger ;
24
- import java .util .ArrayList ;
25
19
import java .io .ByteArrayInputStream ;
26
20
import java .io .ByteArrayOutputStream ;
27
- import java .io .FileInputStream ;
28
- import java .io .FileOutputStream ;
29
- import java .lang .StringBuffer ;
21
+ import java .io .FileNotFoundException ;
22
+ import java .io .IOException ;
23
+ import java .math .BigInteger ;
24
+ import java .security .GeneralSecurityException ;
25
+ import java .security .KeyPairGenerator ;
26
+ import java .security .KeyStore ;
27
+ import java .security .PrivateKey ;
28
+ import java .security .PublicKey ;
30
29
import java .util .Calendar ;
31
30
32
31
import javax .crypto .Cipher ;
33
32
import javax .crypto .CipherInputStream ;
34
33
import javax .crypto .CipherOutputStream ;
34
+ import javax .crypto .KeyGenerator ;
35
+ import javax .crypto .SecretKey ;
36
+ import javax .crypto .spec .SecretKeySpec ;
35
37
import javax .security .auth .x500 .X500Principal ;
36
38
37
39
public class RNSecureKeyStoreModule extends ReactContextBaseJavaModule {
@@ -50,104 +52,163 @@ public String getName() {
50
52
51
53
@ ReactMethod
52
54
public void set (String alias , String input , Promise promise ) {
53
-
54
55
try {
56
+ setCipherText (alias , input );
57
+ promise .resolve ("stored ciphertext in app storage" );
58
+ } catch (Exception e ) {
59
+ e .printStackTrace ();
60
+ Log .e (Constants .TAG , "Exception: " + e .getMessage ());
61
+ promise .reject ("{\" code\" :9,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
62
+ }
63
+ }
55
64
56
- KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
57
- keyStore .load (null );
58
-
59
- if (!keyStore .containsAlias (alias )) {
60
- Calendar start = Calendar .getInstance ();
61
- Calendar end = Calendar .getInstance ();
62
- end .add (Calendar .YEAR , 1 );
63
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec .Builder (getContext ())
64
- .setAlias (alias )
65
- .setSubject (new X500Principal ("CN=" + alias ))
66
- .setSerialNumber (BigInteger .ONE )
67
- .setStartDate (start .getTime ())
68
- .setEndDate (end .getTime ()).build ();
69
-
70
- KeyPairGenerator generator = KeyPairGenerator .getInstance ("RSA" , getKeyStore ());
71
- generator .initialize (spec );
65
+ private PublicKey getOrCreatePublicKey (String alias ) throws GeneralSecurityException , IOException {
66
+ KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
67
+ keyStore .load (null );
72
68
73
- KeyPair keyPair = generator .generateKeyPair ();
69
+ if (!keyStore .containsAlias (alias )) {
70
+ Log .i (Constants .TAG , "no existing asymmetric keys for alias" );
74
71
75
- Log .i (Constants .TAG , "created new key pairs" );
76
- }
72
+ Calendar start = Calendar .getInstance ();
73
+ Calendar end = Calendar .getInstance ();
74
+ end .add (Calendar .YEAR , 50 );
75
+ KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec .Builder (getContext ())
76
+ .setAlias (alias )
77
+ .setSubject (new X500Principal ("CN=" + alias ))
78
+ .setSerialNumber (BigInteger .ONE )
79
+ .setStartDate (start .getTime ())
80
+ .setEndDate (end .getTime ()).build ();
77
81
78
- PublicKey publicKey = keyStore .getCertificate (alias ).getPublicKey ();
82
+ KeyPairGenerator generator = KeyPairGenerator .getInstance ("RSA" , getKeyStore ());
83
+ generator .initialize (spec );
84
+ generator .generateKeyPair ();
79
85
80
- if (input .isEmpty ()) {
81
- Log .d (Constants .TAG , "Exception: input text is empty" );
82
- return ;
83
- }
86
+ Log .i (Constants .TAG , "created new asymmetric keys for alias" );
87
+ }
84
88
85
- Cipher cipher = Cipher .getInstance (Constants .RSA_ALGORITHM );
86
- cipher .init (Cipher .ENCRYPT_MODE , publicKey );
87
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
88
- CipherOutputStream cipherOutputStream = new CipherOutputStream (outputStream , cipher );
89
- cipherOutputStream .write (input .getBytes ("UTF-8" ));
90
- cipherOutputStream .close ();
91
- byte [] vals = outputStream .toByteArray ();
89
+ return keyStore .getCertificate (alias ).getPublicKey ();
90
+ }
92
91
93
- // writing key to storage
94
- KeyStorage .writeValues (getContext (), alias , vals );
95
- Log .i (Constants .TAG , "key created and stored successfully" );
96
- promise .resolve ("key stored successfully" );
92
+ private byte [] encryptRsaPlainText (PublicKey publicKey , byte [] plainTextBytes ) throws GeneralSecurityException , IOException {
93
+ Cipher cipher = Cipher .getInstance (Constants .RSA_ALGORITHM );
94
+ cipher .init (Cipher .ENCRYPT_MODE , publicKey );
95
+ return encryptCipherText (cipher , plainTextBytes );
96
+ }
97
97
98
- } catch (Exception e ) {
99
- Log .e (Constants .TAG , "Exception: " + e .getMessage ());
100
- promise .reject ("{\" code\" :9,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
101
- }
98
+ private byte [] encryptAesPlainText (SecretKey secretKey , String plainText ) throws GeneralSecurityException , IOException {
99
+ Cipher cipher = Cipher .getInstance (Constants .AES_ALGORITHM );
100
+ cipher .init (Cipher .ENCRYPT_MODE , secretKey );
101
+ return encryptCipherText (cipher , plainText );
102
+ }
102
103
104
+ private byte [] encryptCipherText (Cipher cipher , String plainText ) throws GeneralSecurityException , IOException {
105
+ return encryptCipherText (cipher , plainText .getBytes ("UTF-8" ));
103
106
}
104
107
105
- @ ReactMethod
106
- public void get (String alias , Promise promise ) {
108
+ private byte [] encryptCipherText (Cipher cipher , byte [] plainTextBytes ) throws GeneralSecurityException , IOException {
109
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
110
+ CipherOutputStream cipherOutputStream = new CipherOutputStream (outputStream , cipher );
111
+ cipherOutputStream .write (plainTextBytes );
112
+ cipherOutputStream .close ();
113
+ return outputStream .toByteArray ();
114
+ }
107
115
116
+ private SecretKey getOrCreateSecretKey (String alias ) throws GeneralSecurityException , IOException {
108
117
try {
118
+ return getSymmetricKey (alias );
119
+ } catch (FileNotFoundException fnfe ) {
120
+ Log .i (Constants .TAG , "no existing symmetric key for alias" );
109
121
110
- KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
111
- keyStore .load (null );
112
- PrivateKey privateKey = (PrivateKey ) keyStore .getKey (alias , null );
122
+ KeyGenerator keyGenerator = KeyGenerator .getInstance ("AES" );
123
+ //32bytes / 256bits AES key
124
+ keyGenerator .init (256 );
125
+ SecretKey secretKey = keyGenerator .generateKey ();
126
+ PublicKey publicKey = getOrCreatePublicKey (alias );
113
127
114
- Cipher output = Cipher .getInstance (Constants .RSA_ALGORITHM );
115
- output .init (Cipher .DECRYPT_MODE , privateKey );
116
- CipherInputStream cipherInputStream = new CipherInputStream (
117
- new ByteArrayInputStream (KeyStorage .readValues (getContext (), alias )), output );
118
128
119
- ArrayList <Byte > values = new ArrayList <Byte >();
120
- int nextByte ;
121
- while ((nextByte = cipherInputStream .read ()) != -1 ) {
122
- values .add ((byte ) nextByte );
123
- }
124
- byte [] bytes = new byte [values .size ()];
125
- for (int i = 0 ; i < bytes .length ; i ++) {
126
- bytes [i ] = values .get (i ).byteValue ();
127
- }
129
+ //TODO: develop only
130
+ Log .d (Constants .TAG , "saving secret key: " + new String (secretKey .getEncoded (), "UTF-8" ));
128
131
129
- String finalText = new String (bytes , 0 , bytes .length , "UTF-8" );
130
- promise .resolve (finalText );
131
132
132
- } catch (Exception e ) {
133
- Log .e (Constants .TAG , "Exception: " + e .getMessage ());
134
- promise .reject ("{\" code\" :1,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
133
+ Storage .writeValues (getContext (), Constants .SKS_KEY_FILENAME + alias ,
134
+ encryptRsaPlainText (publicKey , secretKey .getEncoded ()));
135
+
136
+ Log .i (Constants .TAG , "created new symmetric keys for alias" );
137
+ return secretKey ;
135
138
}
136
139
}
137
140
141
+ private void setCipherText (String alias , String input ) throws GeneralSecurityException , IOException {
142
+ Storage .writeValues (getContext (), Constants .SKS_DATA_FILENAME + alias ,
143
+ encryptAesPlainText (getOrCreateSecretKey (alias ), input ));
144
+ }
145
+
138
146
@ ReactMethod
139
- public void remove (String alias , Promise promise ) {
147
+ public void get (String alias , Promise promise ) {
140
148
try {
141
- KeyStorage .resetValues (getContext (), alias );
142
- Log .i (Constants .TAG , "key removed successfully" );
143
- promise .resolve ("key removed successfully" );
144
-
149
+ promise .resolve (getPlainText (alias ));
145
150
} catch (Exception e ) {
151
+ e .printStackTrace ();
146
152
Log .e (Constants .TAG , "Exception: " + e .getMessage ());
147
- promise .reject ("{\" code\" :6 ,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
153
+ promise .reject ("{\" code\" :1 ,\" api-level\" :" + Build .VERSION .SDK_INT + ",\" message\" :" + e .getMessage () + "}" );
148
154
}
149
155
}
150
156
157
+ private PrivateKey getPrivateKey (String alias ) throws GeneralSecurityException , IOException {
158
+ KeyStore keyStore = KeyStore .getInstance (getKeyStore ());
159
+ keyStore .load (null );
160
+ return (PrivateKey ) keyStore .getKey (alias , null );
161
+ }
162
+
163
+ private byte [] decryptRsaCipherText (PrivateKey privateKey , byte [] cipherTextBytes ) throws GeneralSecurityException , IOException {
164
+ Cipher cipher = Cipher .getInstance (Constants .RSA_ALGORITHM );
165
+ cipher .init (Cipher .DECRYPT_MODE , privateKey );
166
+ return decryptCipherText (cipher , cipherTextBytes );
167
+ }
168
+
169
+ private byte [] decryptAesCipherText (SecretKey secretKey , byte [] cipherTextBytes ) throws GeneralSecurityException , IOException {
170
+ Cipher cipher = Cipher .getInstance (Constants .AES_ALGORITHM );
171
+ cipher .init (Cipher .DECRYPT_MODE , secretKey );
172
+ return decryptCipherText (cipher , cipherTextBytes );
173
+ }
174
+
175
+ private byte [] decryptCipherText (Cipher cipher , byte [] cipherTextBytes ) throws IOException {
176
+ ByteArrayInputStream bais = new ByteArrayInputStream (cipherTextBytes );
177
+ CipherInputStream cipherInputStream = new CipherInputStream (bais , cipher );
178
+ ByteArrayOutputStream baos = new ByteArrayOutputStream ();
179
+ byte [] buffer = new byte [256 ];
180
+ int bytesRead = cipherInputStream .read (buffer );
181
+ while (bytesRead != -1 ) {
182
+ baos .write (buffer , 0 , bytesRead );
183
+ bytesRead = cipherInputStream .read (buffer );
184
+ }
185
+ return baos .toByteArray ();
186
+ }
187
+
188
+ private SecretKey getSymmetricKey (String alias ) throws GeneralSecurityException , IOException {
189
+ byte [] cipherTextBytes = Storage .readValues (getContext (), Constants .SKS_KEY_FILENAME + alias );
190
+
191
+ //TODO: develop only
192
+ Log .d (Constants .TAG , "reading secret key: " + new String (decryptRsaCipherText (getPrivateKey (alias ), cipherTextBytes ), "UTF-8" ));
193
+
194
+ return new SecretKeySpec (decryptRsaCipherText (getPrivateKey (alias ), cipherTextBytes ), Constants .AES_ALGORITHM );
195
+ }
196
+
197
+ private 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
+ @ ReactMethod
204
+ 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
+
151
212
private Context getContext () {
152
213
return getReactApplicationContext ();
153
214
}
0 commit comments