Android 4.3 で Android Keystore を使う

#この記事の概要
- API Level 23 (Android 6.0) になってAndroid Keystore が色々と強化された
- と同時にAPI Level 18 (Android 4.3) 時代の実装ドキュメントが公式から消えた
- 4.3でも使えなきゃ困る。でも詳しい書き方載ってない。だから書き記す

サンプルコードだけあれば充分という方は[こちら](https://gist.github.com/k-ohkura/22a61981b2d42bd3d7d8f314fc30e582)からどうぞ。
Android Keystoreが利用できない端末で鍵を保存したいときは、[こちら](https://qiita.com/Koganes/items/446f980fc45594fce436)の記事を参考にして下さい。

#Android Keystore とは
Android 4.3 から登場した鍵管理の仕組み。アプリ内になんらかの機密データを保持する場合、例え暗号化して保存しても鍵が見つかってしまうと意味がありません。しかし、自前で鍵をセキュアに管理するのは[なかなか難儀](http://d.hatena.ne.jp/ockeghem/20120213/p1)です。Android Keystoreを使えば鍵の生成や保持をどっか安全な場所でAndroidがうまいことやってくれます（よくしらない）。この鍵は、鍵が生成されたアプリ＆デバイスのみで有効な、まさにアプリ内保存に特化したものとなっています。
※ 仕組みとしてはiOSのKeychainとよく似ています（あちらはアプリ間で鍵の共有もできるけど）。ややこしいことにAndroidにもKeychainというAPIがありますが、iOSのKeychainとは意味合いが違うものになっています

## Android 4.3 -> Android 6.0 でAPIはどう変わったか
内部的には楕円曲線暗号などの新たなアルゴリズムが追加されたり、ようやく共通鍵暗号に対応したりと色々強化されているようですが、コード上の大きな違いは以下の2つです。

- [`KeyPairGeneratorSpec`](http://developer.android.com/intl/ja/reference/android/security/KeyPairGeneratorSpec.html)がdeprecatedになり、代わりに[`KeyGenParameterSpec`](http://developer.android.com/intl/ja/reference/android/security/keystore/KeyGenParameterSpec.html)が用意された
- [`KeyProperties`](http://developer.android.com/intl/ja/reference/android/security/keystore/KeyProperties.html)というクラスが用意され、今まで文字列リテラルで直打ちしていた引数の値等が定数化された

4.3時代と比べると幾分スマートに書けるようになっています。しかしこれらのクラスはAPI Level 23以降しか対応していません（特定のメソッドが、ではなくクラスそのものが23以降にしかない）。2016/05/04 現在、[Android 6.0 のシェア](http://developer.android.com/intl/ja/about/dashboards/index.html)は7.5%となっています。いくらdeprecatedと言われても、こんな状況でAndroid 6.0未満を切り捨てられる豪傑はそうそういないでしょう。

#Android 4.3でも動くサンプル
もともとAndroid 4.3から使えるAPIなので「4.3でも動く」というのは少し変なのですが、[公式のリファレンス](http://developer.android.com/intl/ja/training/articles/keystore.html)はすっかり最新版のAPI Level 23のものに書き換えられ、4.3時代の書き方は[Web Archive](https://archive.org/)にすら残っていません。

というわけでdeprecatedになった旧式のサンプルを書き記しておきます。
トップダウンにいきましょう。まずはKeyPairを作るところ。

```java
private KeyPair createKeyPair() {
    KeyPairGenerator kpg = null;
    try {
        kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
        kpg.initialize(createKeyPairGeneratorSpec());
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return kpg.generateKeyPair();
}
```
戻り値が格納された変数を`keyPair`とすると、`keyPair.getPublic()`で公開鍵、`keyPair.getPrivate()`で秘密鍵が得られます。鍵を生成しているだけのように見えますが、保存まで終わっています。`"RSA"`と`"AndroidKeyStore"`は固定の文字列です。API Level 18では[RSA以外の選択肢がありません](http://developer.android.com/intl/ja/training/articles/keystore.html#SupportedAlgorithms)。
※ 結構色々な検査例外をスローしますが、ここでは個別のcatchは割愛しています。

続いて`createKeyPairGeneratorSpec()`の中身です。KeyPairGeneratorの初期化に使うKeyPairGeneratorSpecのインスタンスを生成します。いわゆるオレオレ証明書です。

```java
private KeyPairGeneratorSpec createKeyPairGeneratorSpec() {
    Calendar start = Calendar.getInstance();
    Calendar end = Calendar.getInstance();
    end.add(Calendar.YEAR, 100);

    KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(mContext)
            .setAlias(KEY_STORE_ALIAS)
            .setSubject(new X500Principal(String.format("CN=%s", KEY_STORE_ALIAS)))
            .setSerialNumber(BigInteger.valueOf(1000000))
            .setStartDate(start.getTime())
            .setEndDate(end.getTime())
            .build();

    return spec;
}
```
暗号化のためにしか使われない証明書だと思うので`setAlias(KEY_STORE_ALIAS)`以外の値はテキトーです。有効期間はなんとなく100年にしときました。Aliasは次回以降KeyStoreから鍵を取り出すときに使います。面倒なのはBuilderにContextを渡さないといけないところです。ダイアログの表示（が必要なとき）にしか使われないようなので、Activityのインスタンスが取得しにくいときはApplication Contextでも渡しておけばよいでしょう。
※ 因みにAPI Level 23の[`KeyGenParameterSpec.Builder`](http://developer.android.com/intl/ja/reference/android/security/keystore/KeyGenParameterSpec.Builder.html)ではこの辺りは簡略化され、指定が無ければ証明書はデフォルト値を使って勝手に作ってくれるようなりました。Contextも不要でした。

最後に、Android Keystoreからそれぞれの鍵を取り出す部分です。

```java
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PublicKey  publicKey  = keyStore.getCertificate(KEY_STORE_ALIAS).getPublicKey();
PrivateKey privateKey = keyStore.getKey(KEY_STORE_ALIAS, null);
```

`keyStore.getKey()`の第2引数はパスワードが入るみたいですが、Android Keystoreの場合は当然そんなものはないので（あったら意味無い）nullで良いっぽいです。この`PublicKey`や`PrivateKey`は[`Cipher`](https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html)で使用できるので、以下のようなメソッドに渡せば任意のbyte列を暗号化/復号することができます。

```java
static private final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";

public byte[] encrypt(byte[] bytes, PublicKey publicKey) {
    try {
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(bytes);

    } catch (GeneralSecurityException e) {
        e.printStackTrace();
        throw new RuntimeException("Encryption failed.");
    }
}

public byte[] decrypt(byte[] bytes, PrivateKey privateKey) {
    try {
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        return cipher.doFinal(bytes);

    } catch (GeneralSecurityException e) {
        e.printStackTrace();
        throw new RuntimeException("Decryption failed.");
    }
}
```

#ソースコード
[こちら](https://gist.github.com/k-ohkura/22a61981b2d42bd3d7d8f314fc30e582)からどうぞ。
※ 記事に載せたコードとは細部が異なっています

以下の行を適当な文字列に変更してください。

```java
static private final String KEY_STORE_ALIAS = "sample_alias"; // Change me
```
以下のメソッドが使えます。
`encrypt(byte[] bytes)` `decrypt(byte[] bytes)` `getPublicKey()` `getPrivateKey()`
鍵の生成とか気にせず、いきなりコールしてOKです。初回なら鍵の生成、2回目からは取り出して使います。`getPublicKey()`と`getPrivateKey()`はCipher以外で鍵を使う場合などにご利用ください。

```java
// 使用例
private AndroidKeyStoreManager mKeyStoreManager;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mKeyStoreManager = AndroidKeyStoreManager.getInstance(this);
    test();
}

private void test() {
    String plainText = "hogehoge";
    byte[] encryptedBytes = mKeyStoreManager.encrypt(plainText.getBytes());
    byte[] decryptedBytes = mKeyStoreManager.decrypt(encryptedBytes);
    String decryptedText = new String(decryptedBytes);
    Log.d("KeyStoreTest", decryptedText);　// -> hogehoge
}
```