【Kotlin/Android】Google Tink Cryptoの使い方!暗号化/複合化の方法

【Kotlin/Android】Google Tink Cryptoの使い方!暗号化/複合化の方法

この記事からわかること

  • Android Studio/KotlinGoogle Tink Crypto使い方
  • 暗号化複合化実装する方法
  • AEAD/MACとは?
  • AES-GCM利用して暗号化するには?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Google Tink Crypto

Source:Google Tink Crypto
公式リファレンス:Tink Cryptographic Library

Google Tink Crypto」はGoogleが暗号化やデジタル署名などのセキュリティ機能を提供するライブラリです。オープンソースかつクロスプラットフォームで活用することができるのでAndroidアプリだけでなくiOSアプリでも機能を利用することができます。

AndroidのKey StoreやiOSのKeychainを利用した鍵の暗号化や保存機能も提供してくれているので複雑な暗号化や鍵の取り扱いを少ないコードで実装することができるようになっています。

Tinkでは「AEAD」などの暗号化方式や「MAC」などの整合性を検証する機能をサポートしています。

AEADとは

Wikipedia:認証付き暗号

AEAD(Authenticated Encryption with Associated Data)とはデータの機密性と完全性、だけでなく改ざん検出のための認証性も提供する暗号化方式です。データの暗号化だけでなく暗号文に認証情報を付加し、データの改ざんを検出できるようになっています。

AEADでは「AES:共通鍵暗号方式(Advanced Encryption Standard)」や「ChaCha20-Poly1305」などの暗号化アルゴリズムを使用します。

暗号化や複合化についてや暗号化アルゴリズムの種類については以下記事も参考にしてください。

おすすめ記事:暗号化と複合化について

MACとは

Wikipedia:メッセージ認証符号

MAC(Message Authentication Code)メッセージの整合性を検証するための認証タグを生成する技術です。具体的にはメッセージに対してMACアルゴリズム(HMACなど)を使用して生成された固定長のタグ(MAC)を算出しmメッセージが改ざんされていないことを確認することができます。

導入方法

AndroidアプリにTinkを導入するには「build.gradle」に以下のように導入します。

dependencies {
  implementation 'com.google.crypto.tink:tink-android:1.7.0'
}

TinkでAEADのAES-GCMを利用して文字列を暗号化・複合化する方法

Tinkを使用して「AEADのAES-GCMを利用して文字列を暗号化・複合化する方法」をまとめていきます。暗号化・複合化を行う専用クラスを実装していきます。

  1. Tinkを初期化
  2. AndroidKeysetManagerを使用してKeysetを管理
  3. 暗号化
  4. 複合化

1.Tinkを初期化

暗号化・複合化を行う機能を保持するTinkManagerを定義します。まずはイニシャライザの中でAeadConfigを初期化します。


class TinkManager(context: Context) {

    init {
        // Tinkを初期化
        AeadConfig.register()
    }

}

2.AndroidKeysetManagerを使用してKeysetを管理

続いてTinkに定義されているAndroidKeysetManagerクラスを使用して暗号化鍵を安全に保存する処理を実装します。キーセットはSharedPreferencesに暗号鍵自体はKeyStoreに保存することができるようです。


class TinkManager(context: Context) {

    /** 暗号鍵セットを管理用 */
    private val keysetHandle: KeysetHandle
    /** AEADプリミティブを保持 */
    private val aead: Aead

    init {
        // Tinkを初期化
        AeadConfig.register()

        // AndroidKeysetManagerクラスで暗号鍵を安全に保存
        // 保存先にはSharedPreferencesを活用
        // 暗号化アルゴリズムをAES256-GCMに指定
        keysetHandle = AndroidKeysetManager.Builder()
            // 保存先ファイル名とキーを指定
            .withSharedPref(context, "master_keyset", "master_key_preference")
            .withKeyTemplate(KeyTemplates.get("AES256_GCM"))
            .withMasterKeyUri("android-keystore://master_key")
            .build()
            .keysetHandle

        // AEADプリミティブを取得
        aead = keysetHandle.getPrimitive(Aead::class.java)
    }
}

