/* * 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.vault; import android.content.Context; import android.security.KeyPairGeneratorSpec; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.util.Calendar; import java.util.GregorianCalendar; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.security.auth.x500.X500Principal; /** * Wraps {@link SecretKey} instances using a public/private key pair stored in * the platform {@link KeyStore}. This allows us to protect symmetric keys with * hardware-backed crypto, if provided by the device. *

* See key wrapping for more * details. *

* Not inherently thread safe. */ public class SecretKeyWrapper { private final Cipher mCipher; private final KeyPair mPair; /** * Create a wrapper using the public/private key pair with the given alias. * If no pair with that alias exists, it will be generated. */ public SecretKeyWrapper(Context context, String alias) throws GeneralSecurityException, IOException { mCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); if (!keyStore.containsAlias(alias)) { generateKeyPair(context, alias); } // Even if we just generated the key, always read it back to ensure we // can read it successfully. final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry( alias, null); mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); } private static void generateKeyPair(Context context, String alias) throws GeneralSecurityException { final Calendar start = new GregorianCalendar(); final Calendar end = new GregorianCalendar(); end.add(Calendar.YEAR, 100); final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) .setAlias(alias) .setSubject(new X500Principal("CN=" + alias)) .setSerialNumber(BigInteger.ONE) .setStartDate(start.getTime()) .setEndDate(end.getTime()) .build(); final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); gen.initialize(spec); gen.generateKeyPair(); } /** * Wrap a {@link SecretKey} using the public key assigned to this wrapper. * Use {@link #unwrap(byte[])} to later recover the original * {@link SecretKey}. * * @return a wrapped version of the given {@link SecretKey} that can be * safely stored on untrusted storage. */ public byte[] wrap(SecretKey key) throws GeneralSecurityException { mCipher.init(Cipher.WRAP_MODE, mPair.getPublic()); return mCipher.wrap(key); } /** * Unwrap a {@link SecretKey} using the private key assigned to this * wrapper. * * @param blob a wrapped {@link SecretKey} as previously returned by * {@link #wrap(SecretKey)}. */ public SecretKey unwrap(byte[] blob) throws GeneralSecurityException { mCipher.init(Cipher.UNWRAP_MODE, mPair.getPrivate()); return (SecretKey) mCipher.unwrap(blob, "AES", Cipher.SECRET_KEY); } }