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 55 // BEGIN_INCLUDE(values) 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 70 // END_INCLUDE(values) 71 72 @Override onCreate(Bundle savedInstanceState)73 public void onCreate(Bundle savedInstanceState) { 74 super.onCreate(savedInstanceState); 75 setHasOptionsMenu(true); 76 setAlias(SAMPLE_ALIAS); 77 } 78 79 @Override onActivityCreated(Bundle savedInstanceState)80 public void onActivityCreated(Bundle savedInstanceState) { 81 super.onActivityCreated(savedInstanceState); 82 } 83 84 @Override onOptionsItemSelected(MenuItem item)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 */ createKeys(Context context)156 public void createKeys(Context context) throws NoSuchProviderException, 157 NoSuchAlgorithmException, InvalidAlgorithmParameterException { 158 // BEGIN_INCLUDE(create_valid_dates) 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); 164 //END_INCLUDE(create_valid_dates) 165 166 // BEGIN_INCLUDE(create_keypair) 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); 172 // END_INCLUDE(create_keypair) 173 174 // BEGIN_INCLUDE(create_spec) 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(); 212 // END_INCLUDE(create_spec) 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 */ signData(String inputStr)221 public String signData(String inputStr) throws KeyStoreException, 222 UnrecoverableEntryException, NoSuchAlgorithmException, InvalidKeyException, 223 SignatureException, IOException, CertificateException { 224 byte[] data = inputStr.getBytes(); 225 226 // BEGIN_INCLUDE(sign_load_keystore) 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 } 259 // END_INCLUDE(sign_data) 260 261 // BEGIN_INCLUDE(sign_create_signature) 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); 274 // END_INCLUDE(sign_data) 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 */ verifyData(String input, String signatureStr)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; 291 // BEGIN_INCLUDE(decode_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 } 310 // END_INCLUDE(decode_signature) 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.PrivateKeyEntry)) { 328 Log.w(TAG, "Not an instance of a PrivateKeyEntry"); 329 return false; 330 } 331 332 // This class doesn't actually represent the signature, 333 // just the engine for creating/verifying signatures, using 334 // the specified algorithm. 335 Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA); 336 337 // BEGIN_INCLUDE(verify_data) 338 // Verify the data. 339 s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate()); 340 s.update(data); 341 return s.verify(signature); 342 // END_INCLUDE(verify_data) 343 } 344 setAlias(String alias)345 public void setAlias(String alias) { 346 mAlias = alias; 347 } 348 } 349