3
3
import android .content .Context ;
4
4
import android .content .SharedPreferences ;
5
5
import android .os .Build ;
6
- import android .security .keystore .KeyGenParameterSpec ;
7
- import android .security .keystore .KeyProperties ;
6
+ import android .security .KeyPairGeneratorSpec ;
8
7
import android .util .Base64 ;
9
8
import android .util .Log ;
10
9
11
10
import org .json .JSONObject ;
12
11
12
+ import java .io .ByteArrayInputStream ;
13
+ import java .io .ByteArrayOutputStream ;
13
14
import java .io .IOException ;
14
- import java .io . UnsupportedEncodingException ;
15
+ import java .math . BigInteger ;
15
16
import java .security .InvalidAlgorithmParameterException ;
16
17
import java .security .InvalidKeyException ;
18
+ import java .security .KeyPair ;
19
+ import java .security .KeyPairGenerator ;
17
20
import java .security .KeyStore ;
18
21
import java .security .KeyStoreException ;
19
22
import java .security .NoSuchAlgorithmException ;
20
23
import java .security .NoSuchProviderException ;
24
+ import java .security .PublicKey ;
25
+ import java .security .SecureRandom ;
21
26
import java .security .UnrecoverableEntryException ;
22
27
import java .security .cert .CertificateException ;
28
+ import java .util .ArrayList ;
29
+ import java .util .Calendar ;
23
30
24
31
import javax .crypto .BadPaddingException ;
25
32
import javax .crypto .Cipher ;
33
+ import javax .crypto .CipherInputStream ;
34
+ import javax .crypto .CipherOutputStream ;
26
35
import javax .crypto .IllegalBlockSizeException ;
27
- import javax .crypto .KeyGenerator ;
28
36
import javax .crypto .NoSuchPaddingException ;
29
- import javax .crypto .SecretKey ;
30
- import javax .crypto . spec . GCMParameterSpec ;
37
+ import javax .crypto .spec . SecretKeySpec ;
38
+ import javax .security . auth . x500 . X500Principal ;
31
39
32
40
/**
33
41
* The Skygear secure persistent store.
38
46
class SecurePersistentStore extends PersistentStore {
39
47
private static final String TAG = "Skygear SDK" ;
40
48
41
- static final String ACCESS_TOKEN_KEY_ALIAS = "SkygearAccessTokenKey" ;
42
- static final String CURRENT_USER_KEY_ALIAS = "SkygearCurrentUserKey" ;
49
+ static final String KEY_ALIAS = "SkygearKey" ;
43
50
44
51
static final String CURRENT_USER_ENCRYPTED_KEY = "current_user_encrypted" ;
45
52
static final String ACCESS_TOKEN_ENCRYPTED_KEY = "access_token_encrypted" ;
46
- static final String CURRENT_USER_IV_KEY = "current_user_iv " ;
47
- static final String ACCESS_TOKEN_IV_KEY = "access_token_iv " ;
53
+ static final String CURRENT_USER_ENCRYPTED_KEY_KEY = "current_user_encrypted_key " ;
54
+ static final String ACCESS_TOKEN_ENCRYPTED_KEY_KEY = "access_token_encrypted_key " ;
48
55
49
- private static final String TRANSFORMATION = "AES/GCM/NoPadding" ;
56
+ private static final String RSA_MODE = "RSA/ECB/PKCS1Padding" ;
57
+ private static final String AES_MODE = "AES/ECB/PKCS7Padding" ;
50
58
private static final String ANDROID_KEY_STORE = "AndroidKeyStore" ;
51
59
52
60
Encryptor encryptor ;
@@ -59,17 +67,15 @@ public SecurePersistentStore(Context context) {
59
67
@ Override
60
68
void restoreAuthUser (SharedPreferences pref ) {
61
69
String encryptedCurrentUser = pref .getString (CURRENT_USER_ENCRYPTED_KEY , null );
62
- String encodedIV = pref .getString (CURRENT_USER_IV_KEY , null );
70
+ String encryptedKey = pref .getString (CURRENT_USER_ENCRYPTED_KEY_KEY , null );
63
71
64
- if (encryptedCurrentUser == null || encodedIV == null ) {
72
+ if (encryptedCurrentUser == null || encryptedKey == null ) {
65
73
this .currentUser = null ;
66
74
return ;
67
75
}
68
76
69
77
try {
70
- byte [] encryptedData = base64DecodeToByte (encryptedCurrentUser );
71
- byte [] iv = base64DecodeToByte (encodedIV );
72
- String currentUserString = getDecryptor ().decryptData (CURRENT_USER_KEY_ALIAS , encryptedData , iv );
78
+ String currentUserString = getDecryptor ().decryptData (encryptedKey , encryptedCurrentUser );
73
79
this .currentUser = RecordSerializer .deserialize (
74
80
new JSONObject (currentUserString )
75
81
);
@@ -84,34 +90,30 @@ void saveAuthUser(SharedPreferences.Editor prefEditor) {
84
90
if (this .currentUser != null ) {
85
91
String currentUserString = RecordSerializer .serialize (this .currentUser ).toString ();
86
92
try {
87
- EncryptedResult r = getEncryptor ().encryptText (CURRENT_USER_KEY_ALIAS , currentUserString );
88
- String encryptedCurrentUser = base64EncodeToString (r .encryptedData );
89
- String encodedIV = base64EncodeToString (r .initializationVector );
90
- prefEditor .putString (CURRENT_USER_ENCRYPTED_KEY , encryptedCurrentUser );
91
- prefEditor .putString (CURRENT_USER_IV_KEY , encodedIV );
93
+ EncryptedResult r = getEncryptor ().encryptText (currentUserString );
94
+ prefEditor .putString (CURRENT_USER_ENCRYPTED_KEY , r .encryptedData );
95
+ prefEditor .putString (CURRENT_USER_ENCRYPTED_KEY_KEY , r .encryptedKey );
92
96
} catch (Exception e ) {
93
97
Log .w (TAG , "Fail to encrypt and save current user object" , e );
94
98
}
95
99
} else {
96
100
prefEditor .remove (CURRENT_USER_ENCRYPTED_KEY );
97
- prefEditor .remove (CURRENT_USER_IV_KEY );
101
+ prefEditor .remove (CURRENT_USER_ENCRYPTED_KEY_KEY );
98
102
}
99
103
}
100
104
101
105
@ Override
102
106
void restoreAccessToken (SharedPreferences pref ) {
103
107
String encryptedAccessToken = pref .getString (ACCESS_TOKEN_ENCRYPTED_KEY , null );
104
- String encodedIV = pref .getString (ACCESS_TOKEN_IV_KEY , null );
108
+ String encryptedKey = pref .getString (ACCESS_TOKEN_ENCRYPTED_KEY_KEY , null );
105
109
106
- if (encryptedAccessToken == null || encodedIV == null ) {
110
+ if (encryptedAccessToken == null || encryptedKey == null ) {
107
111
this .accessToken = null ;
108
112
return ;
109
113
}
110
114
111
115
try {
112
- byte [] encryptedData = base64DecodeToByte (encryptedAccessToken );
113
- byte [] iv = base64DecodeToByte (encodedIV );
114
- this .accessToken = getDecryptor ().decryptData (ACCESS_TOKEN_KEY_ALIAS , encryptedData , iv );
116
+ this .accessToken = getDecryptor ().decryptData (encryptedKey , encryptedAccessToken );
115
117
} catch (Exception e ) {
116
118
Log .w (TAG , "Fail to decrypt saved access token" , e );
117
119
this .accessToken = null ;
@@ -122,105 +124,179 @@ void restoreAccessToken(SharedPreferences pref) {
122
124
void saveAccessToken (SharedPreferences .Editor prefEditor ) {
123
125
if (this .accessToken != null ) {
124
126
try {
125
- EncryptedResult r = getEncryptor ().encryptText (ACCESS_TOKEN_KEY_ALIAS , this .accessToken );
126
- String ecryptedAccessToken = base64EncodeToString (r .encryptedData );
127
- String encodedIV = base64EncodeToString (r .initializationVector );
128
- prefEditor .putString (ACCESS_TOKEN_ENCRYPTED_KEY , ecryptedAccessToken );
129
- prefEditor .putString (ACCESS_TOKEN_IV_KEY , encodedIV );
127
+ EncryptedResult r = getEncryptor ().encryptText (this .accessToken );
128
+ prefEditor .putString (ACCESS_TOKEN_ENCRYPTED_KEY , r .encryptedData );
129
+ prefEditor .putString (ACCESS_TOKEN_ENCRYPTED_KEY_KEY , r .encryptedKey );
130
130
} catch (Exception e ) {
131
131
Log .w (TAG , "Fail to encrypt and save access token" , e );
132
132
}
133
133
} else {
134
134
prefEditor .remove (ACCESS_TOKEN_ENCRYPTED_KEY );
135
- prefEditor .remove (ACCESS_TOKEN_IV_KEY );
135
+ prefEditor .remove (ACCESS_TOKEN_ENCRYPTED_KEY_KEY );
136
136
}
137
137
}
138
138
139
- protected String base64EncodeToString (byte [] b ) {
140
- return Base64 .encodeToString (b , Base64 .DEFAULT );
141
- }
142
-
143
- protected byte [] base64DecodeToByte (String s ) {
144
- return Base64 .decode (s , Base64 .DEFAULT );
145
- }
146
-
147
- protected Encryptor getEncryptor () {
139
+ private Encryptor getEncryptor () {
148
140
if (encryptor == null ) {
149
- encryptor = new Encryptor ();
141
+ encryptor = new Encryptor (this . context );
150
142
}
151
143
return encryptor ;
152
144
}
153
145
154
- protected Decryptor getDecryptor () {
146
+ private Decryptor getDecryptor () {
155
147
if (decryptor == null ) {
156
148
decryptor = new Decryptor ();
157
149
}
158
150
return decryptor ;
159
151
}
160
152
153
+ private String getRSACipherProvider () {
154
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .M ) {
155
+ return "AndroidOpenSSL" ;
156
+ } else {
157
+ return "AndroidKeyStoreBCWorkaround" ;
158
+ }
159
+ }
160
+
161
161
class Decryptor {
162
162
163
163
private KeyStore keyStore ;
164
164
165
- String decryptData (final String alias , final byte [] encrypted , final byte [] iv ) throws NoSuchPaddingException ,
166
- NoSuchAlgorithmException , UnrecoverableEntryException , KeyStoreException , InvalidAlgorithmParameterException ,
167
- InvalidKeyException , BadPaddingException , IllegalBlockSizeException , IOException , CertificateException {
168
- final Cipher cipher = Cipher .getInstance (TRANSFORMATION );
169
- final GCMParameterSpec spec = new GCMParameterSpec (128 , iv );
170
- cipher .init (Cipher .DECRYPT_MODE , getSecretKey (alias ), spec );
171
- return new String (cipher .doFinal (encrypted ), "UTF-8" );
172
- }
165
+ private byte [] rsaDecrypt (byte [] encrypted ) throws KeyStoreException ,
166
+ CertificateException , NoSuchAlgorithmException , IOException , NoSuchProviderException ,
167
+ NoSuchPaddingException , InvalidKeyException , UnrecoverableEntryException {
173
168
174
- private SecretKey getSecretKey (final String alias ) throws UnrecoverableEntryException ,
175
- NoSuchAlgorithmException , KeyStoreException , IOException , CertificateException {
176
169
if (keyStore == null ) {
177
170
keyStore = KeyStore .getInstance (ANDROID_KEY_STORE );
178
171
keyStore .load (null );
179
172
}
180
- return ((KeyStore .SecretKeyEntry ) keyStore .getEntry (alias , null )).getSecretKey ();
173
+ KeyStore .PrivateKeyEntry privateKeyEntry = (KeyStore .PrivateKeyEntry )keyStore .getEntry (
174
+ KEY_ALIAS , null );
175
+ Cipher output = Cipher .getInstance (RSA_MODE , getRSACipherProvider ());
176
+ output .init (Cipher .DECRYPT_MODE , privateKeyEntry .getPrivateKey ());
177
+ CipherInputStream cipherInputStream = new CipherInputStream (
178
+ new ByteArrayInputStream (encrypted ), output );
179
+ ArrayList <Byte > values = new ArrayList <>();
180
+ int nextByte ;
181
+ while ((nextByte = cipherInputStream .read ()) != -1 ) {
182
+ values .add ((byte )nextByte );
183
+ }
184
+
185
+ byte [] bytes = new byte [values .size ()];
186
+ for (int i = 0 ; i < bytes .length ; i ++) {
187
+ bytes [i ] = values .get (i ).byteValue ();
188
+ }
189
+ return bytes ;
190
+ }
191
+
192
+ String decryptData (final String encryptedKey , final String encryptedData ) throws NoSuchPaddingException ,
193
+ NoSuchAlgorithmException , UnrecoverableEntryException , KeyStoreException ,
194
+ InvalidKeyException , IOException , CertificateException , NoSuchProviderException ,
195
+ BadPaddingException , IllegalBlockSizeException {
196
+ byte [] encryptedKeyBytes = Base64 .decode (encryptedKey , Base64 .DEFAULT );
197
+ byte [] encryptedDataBytes = Base64 .decode (encryptedData , Base64 .DEFAULT );
198
+ byte [] secretKey = rsaDecrypt (encryptedKeyBytes );
199
+
200
+ Cipher c = Cipher .getInstance (AES_MODE , "BC" );
201
+ c .init (Cipher .DECRYPT_MODE , new SecretKeySpec (secretKey , "AES" ));
202
+ byte [] decodedBytes = c .doFinal (encryptedDataBytes );
203
+ return new String (decodedBytes , "UTF-8" );
181
204
}
182
205
}
183
206
184
207
class Encryptor {
185
208
186
- EncryptedResult encryptText (final String alias , final String textToEncrypt ) throws NoSuchPaddingException ,
187
- NoSuchAlgorithmException , NoSuchProviderException , InvalidAlgorithmParameterException ,
188
- InvalidKeyException , BadPaddingException , IllegalBlockSizeException , UnsupportedEncodingException {
209
+ private final Context context ;
210
+ private KeyStore keyStore ;
189
211
190
- final Cipher cipher = Cipher .getInstance (TRANSFORMATION );
191
- cipher .init (Cipher .ENCRYPT_MODE , getSecretKey (alias ));
192
- byte [] iv = cipher .getIV ();
212
+ Encryptor (Context context ) {
213
+ super ();
214
+ this .context = context ;
215
+ }
193
216
194
- return new EncryptedResult (cipher .doFinal (textToEncrypt .getBytes ("UTF-8" )), iv );
217
+ EncryptedResult encryptText (final String textToEncrypt ) throws NoSuchPaddingException ,
218
+ NoSuchAlgorithmException , NoSuchProviderException , InvalidKeyException , IOException ,
219
+ BadPaddingException , IllegalBlockSizeException , CertificateException , KeyStoreException ,
220
+ UnrecoverableEntryException , InvalidAlgorithmParameterException {
221
+ byte [] secretKey = generateAESSecret ();
222
+ byte [] encryptedKeyBytes = rsaEncrypt (secretKey );
223
+ String encryptedKey = Base64 .encodeToString (encryptedKeyBytes , Base64 .DEFAULT );
224
+
225
+ Cipher c = Cipher .getInstance (AES_MODE , "BC" );
226
+ c .init (Cipher .ENCRYPT_MODE , new SecretKeySpec (secretKey , "AES" ));
227
+ byte [] encodedBytes = c .doFinal (textToEncrypt .getBytes ("UTF-8" ));
228
+ String encryptedBase64Encoded = Base64 .encodeToString (encodedBytes , Base64 .DEFAULT );
229
+ return new EncryptedResult (encryptedKey , encryptedBase64Encoded );
195
230
}
196
231
197
- private SecretKey getSecretKey (final String alias ) throws NoSuchAlgorithmException ,
198
- NoSuchProviderException , InvalidAlgorithmParameterException {
199
- KeyGenerator keyGenerator ;
200
-
201
- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
202
- keyGenerator = KeyGenerator .getInstance (KeyProperties .KEY_ALGORITHM_AES , ANDROID_KEY_STORE );
203
- keyGenerator .init (new KeyGenParameterSpec .Builder (
204
- alias , KeyProperties .PURPOSE_ENCRYPT | KeyProperties .PURPOSE_DECRYPT )
205
- .setBlockModes (KeyProperties .BLOCK_MODE_GCM )
206
- .setEncryptionPaddings (KeyProperties .ENCRYPTION_PADDING_NONE )
207
- .build ());
208
- } else {
209
- keyGenerator = KeyGenerator .getInstance (ANDROID_KEY_STORE );
210
- // fixup init keyGenerator
232
+ private byte [] generateAESSecret () {
233
+ byte [] key = new byte [16 ];
234
+ SecureRandom secureRandom = new SecureRandom ();
235
+ secureRandom .nextBytes (key );
236
+ return key ;
237
+ }
238
+
239
+ private byte [] rsaEncrypt (byte [] secret ) throws NoSuchAlgorithmException ,
240
+ IOException , NoSuchProviderException , NoSuchPaddingException ,
241
+ InvalidKeyException , InvalidAlgorithmParameterException , CertificateException ,
242
+ KeyStoreException , UnrecoverableEntryException {
243
+ PublicKey publicKey = getRSAPublicKey ();
244
+ Cipher inputCipher = Cipher .getInstance (RSA_MODE , getRSACipherProvider ());
245
+ inputCipher .init (Cipher .ENCRYPT_MODE , publicKey );
246
+
247
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
248
+ CipherOutputStream cipherOutputStream = new CipherOutputStream (outputStream , inputCipher );
249
+ cipherOutputStream .write (secret );
250
+ cipherOutputStream .close ();
251
+
252
+ byte [] vals = outputStream .toByteArray ();
253
+ return vals ;
254
+ }
255
+
256
+ private PublicKey getRSAPublicKey () throws NoSuchAlgorithmException ,
257
+ NoSuchProviderException , InvalidAlgorithmParameterException , KeyStoreException ,
258
+ IOException , CertificateException , UnrecoverableEntryException {
259
+ if (keyStore == null ) {
260
+ keyStore = KeyStore .getInstance (ANDROID_KEY_STORE );
261
+ keyStore .load (null );
262
+ }
263
+
264
+ if (!keyStore .containsAlias (KEY_ALIAS )) {
265
+ return generateRSAPublicKey (KEY_ALIAS );
211
266
}
212
- return keyGenerator .generateKey ();
267
+
268
+ return ((KeyStore .PrivateKeyEntry ) keyStore .getEntry (KEY_ALIAS , null ))
269
+ .getCertificate ().getPublicKey ();
213
270
}
214
271
272
+ private PublicKey generateRSAPublicKey (final String alias ) throws NoSuchAlgorithmException ,
273
+ NoSuchProviderException , InvalidAlgorithmParameterException {
274
+ Calendar start = Calendar .getInstance ();
275
+ Calendar end = Calendar .getInstance ();
276
+ end .add (Calendar .YEAR , 1 );
277
+ KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec .Builder (this .context )
278
+ .setAlias (alias )
279
+ .setSerialNumber (BigInteger .ONE )
280
+ .setSubject (new X500Principal ("CN=" + alias ))
281
+ .setStartDate (start .getTime ())
282
+ .setEndDate (end .getTime ())
283
+ .build ();
284
+ KeyPairGenerator generator = KeyPairGenerator .getInstance ("RSA" , ANDROID_KEY_STORE );
285
+ generator .initialize (spec );
286
+
287
+ KeyPair keyPair = generator .generateKeyPair ();
288
+ return keyPair .getPublic ();
289
+ }
215
290
}
216
291
217
292
class EncryptedResult {
218
- byte [] encryptedData ;
219
- byte [] initializationVector ;
293
+ String encryptedKey ;
294
+ String encryptedData ;
220
295
221
- EncryptedResult (byte [] data , byte [] iv ) {
222
- encryptedData = data ;
223
- initializationVector = iv ;
296
+ EncryptedResult (String encryptedKey , String encryptedData ) {
297
+ super ();
298
+ this .encryptedKey = encryptedKey ;
299
+ this .encryptedData = encryptedData ;
224
300
}
225
301
}
226
302
}
0 commit comments