| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.basicandroidkeystore; |
| |
| import com.example.android.common.logger.Log; |
| |
| import android.content.Context; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.security.KeyPairGeneratorSpec; |
| import android.security.keystore.KeyGenParameterSpec; |
| import android.security.keystore.KeyProperties; |
| import android.support.v4.app.Fragment; |
| import android.util.Base64; |
| import android.view.MenuItem; |
| |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.Signature; |
| import java.security.SignatureException; |
| import java.security.UnrecoverableEntryException; |
| import java.security.cert.CertificateException; |
| import java.security.spec.AlgorithmParameterSpec; |
| import java.util.Calendar; |
| import java.util.GregorianCalendar; |
| |
| import javax.security.auth.x500.X500Principal; |
| |
| public class BasicAndroidKeyStoreFragment extends Fragment { |
| |
| public static final String TAG = "KeyStoreFragment"; |
| |
| // BEGIN_INCLUDE(values) |
| |
| public static final String SAMPLE_ALIAS = "myKey"; |
| |
| // Some sample data to sign, and later verify using the generated signature. |
| public static final String SAMPLE_INPUT="Hello, Android!"; |
| |
| // Just a handy place to store the signature in between signing and verifying. |
| public String mSignatureStr = null; |
| |
| // You can store multiple key pairs in the Key Store. The string used to refer to the Key you |
| // want to store, or later pull, is referred to as an "alias" in this case, because calling it |
| // a key, when you use it to retrieve a key, would just be irritating. |
| private String mAlias = null; |
| |
| // END_INCLUDE(values) |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setHasOptionsMenu(true); |
| setAlias(SAMPLE_ALIAS); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.btn_create_keys: |
| try { |
| createKeys(getActivity()); |
| Log.d(TAG, "Keys created"); |
| return true; |
| } catch (NoSuchAlgorithmException e) { |
| Log.w(TAG, "RSA not supported", e); |
| } catch (InvalidAlgorithmParameterException e) { |
| Log.w(TAG, "No such provider: AndroidKeyStore"); |
| } catch (NoSuchProviderException e) { |
| Log.w(TAG, "Invalid Algorithm Parameter Exception", e); |
| } |
| return true; |
| case R.id.btn_sign_data: |
| try { |
| mSignatureStr = signData(SAMPLE_INPUT); |
| } catch (KeyStoreException e) { |
| Log.w(TAG, "KeyStore not Initialized", e); |
| } catch (UnrecoverableEntryException e) { |
| Log.w(TAG, "KeyPair not recovered", e); |
| } catch (NoSuchAlgorithmException e) { |
| Log.w(TAG, "RSA not supported", e); |
| } catch (InvalidKeyException e) { |
| Log.w(TAG, "Invalid Key", e); |
| } catch (SignatureException e) { |
| Log.w(TAG, "Invalid Signature", e); |
| } catch (IOException e) { |
| Log.w(TAG, "IO Exception", e); |
| } catch (CertificateException e) { |
| Log.w(TAG, "Error occurred while loading certificates", e); |
| } |
| Log.d(TAG, "Signature: " + mSignatureStr); |
| return true; |
| |
| case R.id.btn_verify_data: |
| boolean verified = false; |
| try { |
| if (mSignatureStr != null) { |
| verified = verifyData(SAMPLE_INPUT, mSignatureStr); |
| } |
| } catch (KeyStoreException e) { |
| Log.w(TAG, "KeyStore not Initialized", e); |
| } catch (CertificateException e) { |
| Log.w(TAG, "Error occurred while loading certificates", e); |
| } catch (NoSuchAlgorithmException e) { |
| Log.w(TAG, "RSA not supported", e); |
| } catch (IOException e) { |
| Log.w(TAG, "IO Exception", e); |
| } catch (UnrecoverableEntryException e) { |
| Log.w(TAG, "KeyPair not recovered", e); |
| } catch (InvalidKeyException e) { |
| Log.w(TAG, "Invalid Key", e); |
| } catch (SignatureException e) { |
| Log.w(TAG, "Invalid Signature", e); |
| } |
| if (verified) { |
| Log.d(TAG, "Data Signature Verified"); |
| } else { |
| Log.d(TAG, "Data not verified."); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Creates a public and private key and stores it using the Android Key Store, so that only |
| * this application will be able to access the keys. |
| */ |
| public void createKeys(Context context) throws NoSuchProviderException, |
| NoSuchAlgorithmException, InvalidAlgorithmParameterException { |
| // BEGIN_INCLUDE(create_valid_dates) |
| // Create a start and end time, for the validity range of the key pair that's about to be |
| // generated. |
| Calendar start = new GregorianCalendar(); |
| Calendar end = new GregorianCalendar(); |
| end.add(Calendar.YEAR, 1); |
| //END_INCLUDE(create_valid_dates) |
| |
| // BEGIN_INCLUDE(create_keypair) |
| // Initialize a KeyPair generator using the the intended algorithm (in this example, RSA |
| // and the KeyStore. This example uses the AndroidKeyStore. |
| KeyPairGenerator kpGenerator = KeyPairGenerator |
| .getInstance(SecurityConstants.TYPE_RSA, |
| SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); |
| // END_INCLUDE(create_keypair) |
| |
| // BEGIN_INCLUDE(create_spec) |
| // The KeyPairGeneratorSpec object is how parameters for your key pair are passed |
| // to the KeyPairGenerator. |
| AlgorithmParameterSpec spec; |
| |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
| // Below Android M, use the KeyPairGeneratorSpec.Builder. |
| |
| spec = new KeyPairGeneratorSpec.Builder(context) |
| // You'll use the alias later to retrieve the key. It's a key for the key! |
| .setAlias(mAlias) |
| // The subject used for the self-signed certificate of the generated pair |
| .setSubject(new X500Principal("CN=" + mAlias)) |
| // The serial number used for the self-signed certificate of the |
| // generated pair. |
| .setSerialNumber(BigInteger.valueOf(1337)) |
| // Date range of validity for the generated pair. |
| .setStartDate(start.getTime()) |
| .setEndDate(end.getTime()) |
| .build(); |
| |
| |
| } else { |
| // On Android M or above, use the KeyGenparameterSpec.Builder and specify permitted |
| // properties and restrictions of the key. |
| spec = new KeyGenParameterSpec.Builder(mAlias, KeyProperties.PURPOSE_SIGN) |
| .setCertificateSubject(new X500Principal("CN=" + mAlias)) |
| .setDigests(KeyProperties.DIGEST_SHA256) |
| .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) |
| .setCertificateSerialNumber(BigInteger.valueOf(1337)) |
| .setCertificateNotBefore(start.getTime()) |
| .setCertificateNotAfter(end.getTime()) |
| .build(); |
| } |
| |
| kpGenerator.initialize(spec); |
| |
| KeyPair kp = kpGenerator.generateKeyPair(); |
| // END_INCLUDE(create_spec) |
| Log.d(TAG, "Public Key is: " + kp.getPublic().toString()); |
| } |
| |
| /** |
| * Signs the data using the key pair stored in the Android Key Store. This signature can be |
| * used with the data later to verify it was signed by this application. |
| * @return A string encoding of the data signature generated |
| */ |
| public String signData(String inputStr) throws KeyStoreException, |
| UnrecoverableEntryException, NoSuchAlgorithmException, InvalidKeyException, |
| SignatureException, IOException, CertificateException { |
| byte[] data = inputStr.getBytes(); |
| |
| // BEGIN_INCLUDE(sign_load_keystore) |
| KeyStore ks = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); |
| |
| // Weird artifact of Java API. If you don't have an InputStream to load, you still need |
| // to call "load", or it'll crash. |
| ks.load(null); |
| |
| // Load the key pair from the Android Key Store |
| KeyStore.Entry entry = ks.getEntry(mAlias, null); |
| |
| /* If the entry is null, keys were never stored under this alias. |
| * Debug steps in this situation would be: |
| * -Check the list of aliases by iterating over Keystore.aliases(), be sure the alias |
| * exists. |
| * -If that's empty, verify they were both stored and pulled from the same keystore |
| * "AndroidKeyStore" |
| */ |
| if (entry == null) { |
| Log.w(TAG, "No key found under alias: " + mAlias); |
| Log.w(TAG, "Exiting signData()..."); |
| return null; |
| } |
| |
| /* If entry is not a KeyStore.PrivateKeyEntry, it might have gotten stored in a previous |
| * iteration of your application that was using some other mechanism, or been overwritten |
| * by something else using the same keystore with the same alias. |
| * You can determine the type using entry.getClass() and debug from there. |
| */ |
| if (!(entry instanceof KeyStore.PrivateKeyEntry)) { |
| Log.w(TAG, "Not an instance of a PrivateKeyEntry"); |
| Log.w(TAG, "Exiting signData()..."); |
| return null; |
| } |
| // END_INCLUDE(sign_data) |
| |
| // BEGIN_INCLUDE(sign_create_signature) |
| // This class doesn't actually represent the signature, |
| // just the engine for creating/verifying signatures, using |
| // the specified algorithm. |
| Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA); |
| |
| // Initialize Signature using specified private key |
| s.initSign(((KeyStore.PrivateKeyEntry) entry).getPrivateKey()); |
| |
| // Sign the data, store the result as a Base64 encoded String. |
| s.update(data); |
| byte[] signature = s.sign(); |
| String result = Base64.encodeToString(signature, Base64.DEFAULT); |
| // END_INCLUDE(sign_data) |
| |
| return result; |
| } |
| |
| /** |
| * Given some data and a signature, uses the key pair stored in the Android Key Store to verify |
| * that the data was signed by this application, using that key pair. |
| * @param input The data to be verified. |
| * @param signatureStr The signature provided for the data. |
| * @return A boolean value telling you whether the signature is valid or not. |
| */ |
| public boolean verifyData(String input, String signatureStr) throws KeyStoreException, |
| CertificateException, NoSuchAlgorithmException, IOException, |
| UnrecoverableEntryException, InvalidKeyException, SignatureException { |
| byte[] data = input.getBytes(); |
| byte[] signature; |
| // BEGIN_INCLUDE(decode_signature) |
| |
| // Make sure the signature string exists. If not, bail out, nothing to do. |
| |
| if (signatureStr == null) { |
| Log.w(TAG, "Invalid signature."); |
| Log.w(TAG, "Exiting verifyData()..."); |
| return false; |
| } |
| |
| try { |
| // The signature is going to be examined as a byte array, |
| // not as a base64 encoded string. |
| signature = Base64.decode(signatureStr, Base64.DEFAULT); |
| } catch (IllegalArgumentException e) { |
| // signatureStr wasn't null, but might not have been encoded properly. |
| // It's not a valid Base64 string. |
| return false; |
| } |
| // END_INCLUDE(decode_signature) |
| |
| KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); |
| |
| // Weird artifact of Java API. If you don't have an InputStream to load, you still need |
| // to call "load", or it'll crash. |
| ks.load(null); |
| |
| // Load the key pair from the Android Key Store |
| KeyStore.Entry entry = ks.getEntry(mAlias, null); |
| |
| if (entry == null) { |
| Log.w(TAG, "No key found under alias: " + mAlias); |
| Log.w(TAG, "Exiting verifyData()..."); |
| return false; |
| } |
| |
| if (!(entry instanceof KeyStore.PrivateKeyEntry)) { |
| Log.w(TAG, "Not an instance of a PrivateKeyEntry"); |
| return false; |
| } |
| |
| // This class doesn't actually represent the signature, |
| // just the engine for creating/verifying signatures, using |
| // the specified algorithm. |
| Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA); |
| |
| // BEGIN_INCLUDE(verify_data) |
| // Verify the data. |
| s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate()); |
| s.update(data); |
| return s.verify(signature); |
| // END_INCLUDE(verify_data) |
| } |
| |
| public void setAlias(String alias) { |
| mAlias = alias; |
| } |
| } |