【Kotlin/Android】KeyStoreの使い方!データを高セキュリティで保存する方法
この記事からわかること
- Android Studio/Kotlinでローカルにセキュリティを高くデータを保存する方法
- KeyStoreの使い方と実装方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
KeyStoreとは?
公式リファレンス:Android Keystore Systems
KeyStoreは暗号化されたキーと証明書のエントリを保持するためのセキュアなストレージです。主に秘密鍵、公開鍵、証明書などの情報を安全に保存するために使用されます。勘違いしやすいのはKeyStoreはログイン情報自体をセキュアに保存するための仕組みではなく、暗号化に使用した鍵をセキュアに保存する仕組みだということです。
そのため例えばログイン情報などをローカルに保存したい場合は以下のような手順を踏む必要があります。
ログイン情報を安全に端末内に保持するには?
- ログイン情報を暗号化する:Cipherなど
- 暗号化に使用した鍵をセキュアにローカルに保存する:KeyStore
- 暗号化されたログイン情報をローカルに保存する:SharedPreferences/DataStoreなど
おすすめ記事:【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())
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。