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 }