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」を利用した。
作成した鍵ペアを使って生テキストの暗号化、暗号テキストの復号化できることが確認できた。