1
/*
2
* Copyright (C) 2013 The Android Open Source Project
3
*
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
7
*
8
* http://www.apache.org/licenses/LICENSE-2.0
9
*
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*/
16
17
package com.example.android.basicandroidkeystore;
18
19
import android.content.Context;
20
import android.os.Bundle;
21
import android.security.KeyPairGeneratorSpec;
22
import android.support.v4.app.Fragment;
23
import android.util.Base64;
24
import android.view.MenuItem;
25
26
import com.example.android.common.logger.Log;
27
28
import java.io.IOException;
29
import java.math.BigInteger;
30
import java.security.InvalidAlgorithmParameterException;
31
import java.security.InvalidKeyException;
32
import java.security.KeyPair;
33
import java.security.KeyPairGenerator;
34
import java.security.KeyStore;
35
import java.security.KeyStoreException;
36
import java.security.NoSuchAlgorithmException;
37
import java.security.NoSuchProviderException;
38
import java.security.Signature;
39
import java.security.SignatureException;
40
import java.security.UnrecoverableEntryException;
41
import java.security.cert.CertificateException;
42
import java.util.Calendar;
43
import java.util.GregorianCalendar;
44
45
import javax.security.auth.x500.X500Principal;
46
47
public class BasicAndroidKeyStoreFragment extends Fragment {
48
49
public static final String TAG = "BasicAndroidKeyStoreFragment";
50
52
53
public static final String SAMPLE_ALIAS = "myKey";
54
55
// Some sample data to sign, and later verify using the generated signature.
56
public static final String SAMPLE_INPUT="Hello, Android!";
57
58
// Just a handy place to store the signature in between signing and verifying.
59
public String mSignatureStr = null;
60
61
// You can store multiple key pairs in the Key Store. The string used to refer to the Key you
62
// want to store, or later pull, is referred to as an "alias" in this case, because calling it
63
// a key, when you use it to retrieve a key, would just be irritating.
64
private String mAlias = null;
65
67
68
@Override
69
public void onCreate(Bundle savedInstanceState) {
70
super.onCreate(savedInstanceState);
71
setHasOptionsMenu(true);
72
setAlias(SAMPLE_ALIAS);
73
}
74
75
@Override
76
public void onActivityCreated(Bundle savedInstanceState) {
77
super.onActivityCreated(savedInstanceState);
78
}
79
80
@Override
81
public boolean onOptionsItemSelected(MenuItem item) {
82
switch (item.getItemId()) {
83
case R.id.btn_create_keys:
84
try {
85
createKeys(getActivity());
86
Log.d(TAG, "Keys created");
87
return true;
88
} catch (NoSuchAlgorithmException e) {
89
Log.w(TAG, "RSA not supported", e);
90
} catch (InvalidAlgorithmParameterException e) {
91
Log.w(TAG, "No such provider: AndroidKeyStore");
92
} catch (NoSuchProviderException e) {
93
Log.w(TAG, "Invalid Algorithm Parameter Exception", e);
94
}
95
return true;
96
case R.id.btn_sign_data:
97
try {
98
mSignatureStr = signData(SAMPLE_INPUT);
99
} catch (KeyStoreException e) {
100
Log.w(TAG, "KeyStore not Initialized", e);
101
} catch (UnrecoverableEntryException e) {
102
Log.w(TAG, "KeyPair not recovered", e);
103
} catch (NoSuchAlgorithmException e) {
104
Log.w(TAG, "RSA not supported", e);
105
} catch (InvalidKeyException e) {
106
Log.w(TAG, "Invalid Key", e);
107
} catch (SignatureException e) {
108
Log.w(TAG, "Invalid Signature", e);
109
} catch (IOException e) {
110
Log.w(TAG, "IO Exception", e);
111
} catch (CertificateException e) {
112
Log.w(TAG, "Error occurred while loading certificates", e);
113
}
114
Log.d(TAG, "Signature: " + mSignatureStr);
115
return true;
116
117
case R.id.btn_verify_data:
118
boolean verified = false;
119
try {
120
if (mSignatureStr != null) {
121
verified = verifyData(SAMPLE_INPUT, mSignatureStr);
122
}
123
} catch (KeyStoreException e) {
124
Log.w(TAG, "KeyStore not Initialized", e);
125
} catch (CertificateException e) {
126
Log.w(TAG, "Error occurred while loading certificates", e);
127
} catch (NoSuchAlgorithmException e) {
128
Log.w(TAG, "RSA not supported", e);
129
} catch (IOException e) {
130
Log.w(TAG, "IO Exception", e);
131
} catch (UnrecoverableEntryException e) {
132
Log.w(TAG, "KeyPair not recovered", e);
133
} catch (InvalidKeyException e) {
134
Log.w(TAG, "Invalid Key", e);
135
} catch (SignatureException e) {
136
Log.w(TAG, "Invalid Signature", e);
137
}
138
if (verified) {
139
Log.d(TAG, "Data Signature Verified");
140
} else {
141
Log.d(TAG, "Data not verified.");
142
}
143
return true;
144
}
145
return false;
146
}
147
148
/**
149
* Creates a public and private key and stores it using the Android Key Store, so that only
150
* this application will be able to access the keys.
151
*/
152
public void createKeys(Context context) throws NoSuchProviderException,
153
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
155
// Create a start and end time, for the validity range of the key pair that's about to be
156
// generated.
157
Calendar start = new GregorianCalendar();
158
Calendar end = new GregorianCalendar();
159
end.add(1, Calendar.YEAR);
161
162
164
// The KeyPairGeneratorSpec object is how parameters for your key pair are passed
165
// to the KeyPairGenerator. For a fun home game, count how many classes in this sample
166
// start with the phrase "KeyPair".
167
KeyPairGeneratorSpec spec =
168
new KeyPairGeneratorSpec.Builder(context)
169
// You'll use the alias later to retrieve the key. It's a key for the key!
170
.setAlias(mAlias)
171
// The subject used for the self-signed certificate of the generated pair
172
.setSubject(new X500Principal("CN=" + mAlias))
173
// The serial number used for the self-signed certificate of the
174
// generated pair.
175
.setSerialNumber(BigInteger.valueOf(1337))
176
// Date range of validity for the generated pair.
177
.setStartDate(start.getTime())
178
.setEndDate(end.getTime())
179
.build();
181
183
// Initialize a KeyPair generator using the the intended algorithm (in this example, RSA
184
// and the KeyStore. This example uses the AndroidKeyStore.
185
KeyPairGenerator kpGenerator = KeyPairGenerator
186
.getInstance(SecurityConstants.TYPE_RSA,
187
SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
188
kpGenerator.initialize(spec);
189
KeyPair kp = kpGenerator.generateKeyPair();
190
Log.d(TAG, "Public Key is: " + kp.getPublic().toString());
192
}
193
194
/**
195
* Signs the data using the key pair stored in the Android Key Store. This signature can be
196
* used with the data later to verify it was signed by this application.
197
* @return A string encoding of the data signature generated
198
*/
199
public String signData(String inputStr) throws KeyStoreException,
200
UnrecoverableEntryException, NoSuchAlgorithmException, InvalidKeyException,
201
SignatureException, IOException, CertificateException {
202
byte[] data = inputStr.getBytes();
203
205
KeyStore ks = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
206
207
// Weird artifact of Java API. If you don't have an InputStream to load, you still need
208
// to call "load", or it'll crash.
209
ks.load(null);
210
211
// Load the key pair from the Android Key Store
212
KeyStore.Entry entry = ks.getEntry(mAlias, null);
213
214
/* If the entry is null, keys were never stored under this alias.
215
* Debug steps in this situation would be:
216
* -Check the list of aliases by iterating over Keystore.aliases(), be sure the alias
217
* exists.
218
* -If that's empty, verify they were both stored and pulled from the same keystore
219
* "AndroidKeyStore"
220
*/
221
if (entry == null) {
222
Log.w(TAG, "No key found under alias: " + mAlias);
223
Log.w(TAG, "Exiting signData()...");
224
return null;
225
}
226
227
/* If entry is not a KeyStore.PrivateKeyEntry, it might have gotten stored in a previous
228
* iteration of your application that was using some other mechanism, or been overwritten
229
* by something else using the same keystore with the same alias.
230
* You can determine the type using entry.getClass() and debug from there.
231
*/
232
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
233
Log.w(TAG, "Not an instance of a PrivateKeyEntry");
234
Log.w(TAG, "Exiting signData()...");
235
return null;
236
}
238
240
// This class doesn't actually represent the signature,
241
// just the engine for creating/verifying signatures, using
242
// the specified algorithm.
243
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
244
245
// Initialize Signature using specified private key
246
s.initSign(((KeyStore.PrivateKeyEntry) entry).getPrivateKey());
247
248
// Sign the data, store the result as a Base64 encoded String.
249
s.update(data);
250
byte[] signature = s.sign();
251
String result = Base64.encodeToString(signature, Base64.DEFAULT);
253
254
return result;
255
}
256
257
/**
258
* Given some data and a signature, uses the key pair stored in the Android Key Store to verify
259
* that the data was signed by this application, using that key pair.
260
* @param input The data to be verified.
261
* @param signatureStr The signature provided for the data.
262
* @return A boolean value telling you whether the signature is valid or not.
263
*/
264
public boolean verifyData(String input, String signatureStr) throws KeyStoreException,
265
CertificateException, NoSuchAlgorithmException, IOException,
266
UnrecoverableEntryException, InvalidKeyException, SignatureException {
267
byte[] data = input.getBytes();
268
byte[] signature;
270
271
// Make sure the signature string exists. If not, bail out, nothing to do.
272
273
if (signatureStr == null) {
274
Log.w(TAG, "Invalid signature.");
275
Log.w(TAG, "Exiting verifyData()...");
276
return false;
277
}
278
279
try {
280
// The signature is going to be examined as a byte array,
281
// not as a base64 encoded string.
282
signature = Base64.decode(signatureStr, Base64.DEFAULT);
283
} catch (IllegalArgumentException e) {
284
// signatureStr wasn't null, but might not have been encoded properly.
285
// It's not a valid Base64 string.
286
return false;
287
}
289
290
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
291
292
// Weird artifact of Java API. If you don't have an InputStream to load, you still need
293
// to call "load", or it'll crash.
294
ks.load(null);
295
296
// Load the key pair from the Android Key Store
297
KeyStore.Entry entry = ks.getEntry(mAlias, null);
298
299
if (entry == null) {
300
Log.w(TAG, "No key found under alias: " + mAlias);
301
Log.w(TAG, "Exiting verifyData()...");
302
return false;
303
}
304
305
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
306
Log.w(TAG, "Not an instance of a PrivateKeyEntry");
307
return false;
308
}
309
310
// This class doesn't actually represent the signature,
311
// just the engine for creating/verifying signatures, using
312
// the specified algorithm.
313
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
314
316
// Verify the data.
317
s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate());
318
s.update(data);
319
boolean valid = s.verify(signature);
320
return valid;
322
}
323
324
public void setAlias(String alias) {
325
mAlias = alias;
326
}
327
}