【Java初心者】Google Tinkを使ったAEADテキスト暗号化

# 暗号を安全に使用するために

Google Tinkのサイト( https://github.com/google/tink )のIntroductionには

あなたのアプリケーションで暗号を使用することは、暗闇でチェーンソーを投げるような感覚になるはずはありません。Tinkは、Googleの暗号学者やセキュリティエンジニアのグループによって書かれた暗号ライブラリです。これは、Googleの製品チームと協力し、実装の弱点を修正し、暗号のバックグラウンドがなくても安全に使用できるシンプルなAPIを提供するために生まれました。

と書かれています。「暗闇でチェーンソー」という表現はユニークですが、暗号の知識がなくても安全に使用できるAPIを作るという思想は素晴らしいと思います。

# AEADとは

Wikipediaにはこうあります( https://ja.wikipedia.org/wiki/%E8%AA%8D%E8%A8%BC%E4%BB%98%E3%81%8D%E6%9A%97%E5%8F%B7 )

認証付き暗号（AE: Authenticated Encryption あるいは AEAD: Authenticated Encryption with Associated Data) とは、データの秘匿性、完全性、および認証性を同時に提供する暗号利用モードである。利用の容易な API ひとつでこうした特性が提供されるうえ、復号すると同時に完全性も検証される。

共通鍵暗号方式ではありますが、データの暗号化による「秘匿性」と、復号時に元のデータとの不整合がないことの両方を満たす方式です。

# Javaで書いてみました

Tinkは、バージョンアップのたびに、APIが変わり、以前書いたコードが非推奨となることが多いため、その都度ドキュメントを見ながら修正しています。

以下に示すコードはtink-1.12.0.jarを用いています。依存ライブラリーとして protobuf-java と gson が必要です。

```java
package sample2024;

import com.google.crypto.tink.Aead;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeyTemplates;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.aead.AeadConfig;
import java.io.IOException;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.util.Base64;

public class AeadTest {

    public static void main(String[] args) {
        try {
            String plainText = "こんにちは"; // 平文テキスト
            String password = "password"; // パスワード
            AeadConfig.register();
            System.out.println("AEAD秘密鍵を生成");
            KeysetHandle privateAeadKeysetHandle = KeysetHandle.generateNew(KeyTemplates.get("AES128_GCM"));
            // 秘密鍵の本体
            String serializedKeyset
                    = TinkJsonProtoKeysetFormat.serializeKeyset(privateAeadKeysetHandle, InsecureSecretKeyAccess.get());
            //
            // 秘密鍵をファイルに保存する場合(セキュリティー上推奨されていません)
            // String aeadKey = "aead_priv.key";
            // Path aeadKeyFile = Paths.get(aeadKey);
            // Files.write(aeadKeyFile, serializedKeyset.getBytes(UTF_8));
            //
            // 秘密鍵をファイルから読み込む(セキュリティー上推奨されていません)
            // serializedKeyset = Files.readString(aeadKeyFile, UTF_8);

            System.out.println("AEAD暗号化します");
            System.out.println("平文:" + plainText);
            KeysetHandle keysetHandle = CleartextKeysetHandle.read(
                    JsonKeysetReader.withString(serializedKeyset));
            Aead aead = keysetHandle.getPrimitive(Aead.class);
            byte[] ciphertext = aead.encrypt(plainText.getBytes(), password.getBytes());
            // Base64でエンコードする
            String AeadEncrypt = Base64.getEncoder().encodeToString(ciphertext);
            System.out.println("AEAD暗号化データ:" + AeadEncrypt);

            System.out.println("AEAD復号化します");

            KeysetHandle dkeysetHandle = CleartextKeysetHandle.read(
                    JsonKeysetReader.withString(serializedKeyset));
            Aead daead = dkeysetHandle.getPrimitive(Aead.class);
            String AeadDecrypt = new String(daead.decrypt(
                    Base64.getDecoder().decode(AeadEncrypt), password.getBytes()));

            System.out.println("AEAD復号化データ:" + AeadDecrypt);
        } catch (IOException | GeneralSecurityException e) {
            System.err.println(e.getMessage());
        }
    }
}
```

# 実行結果

```
AEAD秘密鍵を生成
AEAD暗号化します
平文:こんにちは
AEAD暗号化データ:AVhtWzgUhjHe1Vls0HVPgp7Ajzm+0jDmDeTeKC1iqPx3pwoiuUVMFBKPzogagGAe
AEAD復号化します
AEAD復号化データ:こんにちは
```