3.暗号化

生成したAEADプリミティブを使用して文字列の暗号化を行います。暗号化はaead.encryptで行います。暗号化する際に認証情報を渡すことでよりデータの整合性を確保することができますがこのデータは暗号化されないようです。


/**
  * 暗号化
  * @param  plaintext
  *  暗号化対象のテキスト
  * @param  associatedData
  *  認証用データ
  * @return
  *  暗号化されたバイト配列
  */
public fun encrypt(text: String, associatedData: String? = null): ByteArray {
    val plaintextBytes = text.toByteArray(StandardCharsets.UTF_8)
    val associatedDataBytes = associatedData?.toByteArray(StandardCharsets.UTF_8)
    return aead.encrypt(plaintextBytes, associatedDataBytes)
}

4.複合化

生成したAEADプリミティブを使用して暗号化された文字列の複合化を行います。暗号化はaead.decryptで行います。暗号化する際に認証情報を渡している場合は同じ認証情報文字列を渡します。


/**
  * 複合化
  * @param  ciphertext
  *  複合化対象のテキスト(暗号化された文字列)
  * @param  associatedData
  *  認証用データ
  * @return
  *  複合化した文字列
  */
public fun decrypt(ciphertext: ByteArray, associatedData: String? = null): String {
    val associatedDataBytes = associatedData?.toByteArray(StandardCharsets.UTF_8)
    val decryptedBytes = aead.decrypt(ciphertext, associatedDataBytes)
    return String(decryptedBytes, StandardCharsets.UTF_8)
}

使用方法

button.setOnClickListener{
    // TinkManagerインスタンスを生成
    val tinkManager = TinkManager(this)

    // データを暗号化
    val text = "Hello World!"
    val ciphertext = tinkManager.encrypt(text)
    println("Encrypted data: ${ciphertext}") // Encrypted data: [B@5cd1921

    // データを復号化
    val decryptedText = tinkManager.decrypt(ciphertext)
    println("Decrypted data: $decryptedText") // Decrypted data: Hello World!
}

認証情報を渡してみる

AEADの特徴である認証情報を渡して暗号化・複合化を行なってみます。認証情報文字列として渡すのはユーザーIDなどになるかと思います。

val plaintext = "Hello World!"
// 認証情報を渡す
val userId = UUID.randomUUID().toString()
val ciphertext = tinkManager.encrypt(plaintext, userId)
println("Encrypted data: ${ciphertext}")
val decryptedText = tinkManager.decrypt(ciphertext, userId)
println("Decrypted data: ${decryptedText.isSuccess}")

同じ認証情報を使用すれば問題なく暗号化・複合化を行うことができますが、暗号化・複合化に異なる認証情報を渡した場合は複合化の際にjava.security.GeneralSecurityException: decryption failedという例外を吐きます。

FATAL EXCEPTION: main
  Process: com.XXXXXX.プロジェクト名, PID: 19291
  java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:562)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
  Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971) 
  Caused by: java.security.GeneralSecurityException: decryption failed
    at com.google.crypto.tink.aead.AeadWrapper$WrappedAead.decrypt(AeadWrapper.java:112)
    at com.XXXXXX.プロジェクト名.Utility.TinkManager.decrypt(TinkManager.kt:62)

そのため複合化するdecryptメソッドは例外をキャッチできるようにしておいた方が安全です。

public fun decrypt(ciphertext: ByteArray, associatedData: String? = null): Result<String> {
  val associatedDataBytes = associatedData?.toByteArray(StandardCharsets.UTF_8)
  return kotlin.runCatching {
      val decryptedBytes = aead.decrypt(ciphertext, associatedDataBytes)
      String(decryptedBytes, StandardCharsets.UTF_8)
  }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index