Skip to content

Most visited

Recently visited

navigation
BasicAndroidKeyStore / src / com.example.android.basicandroidkeystore /

BasicAndroidKeyStoreFragment.java

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