0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APKからAPIキーが読めてしまう ― Lambda+API Gatewayに逃がして分かったこと

0
Last updated at Posted at 2026-10-18

はじめに

Androidアプリに埋め込んだAPIキーは、APKを逆コンパイルすれば読めます。では「キーをLambdaの環境変数に移して、アプリはLambda経由でAPIを呼ぶ」ようにすれば安全になるのでしょうか。

結論から書くと、Lambdaを挟んだだけでは安全にはなりません。

  • Lambdaの環境変数にAPIキーを置くことで、APKから直接APIキーを読まれることはなくなります。
  • しかし、Lambdaを呼ぶための入り口であるAPI GatewayキーはAPKに残る
  • そのため、APIキーを隠すだけでなく、「使える量に上限をつけて被害を限定する」必要がある

本記事では、以前に書いた「書籍管理アプリ(Kotlin×Google Books API)」の続きとして、APIキーをLambda+API Gatewayに逃がすことで、どのようなことが分かったかをまとめます。

こんな人に向けて書いています

  • AndroidアプリにAPIキーを埋め込んでいるが、APKから読まれるのが怖い人
  • AWSは触り始めたばかりで、API Gatewayの使用量プランを動かして確かめたい人

スクリーンショット内のキーは、すべてダミー値(SampleAPIKey、SampleAPIGatewayKey)に差し替えています。

用語 ひとことで
Lambda サーバーを持たずに、コードを呼ばれたときだけ動かせるサービス
API Gateway Lambdaなどの前に置く、HTTPの入口。認証や流量制限を担当できる
使用量プラン API Gatewayで、APIキーごとに「秒間◯回、1日◯回まで」と上限を決める機能
429 「リクエストが多すぎる」を表すHTTPステータス。上限を超えるとこれが返る

前提

1. 構成図

Before

2. Before: APKを開いたらキーが見えた

デバッグAPK('app-debug.apk')を逆コンパイルツールであるjadxで開くと、'BuildConfig'クラスにGOOGLE_BOOKS_API_KEYが平文の文字列定数として入っていました。

BeforeJadx

キーの値はlocal.properties由来です。local.propertiesは.gitignoreに記載し、Gitには載りませんが、ビルド時にAPKへ平文で埋め込まれます。「Git管理外=安全」ではなく、今後多くのユーザに使用してもらおうとAPKを配布した際に備えて、ビルド成果物まで含めて考える必要があります。

実装と計測

1. Lambdaに逃がす

Google Books APIとの通信をLambdaに任せ、APIキーはLambdaの環境変数に置きます。アプリはLambdaの前に置いたAPI Gateway(GET /books?q=)を呼ぶだけになります。

Lambda側(Java 21)の主な設計は、次の通りです。

項目 決めたこと 理由
Googleキーの送り方 x-goog-api-keyヘッダー URLにキーが残らない
上流エラー Googleの4xx/5xx/タイムアウトは502に変換 こちらの設定ミス(500)と区別する
ログ ステータスとreasonのみ。キー・URLは出さない 秘匿と原因調査の両立
実行ロール AWSLambdaBasicExecutionRoleのみ ログ書き込み以外の権限を与えない

実装の詳細は、後日別記事にまとめる予定です。

2. 構成図

After

3. 実装

修正を行う前にコード(BookRepository.ktとApiService.kt)を確認しました。BookRepositoryは受け取ったキーをそのままApiServiceに渡すだけの単純設計であったため、隠れた依存はありませんでした。以下に変更点をまとめます。

ファイル 変更
ApiKeyInterceptor.kt(新規) 全リクエストにx-api-keyヘッダーを付与
(ビルド設定)build.gradle.kts / local.properties GATEWAY_API_KEYのbuildConfigFieldを追加(APIキーと同じ)
ApiService.kt パスをvolumes→booksに変更、キーの@Queryを削除
RetrofitInstance.kt BASE_URLをAPI GatewayのURLに変更、OkHttpClientにInterceptorを追加
BookRepository.kt searchBooksからapiKey引数を削除
SearchActivity.kt BuildConfig.GOOGLE_BOOKS_API_KEYのimportと参照を削除

