暗号を安全に使用するために
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 が必要です。
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復号化データ:こんにちは