酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

AndroidアプリでRSA暗号の公開鍵と秘密鍵を作成する

Androidアプリ内でRSA暗号の公開鍵と秘密鍵を作成して、公開鍵を使って生テキストを暗号化、秘密鍵を使って暗号テキストを復号化する方法を紹介する。

RSA暗号の公開鍵と秘密鍵を作成する

RSAで鍵ペアを作成する。

package jp.ch3cooh.common.utility

import android.security.keystore.KeyProperties
import android.util.Base64
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher

/** キーペア(保存用) */
data class RSAKeyPair(val publicKey: String, val privateKey: String)

class RSAKeyGenerator {

    /**
     * RSA暗号の公開鍵と秘密鍵を作成する
     */
    fun generate(): RSAKeyPair {
        val random = SecureRandom()
        val keyGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
        keyGenerator.initialize(1024, random)
        val keyPair = keyGenerator.generateKeyPair()

        // Public Key
        val publicKeyBuilder = StringBuilder()
        publicKeyBuilder.append(Base64.encodeToString(keyPair.public.encoded, Base64.NO_WRAP))

        // Private Key
        val privateKeyBuilder = StringBuilder()
        privateKeyBuilder.append(Base64.encodeToString(keyPair.private.encoded, Base64.NO_WRAP))

        return RSAKeyPair(publicKeyBuilder.toString(), privateKeyBuilder.toString())
    }
}

PrivateKeyオブジェクト・PublicKeyオブジェクトのままだと、SharedPreferencesに保存するときに難儀する。BASE64エンコードして文字列として保存しておき、都度BASE64デコードしてPrivateKeyオブジェクト・PublicKeyオブジェクトを生成する使い方を想定している。

利用方法は下記の通り。

val keypair = RSAKeyGenerator().generate()
Log.e("ANGO", keypair.publicKey)
Log.e("ANGO", keypair.privateKey)

実行すると下記のような結果を得ることができる。

こっちが公開鍵 (PUBLIC KEY)

2021-02-22 15:45:21.925 16017-16017/jp.ch3cooh.fourcropper E/ANGO: MIGfMA0GC(中略)QMmGG4Xhgdba1W/+4TTuHMWOwIDAQAB

こっちが秘密鍵 (PRIVATE KEY)

2021-02-22 15:45:21.925 16017-16017/jp.ch3cooh.fourcropper E/ANGO: MIICd(中略)PHlA==

公開鍵を使って生テキストを暗号化

鍵ペアを得ることができたので、公開鍵を使ってテキストを暗号化する。

/**
* 生テキストを暗号化する
*/
fun encrypt(publicKeyString: String, planeText: String): String {
    // 公開鍵のBASE64文字列からPublicKeyオブジェクトを生成する
    val publicKey = stringToPublicKey(publicKeyString)

    val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
    cipher.init(Cipher.ENCRYPT_MODE, publicKey)

    val bytes = cipher.doFinal(planeText.toByteArray())

    return Base64.encodeToString(bytes, Base64.NO_WRAP)
}

private fun stringToPublicKey(publicKeyString: String): PublicKey? {
    var keyString = publicKeyString

    return try {
        if (keyString.contains("-----BEGIN PUBLIC KEY-----")
            || keyString.contains("-----END PUBLIC KEY-----")
        ) {
            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
        }
        if (keyString.contains("-----BEGIN RSA PUBLIC KEY-----")
            || keyString.contains("-----END RSA PUBLIC KEY-----")
        ) {
            keyString = keyString.replace("-----BEGIN RSA PUBLIC KEY-----", "")
                .replace("-----END RSA PUBLIC KEY-----", "")
        }

        keyString.trim { it <= ' ' }
        keyString = keyString.replace("\\s+".toRegex(), "")
        keyString = keyString.replace("\\r+".toRegex(), "")
        keyString = keyString.replace("^ | $|\\n ".toRegex(), "")

        val keyBytes = Base64.decode(keyString, Base64.NO_WRAP)
        val spec = X509EncodedKeySpec(keyBytes)
        val keyFactory = KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
        keyFactory.generatePublic(spec)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
        null
    } catch (e: InvalidKeySpecException) {
        e.printStackTrace()
        null
    }
}

encrypt(String,String)メソッドを使って、生テキスト「あいうえお」を先ほど作成した公開鍵で暗号化します。

val planeText = "あいうえお"

// 暗号化
val encryptedText = RSAKeyGenerator().encrypt(keypair.publicKey, planeText)
Log.e("ANGO", encryptedText)

暗号化しました。

2021-02-22 15:53:09.416 16017-16017/jp.ch3cooh.fourcropper E/ANGO: UnEly44dLd/gtLf29(中略)oK1V+W3880Lg=

秘密鍵を使って暗号テキストを復号化する

秘密鍵を使って暗号化されたテキストを復号化します。

/**
* 暗号化されたテキストを復号化する
*/
fun decrypt(privateKeyString: String, encryptedText: String): String {
    // 秘密鍵のBASE64文字列からPublicKeyオブジェクトを生成する
    val privateKey = stringToPrivateKey(privateKeyString)

    val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
    cipher.init(Cipher.DECRYPT_MODE, privateKey)

    val byteArray = Base64.decode(encryptedText, Base64.NO_WRAP)
    val bytes = cipher.doFinal(byteArray)

    return String(bytes)
}

private fun stringToPrivateKey(privateKeyString: String): PrivateKey? {
    var keyString = privateKeyString

    return try {
        if (keyString.contains("-----BEGIN PRIVATE KEY-----")
            || keyString.contains("-----END PRIVATE KEY-----")
        ) {
            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
        }
        if (keyString.contains("-----BEGIN RSA PRIVATE KEY-----")
            || keyString.contains("-----END RSA PRIVATE KEY-----")
        ) {
            keyString = keyString.replace("-----BEGIN RSA PRIVATE KEY-----", "")
                .replace("-----END RSA PRIVATE KEY-----", "")
        }

        keyString.trim { it <= ' ' }
        keyString = keyString.replace("\\s+".toRegex(), "")
        keyString = keyString.replace("\\r+".toRegex(), "")
        keyString = keyString.replace("^ | $|\\n ".toRegex(), "")

        val keyBytes = Base64.decode(keyString, Base64.NO_WRAP)
        val spec = PKCS8EncodedKeySpec(keyBytes)
        val keyFactory = KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
        keyFactory.generatePrivate(spec)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
        null
    } catch (e: InvalidKeySpecException) {
        e.printStackTrace()
        null
    }
}

decrypt(String,String)メソッドを使って、暗号化されたテキストを先ほど作成した秘密鍵で復号化します。

// 復号化
val decryptedText = RSAKeyGenerator().decrypt(keypair.privateKey, encryptedText)
Log.e("ANGO", decryptedText)

復号化された「あいうえお」を得ることができました。

検証

検証には「Online RSA Encryption, Decryption And Key Generator Tool | Devglan」を利用した。

f:id:ch3cooh393:20210222161931p:plain

作成した鍵ペアを使って生テキストの暗号化、暗号テキストの復号化できることが確認できた。

参考ページ