記事内でコード記述は割愛します。
プロジェクト全体のソースコードは以下のGitHubで公開しています。

https://github.com/A0yyy36/TB_App/tree/v1-api-gateway

4. After: アプリ側でAPIキーは消えた。でも...

Beforeと同じ手順・同じツールで、今度はリリースビルド(./gradlew assembleRelease)のAPKを開き、BuildConfigを確認しました。

フィールド 状態
GOOGLE_BOOKS_API_KEY 実キー値なし
GATEWAY_API_KEY 値が埋め込まれている(AWS形式。AIzaではない)

AfterJadx

Googleのキーは消えました。でも、API Gatewayのキーは、APKから読めます。

つまり、入口は公開のままです。Lambdaを挟んで「隠せた」のは中身(Googleのキー)だけで、入口を呼ぶための鍵は、結局アプリに持たせるしかありません。

5. 使用量プランで被害を限定する

鍵が抜かれる前提なら、抜かれても困らないようにします。API Gatewayの使用量プランで、鍵ごとに使える量へ上限を付けます。

5-1. APIキーの必須化

GET /booksにAPIキー必須の設定をして、挙動を確認しました。

  • x-api-keyなし → 403
  • x-api-keyあり → 200

5-2. 使用量プランの設定

設定 値
レート 5リクエスト/秒
バースト 10
クォータ 1,000回/日

5-3. 負荷テスト

15秒間、8並列でGET /booksを連打しました(3回実施)。

結果 件数
200(成功) 83〜85件(3回平均 84件)
429(制限) 平均 1,311件
合計 平均 1,395件

理論値は、バースト分(最初から使える量)と、15秒間の補充分を足した値です。

5(リクエスト/秒) × 15(秒) + 10(バースト) = 85件

実測の200は83〜85件で、**理論値とほぼ一致**しました。

理論値と実測値が完全に一致していない理由
AWSのドキュメントでは、スロットリングは「ベストエフォート」で適用されることが説明されています。API Gatewayは複数のノードで分散処理されているため、トークンの管理が1つのバケットで完結せず、数件のズレがでることがあります。今回の計測では、3回の平均が理論値と一致しているため、ほぼ正しい挙動と判断しました。
参考資料

5-4. 日次クォーターテスト

クォータを50回/日に下げて連打すると、50件が200、超過後の5件が429(Limit Exceeded)で、計55リクエストで止まりました。

つまり、鍵が抜かれても、1つの鍵で使える量には上限があるということです。「被害の最大値を設計で決められる」のが、使用量プランの価値です。

本構成の問題点

枠は共有です。 使用量プランの上限は、その鍵を使う全員で共有されます。悪意のある誰かがGatewayキーを抜いて枠を使い切ると、正規のユーザーも429になります。「被害の上限」は決められても、「被害に遭わない」わけではありません。

また、旧Googleキーは、Beforeで実キーを使っていたと確定したため、削除・再発行が必須です。

運用面では、CloudWatchのロググループ保持期間(デフォルトは無期限)と、AWS Budgetsの月額アラートも設定したいところです。

今後の展望

**認証・認可(Cognito)**で、ログイン済みのユーザーだけが呼べるようにします。JWT(期限付きの署名付きトークン)で「誰が使えるか」を決められるので、共有枠の問題に対処できます。今回は未検証です。

まとめ

  • Lambdaを挟むと、Googleのキーは消せる。ただしAPI Gatewayのキーは、APKに残る
  • 入口が公開のままなので、使用量プランで被害を限定する。5rps / バースト10 / 1,000回/日の設定では、15秒の持続負荷で200が83〜85件(理論値85件)に収まり、残りは429になった。
    「隠す」と「被害を限定する」は別の話です。キーを隠せたと安心せず、抜かれても困らない設計かどうかを確かめるところまでやって、初めて価値が出るのだと思います。

関連記事

参考記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?