株式会社renue
AI導入・DXの悩みをプロに相談してみませんか?
AIやDXに関する悩みがありましたら、お気軽にrenueの無料相談をご利用ください。 renueのAI支援実績、コンサルティングの方針や進め方をご紹介します。
広告運用プラットフォーム(Google Ads/Meta/TikTok/X/LINE)のAPI連携を社内ツールに組み込む際、最大の設計課題が「顧客ごとのアクセストークン/リフレッシュトークン/デベロッパートークンをどう保管するか」だ。平文でDBに置けば事故一発で全顧客のアカウントが危険にさらされる。本記事ではマルチテナント基盤で一般的に採用されるFernet対称鍵暗号 + SQLAlchemy型変換レイヤーによる認証情報永続化パターンを、実装コードと共に解説する。
なぜ「平文でDBに保存」がダメなのか
- DB漏洩即破滅: MySQLダンプ1つで全顧客のAPIアカウントが第三者に使える状態になる
- バックアップ拡散: 定期バックアップ・レプリカ・開発環境コピーに平文が残る
- ログ混入リスク: クエリログや
str(model)で平文が吐き出される事故 - 開発者の過剰権限: アプリ開発者が本番DBを覗けると、顧客の広告アカウントを閲覧できてしまう
- コンプライアンス: プライバシーマーク/ISMS/SOC2 監査で「暗号化していない」は即指摘
一方で「KMS/Vaultを1発導入」は中小SaaSには重すぎる。本記事のゴールは「Pythonのcryptographyライブラリだけで、重厚なインフラ追加なしに」認証情報を安全に永続化する現実解を提示することだ。
Fernet: 何を選んで何を捨てるか
Fernetはcryptographyライブラリが提供する対称鍵認証付き暗号化スキームだ。内部的にはAES-128-CBC + HMAC-SHA256で構成されている。
| 特性 | Fernetの採用 |
|---|---|
| アルゴリズム | AES-128-CBC + HMAC-SHA256 (認証付き暗号) |
| 鍵長 | 32バイト(256ビット) URL-safe base64 |
| IV | 暗号化ごとにランダム生成(自動) |
| トークン形式 | version + timestamp + IV + ciphertext + HMAC |
| 改ざん検知 | あり(HMAC) |
| 鍵ローテーション | MultiFernetで複数鍵同時対応可 |
選ばなかった選択肢
- 自前AES実装: IVとHMACの実装ミスが命取り — 選ばない
- MySQL AES_ENCRYPT関数: 鍵がSQLログに漏れる・ECBモードで弱い
- Azure Key Vault直接: 各リクエストでKMSコール発生=コスト/レイテンシ増
- pgp_sym_encrypt (PostgreSQL): MySQL環境では使えない
レイヤー1: 極小のcipherラッパー(30行)
認証情報暗号化のコア実装は驚くほどシンプルで、実用上30行で十分だ。
設計上の勘所
- None透過:
Noneが来たらNoneを返す — DBのNULLableカラムと整合 - 鍵は毎回settingsから取る: 将来KMS経由で動的取得に置き換えやすい
- InvalidTokenをValueErrorにラップ: 呼び出し側の例外処理を薄くできる
- UTF-8固定: 日本語文字列(account_name等)も暗号化対象になるため明示
- モジュールレベル関数で提供: クラス化するほどの状態がない — Fernetインスタンスはステートレス
レイヤー2: 鍵生成とsettings配置
Fernet鍵の生成は1コマンドで済む。
これを専用の環境変数として設定する。
環境別の鍵管理戦略
| 環境 | 鍵の保管場所 | 配布方法 |
|---|---|---|
| ローカル開発 | .env.local (gitignore) | 1Password等で共有 |
| CI | GitHub Actions Secrets | 組織管理者のみアクセス |
| ステージング | Azure Key Vault → App Service環境変数 | Managed Identity経由 |
| 本番 | Azure Key Vault → App Service環境変数 | 限定ロールのみ閲覧可 |
重要なのは環境ごとに別の鍵を使うこと。本番の鍵で暗号化したデータを開発環境にコピーしても復号できない設計にすると、バックアップからの情報漏洩リスクを最小化できる。
レイヤー3: 複数広告プラットフォームをまたぐ共通利用
この暗号化ヘルパーの真価は「5広告プラットフォームで同一パターンを使い回せる」点だ。Google Ads/Meta/TikTok/X/LINE Adsそれぞれに別実装を書く必要がない。
Meta/TikTok/X/LINEのルーターも全く同じ共通の復号ヘルパーを通すパターン。読みやすさと監査性が両立する。
保存時のパターン(create/update)
認証情報カラムは常に共通の暗号化ヘルパーを通す。識別用のaccount_name等は平文という使い分けが実運用で重要になる(検索・UI表示で必要)。
レイヤー4: SQLAlchemy TypeDecoratorで完全自動化(発展形)
暗号化・復号の呼び出しを書き忘れると致命的な事故になる。発展形としてSQLAlchemy TypeDecoratorで暗号化を型レベルに埋め込む設計がある。
この設計だとアプリケーションコード側は暗号化の存在を意識しない。SELECT時に自動復号、INSERT/UPDATE時に自動暗号化される。ただしLIKE検索や完全一致検索ができなくなる副作用があるため、検索対象のカラムには使わない。
レイヤー5: 鍵ローテーション(MultiFernet)
「鍵は半年〜1年で交換する」のがセキュリティベストプラクティスだ。FernetはMultiFernetで複数鍵を同時に扱えるため、ダウンタイムゼロの鍵交換が可能。
鍵ローテーションの5段階フロー
- 新鍵生成: Fernetの鍵生成機能で新鍵を作る
- MultiFernetに追加:
[new_key, old_key]の順で両方サポート状態に - rewrap バッチ: 既存DB行を順次読み出し→復号→新鍵で再暗号化して更新
- 旧鍵除去: 全件rewrap完了後、旧鍵を環境変数から削除
- MultiFernet撤去: 単一鍵運用に戻す
rewrap処理は深夜メンテ枠で回し、進捗はログに記録しておくと安心だ。
よくある設計ミスと対策
| ミス | 影響 | 対策 |
|---|---|---|
| 鍵をgit管理 | リポジトリ全体が危険 | .gitignore + pre-commit hook |
| 鍵を全環境共通 | 開発DBコピーから本番漏洩 | 環境別鍵 + 定期ローテ |
| 鍵をログに出力 | ログ基盤経由で漏洩 | ロガーのredact設定 |
| 復号結果をキャッシュ | メモリダンプで漏洩 | 必要な時だけ復号・破棄 |
| exception messageに平文 | エラー監視SaaS経由で漏洩 | exceptionは汎用メッセージのみ |
KMS/Vaultに移行する判断基準
本記事のFernet + env_varパターンは「スタートアップ〜中規模SaaS」に最適解だが、以下の状況ではAzure Key Vault / AWS KMS / HashiCorp Vault への移行を検討すべき。
- SOC2 Type 2/ISO 27001取得が必要: 鍵管理の監査ログが要求される
- 開発者が50人を超えた: 鍵配布の人間リスクが無視できない
- マルチリージョン展開: リージョンごとに鍵管理が必要
- Hardware Security Module要件: FIPS 140-2等のコンプラ要件がある
- 自動ローテーションを組み込みたい: MultiFernet手運用は限界がある
まとめ
- 30行のFernetラッパーで広告5社分の認証情報を統一パターンで暗号化できる
- None透過 + UTF-8固定 + InvalidToken→ValueErrorラップでAPIが綺麗になる
- 環境別鍵で開発DBコピーからの情報漏洩を防ぐ
- TypeDecorator発展形で暗号化を型に埋め込み、実装ミスをゼロにする
- MultiFernetでダウンタイムゼロ鍵ローテーションを実現
- KMS/Vault移行判断はスケール/コンプラ要件を基準に




