はじめに

みなさん、Tink ってご存知ですか?Google が開発した暗号化ライブラリです(名前がかわいい)。
Google Cloud も活用しつつ利用可能な暗号化ライブラリでして、調べてみたら様々な用途で適用できそうなものでした。
今回実際に試してみましたので、Tink とはというところから、2種類の暗号化パターンをご紹介及び、それぞれ実際にやっていきたいと思います。
ブログ記事は2本立てでこんな感じで分けたいと思います。

  • 前編(本記事): Tink の概要と、Cloud KMS 連携での AEAD 暗号化
  • 後編: Streaming AEAD によるストリーミング暗号化と、KMS 非接続のオフライン暗号化

この記事で扱うこと:

  • Tink の概要と Google Cloud(Cloud KMS)との連携
  • 通常の暗号化(AEAD)の実装と実行結果
  • 暗号化前後のファイルがどう変わるかの可視化

この記事で扱わないこと:

  • 暗号理論の数学的な詳細
  • 本番環境向けのセキュリティ設計(鍵ローテーション運用等)

んじゃはじめます。

1. Tink とは

Tink は、Google が開発・公開しているオープンソースの暗号化ライブラリです。
「暗号化は正しく使うのが難しい」という課題に対して、誤用しにくい API 設計を目指して作られています。例えば、非推奨なアルゴリズムを選択しにくくなっていたり、鍵管理の仕組みが組み込まれていたりします。

主な特徴:

特徴 内容
マルチ言語対応 Java, Python, C++, Go, Objective-C
鍵管理の抽象化 Cloud KMS, AWS KMS 等と透過的に連携可能
プリミティブベース AEAD, Streaming AEAD, MAC, デジタル署名等を統一的なインターフェースで提供
誤用防止 安全でないアルゴリズムの選択を困難にする設計

Google Cloud との関係で言うと、Cloud KMS の鍵を Tink から直接参照でき、エンベロープ暗号化(データ暗号化鍵を KMS の鍵でさらに暗号化する方式)が簡単に実現できます。

2. 今回試す暗号化の2パターン

今回は Tink が提供する暗号化プリミティブのうち、以下の2つを試します。

AEAD(通常の暗号化)

AEAD(Authenticated Encryption with Associated Data) は、データ全体を一括で暗号化します。

特徴としては以下です。

  • ファイル全体をメモリに読み込んで暗号化
  • 小さいファイル(数MB程度まで)に向いている
  • 暗号化と同時に改ざん検知も行う(Authenticated の部分)。暗号化時に MAC(メッセージ認証コード)を生成して暗号文と一緒に保存し、復号時に再計算して一致するか検証する。暗号文が途中で書き換えられていれば復号を拒否する仕組み
  • Cloud KMS と組み合わせることで、エンベロープ暗号化(データ暗号化鍵を KMS のマスターキーでさらに暗号化する方式)にも対応。鍵管理を Cloud KMS に任せつつ、AEAD による暗号化等に利用できる

※当該資料で利用している画像データは生成 AI による画像を利用しております。

Streaming AEAD(ストリーミング暗号化)

Streaming AEAD は、データをセグメント単位でチャンク処理します。

特徴としては以下です。

  • ファイルを少しずつ読み込みながら暗号化
  • メモリ使用量が一定(大容量ファイルでも安心)
  • 数GB〜数TBのファイルにも対応可能

比較表

項目 AEAD Streaming AEAD
処理方式 一括 ストリーミング(セグメント単位)
メモリ使用量 ファイルサイズ分 一定(セグメントサイズ分)
適したファイルサイズ 〜数MB 数MB〜数TB
暗号化オーバーヘッド 数十バイト セグメントあたり数十バイト(全体で0.002%未満)
ユースケース 設定ファイル、トークン等 ログ、バックアップ、大容量データ等

本記事(前編)では Cloud KMS と連携した AEAD を実際にやっていきます。
後編では Streaming AEAD を行って違いが見れればと思います。

