Certificate Pinningは、サーバーの証明書または公開鍵を固定するメカニズムであり、アプリケーションは既知のフィンガープリントを使用してHTTPS接続を検証します。CAを介した標準的な信頼チェーンとは異なり、ピンニングは、たとえ侵害された認証局でもドメインの偽造証明書を発行できないことを保証します。OWASP MSTG(2025)によると、Certificate PinningはL2保護レベルのアプリケーションに必須のコントロールリストに含まれています。実装には、コード内での証明書ハッシュの保存と、各リクエスト時の検証が含まれます。
重要なポイント
Certificate Pinningは、アプリケーションが信頼する証明書のフィンガープリントを保存し、HTTPS接続を確立するための唯一の基準として使用するセキュリティ技術です。標準的なTLSモデルでは、クライアントはサーバーの証明書が信頼できるルートCAによって署名されていることを検証します — システムにプリインストールされた数百の認証局のいずれかです。Certificate Pinningはこのチェーンを直接検証に置き換えます。証明書は保存されたサンプルと一致するか、期待される公開鍵を含んでいなければなりません。
標準モデルの問題は、CA侵害インシデント — DigiNotar(2011)、Comodo(2011)、TrustCor(2022)— の後に明らかになりました。CAがドメインに対して偽造証明書を発行すると、ブラウザやアプリケーションはそれを有効として受け入れます。Certificate Pinningはこの攻撃を防止します。完全に署名された偽造証明書でも、そのフィンガープリントがアプリケーションに固定されたものと一致しないため、拒否されます。
ピンニングという用語は、pin(「ピン」または「固定具」)に由来します。開発者は信頼する証明書を固定し、それからの逸脱は接続をブロックします。Mitre CWE-295の調査によると、不適切な証明書検証はモバイルアプリケーションにおける最も危険なセキュリティエラーのトップ10の1つであり、Certificate Pinningはそれを防止する直接的な方法です。
当初、Certificate PinningはHPKP(HTTP Public Key Pinning)メカニズムを通じてブラウザで使用され、RFC 7469で標準化されました。開発者は期待されるキーのハッシュを含むHTTP Public-Key-Pinsヘッダーを送信し、ブラウザは指定された期間それらを保存しました。しかし、HPKPは危険であることが判明しました。設定ミスが1つあるだけでサイトを数ヶ月間ブロックする可能性がありました。2018年、ChromeはHPKPのサポートを終了し、現在の標準はクライアントサイド実装 — モバイルアプリケーションまたはブラウザ拡張機能内 — になりました。
Certificate Pinningのプロセスには、フィンガープリントの計算、接続の検証、エラー処理の3つの主要な段階が含まれます。準備段階で、開発者は本番サーバーの証明書または公開鍵のSHA-256ハッシュを取得します。GDPRおよびPCI DSS準拠のアプリケーションでは、チェーン内の中間CAのフィンガープリントも固定する必要があります。
HTTPSリクエストのたびに、アプリケーションはTLS認証コールバックをインターセプトし、サーバーの証明書を抽出してそのSHA-256ハッシュを計算します。このハッシュは、保存された信頼できるフィンガープリントのリストと比較されます。一致するものがあれば接続が続行されます。ない場合、アプリケーションは接続を終了し、攻撃者に実装の詳細を明かさずにエラーを報告する必要があります。
fun validateCertificate(certificate: X509Certificate,
expectedHash: String): Boolean {
val digest = MessageDigest.getInstance("SHA-256")
val hash = Base64.encodeToString(
digest.digest(certificate.publicKey.getEncoded()),
Base64.DEFAULT
).trim()
return hash == expectedHash
}
この関数はサーバーからX509Certificateオブジェクトと期待されるハッシュを受け取ります。最初に証明書の公開鍵を抽出し、SHA-256ハッシュを計算してBase64でエンコードします。結果は期待されるフィンガープリントと比較されます。本番環境では、ローテーションをサポートするために2〜3個のフィンガープリントの配列に対する検証を追加する必要があります。
ピンニングを実装する際、どの暗号オブジェクトを固定するかを選択する必要があります。Certificate PinningはX.509証明書自体 — そのシリアル番号、有効期間、チェーン全体 — にバインドします。Public Key Pinningは証明書内の公開鍵のみを固定し、他のフィールドを無視します。この選択は運用コストに大きく影響します。
| 基準 | Certificate Pinning | Public Key Pinning |
|---|---|---|
| 固定対象 | X.509証明書全体 | RSA/ECDSA公開鍵 |
| ローテーション | 再発行のたびに更新が必要 | 同じ鍵での証明書更新時に変更なし |
| セキュリティ | 最大精度のバインディング | 詳細にあまり影響されない |
| 柔軟性 | 低い — 証明書は1〜2年ごとに変更 | 高い — 鍵は5〜10年使用可能 |
| 推奨 | 更新が制御されたクリティカルシステム向け | ほとんどのモバイルアプリケーションとAPI向け |
Public Key Pinningはほとんどのプロジェクトで推奨される選択肢です。証明書の再発行時、サーバーの公開鍵は通常変更されません — 会社は古い鍵を新しい証明書で署名するだけです。つまり、鍵ペアが変更されていなければ、証明書変更後にアプリケーションの更新は不要です。一方、Certificate Pinningは、開発者がサーバーとクライアントコードの両方を完全に制御するシナリオ(厳格な更新サイクルを持つエンタープライズアプリケーションなど)で推奨されます。
TOFUは、Certificate Pinningを事前に設定せず、サーバーへの最初の接続時に証明書を記憶する戦略です。このアプローチは、事前にどのサーバーに接続するかわからないアプリケーションに便利です。欠点は最初の攻撃に対する脆弱性です。最初の接続がインターセプトされると、偽造証明書が信頼できるものとして受け入れられます。TOFUはSSH接続や一部のP2Pプロトコルで使用されています。
両方のプラットフォームで、Certificate PinningはネットワークスタックレベルでTLS接続をインターセプトして実装されます。iOSでは、URLSessionデリゲートまたはAlamofire ServerTrustManagerが使用されます。Androidでは、推奨される方法はOkHttp CertificatePinnerであり、これは人気のあるHTTPクライアントに組み込まれ、ドメインごとに複数のフィンガープリントを設定できます。
func validate(serverTrust: SecTrust,
pinnedHash: String) -> Bool {
guard let certificates = SecTrustCopyCertificateChain(serverTrust)
as? [SecCertificate] else { return false }
for certificate in certificates {
let data = SecCertificateCopyData(certificate)
var hash = Data(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
CC_SHA256($0.baseAddress,
CC_LONG(data.count), &hash)
}
if hash.base64EncodedString() == pinnedHash {
return true
}
}
return false
}
Swift関数では、serverTrustから証明書チェーンが抽出され、各証明書のSHA-256ハッシュが計算され、結果が期待値と比較されます。チェーン内のすべての証明書を反復処理することで、中間CAレベルでのピンニングが可能になります — 中間証明書が一致すれば接続が受け入れられます。これにより、リーフ証明書のローテーション時の柔軟性が提供されます。
アプリケーションがOkHttpを使用しない場合、カスタムX509TrustManagerを介してCertificate Pinningを実装できます。この方法はより多くのコードを必要としますが、検証プロセスを完全に制御できます。TrustManagerはcheckServerTrustedメソッドをオーバーライドし、開発者が手動でサーバー証明書を検証して信頼するかどうかを決定します。これはOkHttpライブラリが利用できない特定のシナリオでのみ推奨されます。
最も一般的な間違いはバックアップピンの欠如です。開発者が単一の証明書フィンガープリントのみを含め、それが期限切れになると、ユーザーは一斉に接続を失います。最小限の許容構成は2つのフィンガープリントです。現在の証明書とバックアップ用です。理想的には3つ:現在のもの、バックアップ、そしてフォールバックとしてルートCAのフィンガープリントです。
2つ目の間違いは、ピンをコードにプレーンテキストで保存することです。APKやIPAにアクセスできる攻撃者は、フィンガープリントを簡単に抽出して置き換えることができます。ハッシュの難読化が推奨されます。文字列を分割し、暗号化されたリソースに保存するか、実行時に計算します。Androidでは、文字列定数の難読化を備えたProGuardが効果的です。
3つ目の間違いは、開発用証明書レベルでのピンニングです。開発用と本番用の証明書は通常異なりますが、開発者はリリースビルド時にピンの切り替えを忘れることがよくあります。結果として、本番アプリケーションがサーバーに接続できなくなります。解決策は、BuildConfigまたはフレーバー固有のリソースを介して、デバッグとリリースで個別のピン設定を行うことです。
よくある質問
SSL PinningはSSL/TLS証明書にバインドする一般的な用語です。Certificate Pinningは、公開鍵だけでなくX.509証明書自体を固定する具体的な実装です。違いはバインド対象にあります:証明書 vs 鍵。
ProGuard(Android)による難読化を施したリソースへの保存、またはKeychain(iOS)による暗号化保存が推奨されます。暗号化なしでstrings.xmlやInfo.plistにプレーンテキストでピンを保存することは避けてください。
サーバー上の証明書が変更されるたびに。現在の証明書の有効期限の3〜6ヶ月前に新しいフィンガープリントをバックアップピンとして追加し、ローテーション後に古いものを削除することを推奨します。少なくとも1つのバックアップピンが必須です。
はい、条件付きコンパイルによって可能です。デバッグビルドではピンニングを無効にし、リリースビルドでは有効にします。切り替えにはAndroidのBuildConfig.DEBUGまたはiOSの#if DEBUGを使用してください。ユーザーがアクセス可能なランタイムフラグでこれを行わないでください。
直ちに新しいフィンガープリントを含むアプリケーションアップデートをリリースし、ストアに公開してください。強制更新メカニズムを使用します。バックアップピンに予備CAのフィンガープリントが含まれている場合、異なる証明書を持つ別のドメインに一時的に切り替えることができます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。