Certificate Pinning — механизм фиксации сертификата или открытого ключа сервера, при котором приложение использует заранее известный отпечаток для проверки HTTPS-соединения. В отличие от стандартной цепочки доверия через CA, pinning гарантирует, что даже скомпрометированный центр сертификации не сможет выдать подменный сертификат для вашего домена. По данным OWASP MSTG (2025), Certificate Pinning входит в список обязательных controls для приложений уровня защиты L2. Реализация включает хранение хешей сертификатов в коде и проверку при каждом запросе.
Главное
Certificate Pinning — это техника безопасности, при которой приложение хранит отпечаток (fingerprint) доверенного сертификата и использует его как единственный критерий для установления HTTPS-соединения. В стандартной модели TLS клиент проверяет, что сертификат сервера подписан доверенным корневым CA — любым из сотен предустановленных в системе центров. Certificate Pinning заменяет эту цепочку на прямую проверку: сертификат должен совпадать с сохранённым образцом или содержать ожидаемый открытый ключ.
Проблема стандартной модели стала очевидной после инцидентов с компрометацией CA — DigiNotar (2011), Comodo (2011), TrustCor (2022). Если CA выпускает подменный сертификат для вашего домена, браузер или приложение принимает его как валидный. Certificate Pinning предотвращает эту атаку: даже идеально подписанный подменный сертификат будет отклонён, так как его отпечаток не совпадает с зафиксированным в приложении.
Термин pinning происходит от pin — «штырь» или «фиксатор»: разработчик фиксирует доверенный сертификат, и любое отклонение от него блокирует соединение. По данным исследования Mitre CWE-295, improper certificate validation остаётся одной из top-10 наиболее опасных ошибок безопасности в мобильных приложениях, и Certificate Pinning является прямым методом её предотвращения.
Изначально Certificate Pinning использовался в браузерах через механизм HPKP (HTTP Public Key Pinning), стандартизированный в RFC 7469. Разработчик отправлял HTTP-заголовок Public-Key-Pins с хешами ожидаемых ключей, и браузер запоминал их на указанный срок. Однако HPKP оказался опасным: одна ошибка в конфигурации могла заблокировать сайт на месяцы. В 2018 году Chrome прекратил поддержку HPKP, и сейчас стандартом стала программная реализация на стороне клиента — внутри мобильного приложения или браузерного расширения.
Процесс Certificate Pinning включает три ключевых этапа: вычисление отпечатка, проверка при соединении и обработка ошибки. На этапе подготовки разработчик получает SHA-256 хеш сертификата или открытого ключа продакшен-сервера. Для GDPR- и PCI DSS-совместимых приложений также требуется зафиксировать отпечатки промежуточных CA в цепочке.
При каждом HTTPS-запросе приложение перехватывает callback аутентификации 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 отпечатков для поддержки ротации.
При реализации pinning нужно выбрать, какой именно криптографический объект фиксировать. 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 хеш, и результат сравнивается с ожидаемым. Проход по всем сертификатам в цепочке позволяет реализовать pinning на уровне промежуточного CA — если промежуточный сертификат совпадает, соединение принимается. Это даёт гибкость при ротации leaf-сертификатов.
Если приложение не использует OkHttp, Certificate Pinning можно реализовать через кастомный X509TrustManager. Этот метод требует больше кода, но даёт полный контроль над процессом проверки. TrustManager переопределяет метод checkServerTrusted, где разработчик вручную проверяет сертификаты сервера и принимает решение о доверии. Рекомендуется только для специфических сценариев, где библиотека OkHttp недоступна.
Самая частая ошибка — отсутствие backup pins. Разработчик закладывает один отпечаток сертификата, и при его истечении пользователи массово теряют соединение. Минимально допустимая конфигурация — два отпечатка: текущий сертификат и резервный. Оптимально — три: текущий, резервный и отпечаток корневого CA как fallback.
Вторая ошибка — хранение pins в открытом виде в коде. Атакующий с доступом к APK или IPA может легко извлечь отпечатки и подменить их. Рекомендуется обфускация хешей: разбить строку на части, хранить в ресурсах с шифрованием или вычислять через runtime. Для Android эффективен ProGuard с обфускацией строковых констант.
Третья ошибка — пиннинг на уровне development-сертификата. Сертификаты разработки и продакшена обычно разные, но разработчики часто забывают переключить pins при сборке релиза. Результат — продакшен-приложение не может соединиться с сервером. Решение — раздельная конфигурация pins для debug и release через BuildConfig или flavour-специфичные ресурсы.
Часто задаваемые вопросы
SSL Pinning — общий термин для привязки к SSL/TLS сертификату. Certificate Pinning — конкретная реализация, фиксирующая сам сертификат X.509, а не только открытый ключ. Разница в объекте привязки: сертификат vs ключ.
Рекомендуется хранить хеши в ресурсах с обфускацией через ProGuard (Android) или зашифрованными через Keychain (iOS). Избегайте хранения pins в открытом виде в strings.xml или Info.plist без шифрования.
При каждой смене сертификата на сервере. Рекомендуется добавлять новый отпечаток как backup pin за 3–6 месяцев до истечения текущего, а после ротации удалять старый. Минимум один backup pin обязателен.
Да, через условную компиляцию: в debug-сборке pinning отключён, в release — включён. Используйте BuildConfig.DEBUG на Android или #if DEBUG на iOS для переключения. Никогда не делайте это через runtime-флаг, доступный пользователю.
Немедленно выпустить обновление приложения с новыми отпечатками и опубликовать его в сторах. Использовать механизм форсированного обновления. Если backup pins включали отпечаток резервного CA, временно можно переключиться на другой домен с другим сертификатом.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также