前回はSwiftでLaravel APIを用いて暗号化と復号を実現できるクラスを紹介しました。
今回はKotlinで同等のクラスを作成します。
依存関係
appのbuild.gradleに記載してある依存関係です。
Apache Commons CodecとMoshiを使用しています。
暗号化とJsonのパースでそれぞれ使用しています。
1 2 3 4 5 6 7 | dependencies { // 省略... implementation 'commons-codec:commons-codec:1.10' implementation 'com.squareup.moshi:moshi:1.6.0' implementation 'com.squareup.moshi:moshi-kotlin:1.6.0' kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.6.0' } |
Cryptoクラス
Swift版のソースを書き直したのが以下になります。
暗号化を行うクラスはコンストラクタの引数にLaravel APIキーを受け取る作りになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | import android.util.Log import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.apache.commons.codec.binary.Hex import javax.crypto.Cipher import javax.crypto.Mac import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec // Json変換用のクラス data class LaravelCryptoJson(val iv: String, val value: String, val mac: String) // 暗号化・復号クラス data class LaravelCryptoData(val result: Boolean, val data: String) class LaravelCrypto(baseKey64: String) { companion object { private const val TRANS = "AES/CBC/PKCS5Padding" private const val NO_WRAP = android.util.Base64.NO_WRAP private const val DEFAULT = android.util.Base64.DEFAULT } private val secretKeySpec: SecretKeySpec private val macKey: SecretKeySpec init { try { val key = android.util.Base64.decode(baseKey64, NO_WRAP) secretKeySpec = SecretKeySpec(key, "AES") macKey = SecretKeySpec(key, "HmacSHA256") } catch (e: Exception) { throw e } } /* 暗号化処理を実行するメソッド */ fun encrypto(text: String): LaravelCryptoData { try { val encrypter = Cipher.getInstance(TRANS) encrypter.init(Cipher.ENCRYPT_MODE, secretKeySpec) val arrayIv = encrypter.iv val array64Iv = android.util.Base64.encode(arrayIv, NO_WRAP) val str64Iv = String(array64Iv) val strSerialize = stringSerializer(text) val crypto = encrypter.doFinal(strSerialize.toByteArray()) val base64Crypto = android.util.Base64.encode(crypto, NO_WRAP) val str64Crypto = String(base64Crypto) val hmacSha256 = Mac.getInstance("HmacSHA256") hmacSha256.init(macKey) hmacSha256.update(array64Iv) val arrayMac = hmacSha256.doFinal(str64Crypto.toByteArray()) val hexMac = Hex.encodeHex(arrayMac) val strMac = String(hexMac) val data = LaravelCryptoJson(str64Iv, str64Crypto, strMac) val json = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() .adapter(LaravelCryptoJson::class.java) .toJson(data) val json64 = android.util.Base64.encode(json.toByteArray(), NO_WRAP) return LaravelCryptoData(true, String(json64)) } catch (e: Exception) { Log.e(javaClass.name, "$e") } return LaravelCryptoData(false, "") } /* 復号処理を実行するメソッド */ fun decrypto(str64: String): LaravelCryptoData { try { val strJson = android.util.Base64.decode(str64, DEFAULT) val data = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() .adapter(LaravelCryptoJson::class.java) .fromJson(String(strJson)) data ?: throw Exception("json format error") val arrayIv = android.util.Base64.decode(data.iv, DEFAULT) val arrayValue = android.util.Base64.decode(data.value, DEFAULT) val iv = IvParameterSpec(arrayIv) val decrypter = Cipher.getInstance(TRANS) decrypter.init(Cipher.DECRYPT_MODE, secretKeySpec, iv) val arrayText = decrypter.doFinal(arrayValue) val result = stringDeserializer(String(arrayText)) val hmacSha256 = Mac.getInstance("HmacSHA256") hmacSha256.init(macKey) hmacSha256.update(data.iv.toByteArray()) val arrayMac = hmacSha256.doFinal(data.value.toByteArray()) val str64Mac = String(android.util.Base64.encode(arrayMac, DEFAULT)) val arrayHexMac = Hex.decodeHex(data.mac.toCharArray()) val str64HexMac = String(android.util.Base64.encode(arrayHexMac, DEFAULT)) if (str64HexMac != str64Mac) { return LaravelCryptoData(false, "Mac mismatch") } return LaravelCryptoData(true, result) } catch (e: Exception) { Log.e(javaClass.name, "$e") } return LaravelCryptoData(false, "") } private fun stringSerializer(str: String): String { return str /* // Laravel 5.5.* より古いバージョンでは必要かも? return "s:${str.toByteArray().count()}:\"$str\";" */ } private fun stringDeserializer(str: String): String { return str /* // Laravel 5.5.* より古いバージョンでは必要かも? val text = str.split(":").last() val result = text.subSequence(1, text.lastIndex - 1) return result.toString() */ } } |
使用方法
Laravelではシリアライズしない暗号化・復号のみ使用できます。
1 2 | $encrypted = Crypt::encryptString('Hello world.'); $decrypted = Crypt::decryptString($encrypted); |
AndroidはLaravelCryptoクラスを以下のように使用します。
“enter your app key”を使用する環境に合わせて変更してください。
(.envのAPP_KEYの値から”base64:”を除いた値です。)
結構長い文字列なのでXMLに記述したほうが使いやすいです。
1 2 3 | val crypto = LaravelCrypto("enter your app key") val encrypted = crypto.encrypto("Hello world!") val decrypted = crypto.decrypto(encrypted.data) |
動作環境
上記ソースの動作確認は以下で行いました。
【Android】
Android Studio 3.3
Kotlin 1.3.11
gradle 3.3.0
compileSdkVersion 28
minSdkVersion 21
targetSdkVersion 28
【API】
Laravel 5.5.*
PHP 7.2.5
最後に
KotlinとLaravelでの暗号化・復号の連携ができるようになりました。
皆さんの良き開発ライフを祈っております。
コメントを残す