3. 環境準備

今回の検証環境で必要なものは以下です。

  • Python
  • tink
  • Google Cloud プロジェクト
  • gcloud CLI

Tink のインストール

pip install tink google-cloud-kms

Cloud KMS の鍵を作成

オンライン版で使用する対称鍵(エンベロープ暗号化用)を作成します。

# 鍵リングの作成
gcloud kms keyrings create tink-blog-keyring \
  --location asia-northeast1

# 対称暗号化鍵の作成(エンベロープ暗号化の KEK として使用)
gcloud kms keys create tink-blog-key \
  --location asia-northeast1 \
  --keyring tink-blog-keyring \
  --purpose encryption

4. Cloud KMS 連携で AEAD 暗号化してみる

テスト用ファイルの作成

echo "これはテスト用の機密データです。Tink AEAD で暗号化します。" > plaintext_small.txt

暗号化スクリプト(aead_demo.py

内容的には、、

  • スクリプト開始時にファイル内容だして
  • 暗号化終わったら、その中身開いて
  • そのあとまた復号化して中身開いて
  • 同じ内容で戻ってるよ

っていうスクリプトです。

import tink
from tink import aead
from tink.integration import gcpkms
import os
import subprocess

# --- 設定 ---
PROJECT_ID = os.environ.get("PROJECT_ID", "your-project-id")
LOCATION = "asia-northeast1"
KEYRING = "tink-blog-keyring"
KEY = "tink-blog-key"
KEK_URI = f"gcp-kms://projects/{PROJECT_ID}/locations/{LOCATION}/keyRings/{KEYRING}/cryptoKeys/{KEY}"

# --- 初期化 ---
aead.register()
gcpkms.GcpKmsClient.register_client(key_uri=KEK_URI, credentials_path="")

key_template = aead.aead_key_templates.create_kms_envelope_aead_key_template(
    kek_uri=KEK_URI,
    dek_template=aead.aead_key_templates.AES256_GCM
)
keyset_handle = tink.new_keyset_handle(key_template)
cipher = keyset_handle.primitive(aead.Aead)

# --- 暗号化 ---
print("=" * 50)
print("AEAD 暗号化デモ")
print("=" * 50)

with open("plaintext_small.txt", "rb") as f:
    plaintext = f.read()

print(f"\n[1] 平文の内容:")
print(f"    {plaintext.decode('utf-8')}")
print(f"    サイズ: {len(plaintext)} bytes")

ciphertext = cipher.encrypt(plaintext, b"associated-data:plaintext_small.txt")

with open("encrypted_small.bin", "wb") as f:
    f.write(ciphertext)

print(f"\n[2] 暗号化完了:")
print(f"    出力ファイル: encrypted_small.bin")
print(f"    サイズ: {len(ciphertext)} bytes")

# 暗号化ファイルの中身を確認(hexdump)
print(f"\n[3] 暗号化ファイルの中身(先頭64バイト):")
hex_output = subprocess.run(
    ["hexdump", "-C", "-n", "64", "encrypted_small.bin"],
    capture_output=True, text=True
)
print(hex_output.stdout)

# --- 復号 ---
with open("encrypted_small.bin", "rb") as f:
    ciphertext_read = f.read()

decrypted = cipher.decrypt(ciphertext_read, b"associated-data:plaintext_small.txt")

with open("decrypted_small.txt", "wb") as f:
    f.write(decrypted)

print(f"[4] 復号完了:")
print(f"    復号後の内容: {decrypted.decode('utf-8')}")

# --- 一致確認 ---
print(f"\n[5] 平文と復号結果の一致確認:")
if plaintext == decrypted:
    print("    -> 一致! 暗号化・復号が正常に動作しています。")
else:
    print("    -> 不一致! 何かがおかしいです。")

実行します

python aead_demo.py

実行結果

==================================================
AEAD 暗号化デモ
==================================================

[1] 平文の内容:
    これはテスト用の機密データです。Tink AEAD で暗号化します。

    サイズ: 83 bytes

[2] 暗号化完了:
    出力ファイル: encrypted_small.bin
    サイズ: 230 bytes

[3] 暗号化ファイルの中身(先頭64バイト):
00000000  00 00 00 73 0a 24 00 0e  5d de 28 16 e7 94 1c f4  |...s.$..].(.....|
00000010  05 3d 6f 8c e5 a3 7b 3b  07 70 47 ea 25 e0 f8 58  |.=o...{;.pG.%..X|
00000020  11 28 02 9c 06 c8 5b db  07 6f 12 4b 00 60 58 7f  |.(....[..o.K.`X.|
00000030  71 b7 d6 c7 a3 43 85 60  e4 8b 81 ec 7b 46 dd bc  |q....C.`....{F..|

[4] 復号完了:
    復号後の内容: これはテスト用の機密データです。Tink AEAD で暗号化します。

[5] 平文と復号結果の一致確認:
    -> 一致! 暗号化・復号が正常に動作しています。

83 バイトの平文が 230 バイトの暗号文になっています。増加分はエンベロープ暗号化のメタデータ(暗号化済み DEK 等)です。

暗号化状況の確認

暗号化されたファイルの中身、ファイルが同じことを確認します。

echo ""
echo "=== 平文の hexdump(先頭) ==="
hexdump -C -n 64 plaintext_small.txt
echo ""
echo "=== 暗号文の hexdump(先頭) ==="
hexdump -C -n 64 encrypted_small.bin
echo ""
echo "=== 復号結果の一致確認 ==="
diff plaintext_small.txt decrypted_small.txt && echo "小ファイル: 一致"

結果が以下です。

=== 平文の hexdump(先頭) ===
00000000  e3 81 93 e3 82 8c e3 81  af e3 83 86 e3 82 b9 e3  |................|
00000010  83 88 e7 94 a8 e3 81 ae  e6 a9 9f e5 af 86 e3 83  |................|
00000020  87 e3 83 bc e3 82 bf e3  81 a7 e3 81 99 e3 80 82  |................|
00000030  54 69 6e 6b 20 41 45 41  44 20 e3 81 a7 e6 9a 97  |Tink AEAD ......|

=== 暗号文の hexdump(先頭) ===
00000000  00 00 00 73 0a 24 00 0e  5d de 28 16 e7 94 1c f4  |...s.$..].(.....|
00000010  05 3d 6f 8c e5 a3 7b 3b  07 70 47 ea 25 e0 f8 58  |.=o...{;.pG.%..X|
00000020  11 28 02 9c 06 c8 5b db  07 6f 12 4b 00 60 58 7f  |.(....[..o.K.`X.|
00000030  71 b7 d6 c7 a3 43 85 60  e4 8b 81 ec 7b 46 dd bc  |q....C.`....{F..|

=== 復号結果の一致確認 ===
小ファイル: 一致

平文の hexdump では UTF-8 のバイト列や Tink AEAD という文字列が確認できますが、暗号文は完全にランダムなバイト列になっており、元のデータは一切読み取れなくなっています。
また暗号化前のファイルと、復号化後のファイルを比較して同じファイルであることもわかります。

まとめ

今回は Google の暗号化ライブラリ Tink を使って、Cloud KMS 連携での AEAD を試しました。

エンベロープ暗号化では、DEK(データ暗号化鍵)の生成から暗号化まで Tink と Cloud KMS が自動で処理してくれるため、鍵管理を自分で実装する必要がなくなり、オンラインで接続できる環境であれば負担を軽減できる仕組みでした。

なお、AEAD はファイル全体をメモリに読み込む方式のため、大きいファイルには向いてなさそうです。
後編では、大きいファイルにマッチしそうな Streaming AEAD での暗号化復号化と、Cloud KMS に接続できないようなケースで、Cloud KMS を利用した暗号化を試します。

参考