13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【コピペで使える!】 Android Keystoreシステムを使ってパスワードを安全に管理する

Posted at

ユーザーの新規登録→ログインをアプリで実装していたのですが、途中でパスワードを安全に保存する必要があることに気づいて調べました。

公式ドキュメント に情報がありますが、Android 6.x以上でしか使えない情報でした。

本エントリではAndroid 4.3以降でパスワードを暗号化し保存/復号化して取得する方法について説明します。

コード

暗号化に必要なコードは次の通り

import android.content.Context
import android.security.KeyPairGeneratorSpec
import android.util.Base64
import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.util.*
import javax.crypto.Cipher
import javax.security.auth.x500.X500Principal


val PROVIDER = "AndroidKeyStore"
val ALGORITHM = "RSA"
val CIPHER_TRANSFORMATION = "RSA/ECB/PKCS1Padding"

/**
 * テキストを暗号化する
 * @param context
 * @param alias キーペアを識別するためのエリアス。用途ごとに一意にする。
 * @param plainText 暗号化したいテキスト
 * @return 暗号化されBase64でラップされた文字列
 */
fun encrypt(context: Context, alias: String, plainText: String): String {
    val keyStore = KeyStore.getInstance(PROVIDER)
    keyStore.load(null)

    // キーペアがない場合生成
    if (!keyStore.containsAlias(alias)) {
        val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER)
        keyPairGenerator.initialize(createKeyPairGeneratorSpec(context, alias))
        keyPairGenerator.generateKeyPair()
    }
    val publicKey = keyStore.getCertificate(alias).getPublicKey()
    val privateKey = keyStore.getKey(alias, null)

    // 公開鍵で暗号化
    val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION)
    cipher.init(Cipher.ENCRYPT_MODE, publicKey)
    val bytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))

    // SharedPreferencesに保存しやすいようにBase64でString化
    return Base64.encodeToString(bytes, Base64.DEFAULT)
}

/**
 * 暗号化されたテキストを復号化する
 * @param alias キーペアを識別するためのエリアス。用途ごとに一意にする。
 * @param encryptedText encryptで暗号化されたテキスト
 * @return 復号化された文字列
 */
fun decrypt(alias: String, encryptedText: String): String? {
    val keyStore = KeyStore.getInstance(PROVIDER)
    keyStore.load(null)
    if (!keyStore.containsAlias(alias)) {
        return null
    }

    // 秘密鍵で復号化
    val privateKey = keyStore.getKey(alias, null)
    val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION)
    cipher.init(Cipher.DECRYPT_MODE, privateKey)
    val bytes = Base64.decode(encryptedText, Base64.DEFAULT)

    val b = cipher.doFinal(bytes)
    return String(b)
}

/**
 * キーペアを生成する
 */
fun createKeyPairGeneratorSpec(context: Context, alias: String): KeyPairGeneratorSpec {
    val start = Calendar.getInstance()
    val end = Calendar.getInstance()
    end.add(Calendar.YEAR, 100)

    return KeyPairGeneratorSpec.Builder(context)
            .setAlias(alias)
            .setSubject(X500Principal(String.format("CN=%s", alias)))
            .setSerialNumber(BigInteger.valueOf(1000000))
            .setStartDate(start.getTime())
            .setEndDate(end.getTime())
            .build()
}

どうやらキーストアはキーペアの管理を安全にしてくれるだけみたいです。キーを安全に管理してアクセスできるようにしてあげるから、キーを使って好きに暗号化/復号化してねってことで、暗号化されたコンテンツの保存はキーストアの役割でないようです。
今回はSharedPreferencesに保存します。

使い方

暗号化されたテキストは見られても問題ない(←これほんとにホント?)のでSharedPreferencesに保存して、次のような関数を使って保存/取得します。

/**
 * パスワードを暗号化して保存する
 */
fun savePassword(context: Context, plainPassword: String) {
    val encryptedPassword = encrypt(context, "password", plainPassword)
    val editor = getSharedPreferences(context).edit()
    editor.putString("encrypted_password", encryptedPassword).commit()
}

/**
 * パスワードを復号化して取得する
 */
fun getPassword(context: Context): String? {
    val encryptedPassword = getSharedPreferences(context).getString("encrypted_password", null)
    if (encryptedPassword == null) {
        return encryptedPassword
    }
    val plainPassword = decrypt("password", encryptedPassword)
    return plainPassword
}

参考

https://qiita.com/f_nishio/items/485490dea126dbbb5001
https://qiita.com/Koganes/items/e8253f13ecb534ca11a1

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?