【Kotlin/Android】KeyStoreの使い方!データを高セキュリティで保存する方法

【Kotlin/Android】KeyStoreの使い方!データを高セキュリティで保存する方法

この記事からわかること

  • Android Studio/Kotlinローカルセキュリティ高くデータ保存する方法
  • KeyStore使い方実装方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

KeyStoreとは?

公式リファレンス:Android Keystore Systems

KeyStore暗号化されたキーと証明書のエントリを保持するためのセキュアなストレージです。主に秘密鍵、公開鍵、証明書などの情報を安全に保存するために使用されます。勘違いしやすいのはKeyStoreはログイン情報自体をセキュアに保存するための仕組みではなく、暗号化に使用した鍵をセキュアに保存する仕組みだということです。

そのため例えばログイン情報などをローカルに保存したい場合は以下のような手順を踏む必要があります。

ログイン情報を安全に端末内に保持するには?

おすすめ記事:【Kotlin/Android】Cipherの使い方!暗号化・複合化の実装方法!

SharedPreferenceやDataStoreは重要な情報の保管には適していないので暗号化して保存することが推奨されており、暗号化をするために必要は鍵をセキュアに保存するためにKeyStoreを使用することが推奨されています。

KeyStoreの仕組みとして大事なのは初回の利用時にキーを生成しそれをローカルに保存することです。その後は生成された(ローカルに保存された)キーを再利用してデータを暗号化や復号化するので、ローカルのキーを削除(アプリをアンインストールなど)しない限り、新しいキーが生成されることはありません。

KeyStoreの使い方

KeyStoreを活用して秘密鍵を取得できるユーティリティークラスを作成してみます。ここで作成する秘密鍵が初回のみ生成されローカルにセキュアに保存され、再利用されるものになります。

1.KeyStoreプロバイダーとキーのエイリアスを定義

まずはAndroidのKeyStoreプロバイダーを指定するための定数KeyStoreに保存されるキーのエイリアスを指定するための定数を定義します。エイリアスの方は好きな名前で良いですが途中で変更するとキーも変更になる(異なるキーが生成される)ので注意してください。

class KeyStoreUtility {
    companion object {
        private const val ANDROID_KEY_STORE = "AndroidKeyStore"
        private const val ANDROID_KEY_ALIAS = "AndroidKeyAlias"
    }
}

2.KeyStoreオブジェクトを取得

続いてKeyStoreプロバイダーを元にKeyStoreオブジェクトを取得します。

private val keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEY_STORE)

3.秘密鍵の生成とローカルに存在しないかチェック

新しい秘密鍵を生成するためのメソッドを実装します。秘密鍵の生成にはKeyPairGeneratorクラスを利用します。生成する前に既に作成済みかどうかを!keyStore.containsAlias(alias)でチェックしています。

private fun createNewKey(keyStore: KeyStore, alias: String) {
    try {
        // 既に作成ずみかチェック
        if (!keyStore.containsAlias(alias)) {
            // 存在しなければ新規で秘密鍵を生成
            val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_RSA,
                ANDROID_KEY_STORE
            )
            keyPairGenerator.initialize(
                KeyGenParameterSpec.Builder(
                    alias,
                    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                )
                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                    .build()
            )
            keyPairGenerator.generateKeyPair()
        }
    } catch (e: java.lang.Exception) {
        Log.e("TAG", e.toString())
    }
}

4.KeyStoreオブジェクトを有効にする

KeyStoreオブジェクトを有効にするためにkeyStore.loadメソッドを実行し秘密鍵を生成するためのcreateNewKeyメソッドを呼び出せばOKです。

init {
    prepareKeyStore()
}
  
private fun prepareKeyStore() {
    try {
        keyStore.load(null)
        createNewKey(keyStore, ANDROID_KEY_ALIAS)
    } catch (e: Exception) {
        Log.e("Tag", e.toString())
    }
}

秘密鍵を取得する

最後に生成した秘密鍵を利用できるように取得するメソッドを用意します。

fun getSecretKey(): SecretKey? {
    return try {
        keyStore.getKey(ANDROID_KEY_ALIAS, null) as? SecretKey
    } catch (e: Exception) {
        Log.e("Tag", e.toString())
        null
    }
}

6.クラス全体のコード

class KeyStoreUtility {
    companion object {
        private const val ANDROID_KEY_STORE = "AndroidKeyStore"
        private const val ANDROID_KEY_ALIAS = "AndroidKeyAlias"
    }

    private val keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEY_STORE)

    init {
        prepareKeyStore()
    }

    /**
     * KeyStoreを取得し、既存のキーが存在しない場合に
     * [createNewKey]メソッドで新しいキーを生成
     */
    private fun prepareKeyStore() {
        try {
            keyStore.load(null)
            createNewKey(keyStore, ANDROID_KEY_ALIAS)
        } catch (e: Exception) {
            Log.e("Tag", e.toString())
        }
    }

    /**
     * 新しいキーを生成
     * 指定されたエイリアス名で[KeyPairGenerator]を初期化
     * [KeyGenParameterSpec]を使用してキーの生成条件を設定
     * [generateKeyPair]メソッドを呼び出して実際にキーペアを生成
     */
    private fun createNewKey(keyStore: KeyStore, alias: String) {
        try {
            if (!keyStore.containsAlias(alias)) {
                val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_RSA,
                    ANDROID_KEY_STORE
                )
                keyPairGenerator.initialize(
                    KeyGenParameterSpec.Builder(
                        alias,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                    )
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .build()
                )
                keyPairGenerator.generateKeyPair()
            }
        } catch (e: java.lang.Exception) {
            Log.e("TAG", e.toString())
        }
    }

    /**
     * 秘密鍵を取得
     */
    fun getSecretKey(): SecretKey? {
        return try {
            keyStore.getKey(ANDROID_KEY_ALIAS, null) as? SecretKey
        } catch (e: Exception) {
            Log.e("Tag", e.toString())
            null
        }
    }
}

使用例

あとはこの秘密鍵を使用してCipherなどを利用して暗号化処理を実装し、SharedPreferenceなどへ保存処理を実装すれば完了です。

val keyStoreUtility = KeyStoreUtility()

val secretKey = keyStoreUtility.secretKeyEntry.secretKey

// 秘密鍵を使ってデータを暗号化するなどの操作
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val encryptedData = cipher.doFinal(plainText.toByteArray())

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index