SSL Pinningは、アプリケーションがCAの信頼チェーンに依存するのではなく、事前に既知のフィンガープリントまたは証明書に対してサーバー証明書を検証するセキュリティ技術です。標準的な検証とは異なり、ピニングは不正なルート認証局を通じたトラフィックの傍受を防ぎます。OWASP Mobile Security Testing Guide (2025)によると、この技術はMITM攻撃からの保護のためのトップ3推奨コントロールに含まれています。ピニングがない場合、不正なルート証明書を持つ攻撃者はアプリケーションのすべてのHTTPSトラフィックを復号化できます。
重要なポイント
SSL Pinningは、モバイルまたはウェブアプリケーションが信頼できるサーバー証明書または公開鍵を記憶し、証明書が保存されたものと一致しない接続をすべて拒否するセキュリティメカニズムです。標準的なHTTPSスキームでは、クライアントはルートCAまでの信頼チェーンを通じて証明書を検証します — 任意のCAが任意のドメインの証明書に署名できます。SSL Pinningはこの弱点を排除します:数百のCAを信頼する代わりに、アプリケーションは特定の1つの証明書のみを信頼します。
標準検証の問題は、数百のルートCAのいずれかがあなたのドメインに対して有効な証明書を発行できることです — 偶然または強制によって。独自のルート証明書を持つ企業プロキシにアクセスできる攻撃者は、ブラウザの警告なしにMITM攻撃を実行できます。SSL Pinningはこの脆弱性を閉じます:CAが不正な証明書を発行しても、フィンガープリントが記録されたものと一致しないため、アプリケーションはそれを拒否します。
モバイルアプリケーションでは、デバイスがしばしば安全でないネットワーク(公共Wi-Fi、トラフィック検査付きの企業プロキシ、感染したアクセスポイント)で動作するため、SSL Pinningが特に重要です。Verizon Mobile Security Index (2025)によると、モバイルアプリケーションでのデータ漏洩の60%以上がトランスポート層でのトラフィック傍受に関連しています。
モバイルアプリケーションは機密データ(認証トークン、支払い情報、ユーザーの個人データ)を送信します。追加の保護がない場合、デバイス上のルート証明書の置き換え(企業プロファイルや悪意のあるアプリケーションのインストール後など)によってHTTPSが危険にさらされる可能性があります。SSL Pinningは、デバイスに不正なルートCAがインストールされていても、アプリケーションが独自のホワイトリストに対して証明書を検証し続けることを保証します。
SSL Pinningのプロセスは、フィンガープリントの取得、接続時の検証、エラー処理の3つの段階で構成されます。開発中、エンジニアはサーバー証明書のSHA-256フィンガープリントを取得し(openssl x509 -fingerprint -sha256)、それをアプリケーションコードまたは設定ファイルに埋め込みます。HTTPSリクエストのたびに、アプリケーションは受信した証明書のフィンガープリントを計算し、保存されたものと比較します — 値が一致しない場合、接続は終了されます。
第一段階はビルド時のピニングです:開発者は事前にサーバー証明書を知っており、そのハッシュを埋め込みます。第二段階は最初の接続時のピニング(TOFU:trust on first use)です:アプリケーションは最初のリクエストで証明書を記憶し、後続のすべてのリクエストを検証するためにそれを使用します。TOFUは動的な環境に便利ですが、最初の攻撃に対して脆弱です — 最初の接続がすでに傍受されている場合、不正な証明書が信頼できるものとして受け入れられます。
重要な詳細はバックアップピン(backup pins)です。証明書には有効期限があり、交換されると、更新されていないアプリケーションはサーバーへの接続を失います。エンジニアは2〜3の追加フィンガープリントを含めます — 例えば、バックアップ証明書のフィンガープリントとルートCAのフィンガープリント。メインの証明書が変更されると、アプリケーションはバックアップピンに対して検証し、接続は機能し続けます。
# SHA-256証明書フィンガープリントの取得
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64
ピニングを実装するには2つの主要なアプローチがあります:証明書全体へのバインディング(certificate pinning)と公開鍵へのバインディング(public key pinning)です。各アプローチには、セキュリティと保守性に影響を与える独自の強みと制限があります。
| タイプ | バインディング対象 | 柔軟性 | セキュリティ |
|---|---|---|---|
| Certificate Pinning | X.509証明書全体 | 低い — 証明書変更時に更新が必要 | 高い — 正確なバインディング |
| Public Key Pinning | 証明書の公開鍵 | 中程度 — 鍵が新しい証明書に含まれる可能性あり | 高い — 証明書の詳細に影響されにくい |
| Hash Pinning | 証明書または鍵のSHA-256ハッシュ | 高い — 鍵を変更せずに証明書を変更可能 | 中程度 — ハッシュの強度に依存 |
証明書ピニングは最も厳格な方法です。アプリケーションは信頼できる証明書またはそのSHA-256フィンガープリントのコピーを保存し、HTTPS接続のたびにサーバー証明書と比較します。この方法は最大のセキュリティを提供しますが、ローテーション中に問題を引き起こします — 証明書は通常1〜2年有効で、その後強制的なアプリケーション更新が必要です。制御された更新サイクルを持つ重要なシステムに推奨されます。
公開鍵ピニングはより柔軟なアプローチです。証明書全体の代わりに、アプリケーションはサーバーのRSAまたはECDSA公開鍵のみを記憶します。会社が同じ鍵ペアを使用する場合、証明書の再発行時に鍵は変更されずに維持できます。これにより、アプリケーション更新の頻度が減少します。ただし、鍵が侵害された場合、すべてのクライアントでのカスケード交換が必要になります。
Appleプラットフォームでは、SSL PinningはURLSessionデリゲートを通じて実装されます。開発者はURLSessionDelegateプロトコルを実装するクラスを作成し、didReceive challengeメソッドをオーバーライドして、保存されたフィンガープリントに対して手動でサーバー証明書を検証します。別のアプローチは、構成を簡素化するServerTrustManagerを備えたAlamofireを使用することです。
class SSLPinningDelegate: NSObject, URLSessionDelegate {
let pinnedHash = "sha256/Wi24BE7j5qLk0iLvPq6ePEsVRqZ1yW0F6wLg="
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust
else { return completionHandler(.cancelAuthenticationChallenge, nil) }
if validate(serverTrust, pinnedHash) {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
この例では、デリゲートはURLSessionから認証リクエストを受け取り、challengeからserverTrustを抽出し、証明書のSHA-256フィンガープリントを保存されたものと比較します。フィンガープリントが一致する場合 — 接続は続行され、そうでなければchallengeは拒否されます。本番環境では、複数のバックアップピンの検証と監視のためのエラーログ記録を追加する価値があります。
iOS 14以降、AppleはInfo.plistを通じてCertificate Pinningの組み込みサポートを追加しました。開発者はNSAppTransportSecurityキーでNSPinnedDomainsサブ辞書とともに信頼できる証明書を指定します。このアプローチはコードを書く必要がありませんが、柔軟性に欠けます — ピンを動的に変更したり、検証エラーを記録したりすることはできません。
Androidでは、SSL Pinningを実装する3つの主要な方法があります:OkHttpライブラリのCertificatePinnerを通じて、XMLのNetwork Security Configを通じて、そしてHttpsURLConnectionのカスタム検証を通じてです。OkHttpは最も一般的で推奨されるアプローチであり、Retrofitやその他のHTTPクライアントで使用されています。
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com",
"sha256/Wi24BE7j5qLk0iLvPq6ePEsVRqZ1yW0F6wLg=")
.add("api.example.com",
"sha256/FiPq6ePEsVRqZ1yW0F6wLgWi24BE7j5qLk0iL=") // バックアップピン
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
OkHttpの設定では、開発者はドメインと1つ以上のSHA-256フィンガープリントを指定します。最初のフィンガープリントで、OkHttpはサーバー証明書を指定されたピンと比較します。一致しない場合、クライアントはSSLPeerUnverifiedExceptionをスローします。バックアップピンは必須です — これがないと、証明書が変更されたときにAPIリクエストが即座に失敗し始めます。
AndroidはAPI 24以降、XML設定を通じて宣言的なCertificate Pinningをサポートしています。ファイルres/xml/network_security_config.xmlには、ドメインとそのフィンガープリントのリストが含まれています。この方法は静的設定に便利ですが、異常のログ記録を伴うTOFUやカスタム検証ロジックを実装することはできません。
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-12-31">
<pin digest="SHA-256">
Wi24BE7j5qLk0iLvPq6ePEsVRqZ1yW0F6wLg=</pin>
<pin digest="SHA-256">
FiPq6ePEsVRqZ1yW0F6wLgWi24BE7j5qLk0iL=</pin>
</pin-set>
</domain-config>
</network-security-config>
SSL Pinningはモバイルアプリケーションのセキュリティを大幅に向上させますが、運用上の複雑さをもたらします。主な利点は、ルートCAが侵害された場合でもMITM攻撃からの保護です。アプリケーションは、公開認証局のインフラ全体ではなく、開発者が明示的に指定した証明書のみを信頼します。これは金融アプリケーション、メッセンジャー、機密データを扱うアプリケーションにとって特に重要です。
主な欠点は証明書ローテーションの複雑さです。証明書が期限切れまたは失効した場合、アプリケーションを更新していないユーザーは接続を失います。これはバックアップピンと段階的更新メカニズムによって解決されます:新しいアプリケーションは古い証明書と新しい証明書の両方を認識し、ユーザーの完全な更新後に古いピンはコードから削除されます。少なくとも2つのバックアップピンを含めることをお勧めします — 1つは現在の証明書用、もう1つは将来用です。
もう1つの妥協点は、ピニングを無効にせずにトラフィックデバッグ(Charles Proxy、Burp Suite)に公開プロキシを使用できないことです。これは開発中のネットワークリクエストのデバッグを複雑にします。解決策は条件付きコンパイルです:デバッグビルドではピニングが無効、リリースビルドでは有効です。OWASPは切り替えにBuildConfig.DEBUGフラグの使用を推奨しています。
| 側面 | 利点 | 欠点 |
|---|---|---|
| セキュリティ | 不正なCAを通じたMITMからの保護 | 鍵侵害時の複雑さ |
| 保守 | 明示的な信頼制御 | ローテーションにアプリケーション更新が必要 |
| デバッグ | 正しいサーバーへの接続を保証 | デバッグプロキシをブロック |
よくある質問
標準的なHTTPS検証は、既知のルートCAによって署名された任意の証明書を信頼します。SSL Pinningは特定の証明書または鍵のみを信頼します — CAが不正な証明書を発行した場合、アプリケーションはそれを拒否します。
証明書は通常1〜2年有効です。現在の証明書の有効期限の3〜6ヶ月前にピンを更新し、新しいフィンガープリントをバックアップピンとして追加し、ローテーション後に古いものを削除することをお勧めします。
はい、ただしCDNはエッジサーバー間の切り替え時に証明書を変更する可能性があることに注意してください。特定の証明書ではなく公開鍵にピニングし、複数のバックアップピンを使用することをお勧めします。
接続はエラーで終了します — AndroidではSSLPeerUnverifiedException、iOSではchallengeが.cancelAuthenticationChallengeで拒否されます。アプリケーションはこのエラーを適切に処理し、ユーザーに通知する必要があります。
いいえ、ただしOWASPは機密データを扱うアプリケーション(銀行、医療、企業システム)にそれを推奨しています。単純な読み取り専用アプリケーションの場合は、EV証明書を使用した標準的なHTTPS検証で通常十分です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。