初识 iOS 开发中的证书固定

引言

在移动应用安全领域,HTTPS/TLS 是数据传输的第一道防线,但仅依赖系统默认的证书验证仍有被中间人(MITM)攻击的风险。Certificate Pinning(证书固定)通过将客户端信任“钉”在指定的服务器证书或公钥上,彻底杜绝了伪造证书带来的隐患,是面向金融、医疗、支付等高安全场景的必备防护手段。


为什么要做证书固定

  • 防范中间人攻击(MITM)
    攻击者可能在公共网络或被劫持的路由中插入自签或盗取的证书,绕过系统验证,让用户数据泄露或被篡改。
  • 抵御企业/校园 HTTPS 代理
    有些网络环境部署了 HTTPS 解密代理,虽然系统层面信任了其根证书,但应用层可通过固定真实服务器证书拒绝代理中转。
  • 满足合规审计
    金融、医疗等行业对通信安全有严格审计要求,证书固定可证明客户端仅信任预定义的证书或公钥。

核心原理与验证流程

  1. 预嵌入证书或公钥

    • 将服务器证书(.cer)或对应公钥哈希(Base64)打包进 App Bundle。
  2. 拦截 TLS 验证回调

    • URLSessionDelegatedidReceiveChallenge 中,先调用系统的 SecTrustEvaluate 完成基础验证,再进行自定义校验。
  3. 提取公钥并计算哈希

    • 从服务器返回的证书中提取公钥数据,对其执行 SHA-256 运算,生成 Base64 编码的哈希值。
  4. 哈希比对并决策

    • 将计算所得哈希与预置的“钉扎”值对比:

      • 匹配 → 继续通信(.useCredential(trust:)
      • 不匹配 → 拒绝连接(.cancelAuthenticationChallenge

实现示例

1. 原生 NSURLSession 公钥固定

class PinnedDelegate: NSObject, URLSessionDelegate {// 本地存储的公钥哈希(Base64 编码)private let pinnedHash = "Base64EncodedPublicKeyHashHere"func urlSession(_ session: URLSession,didReceive challenge: URLAuthenticationChallenge,completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {// 1. 检查是否为 ServerTrust 验证guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,let trust = challenge.protectionSpace.serverTrust else {completionHandler(.performDefaultHandling, nil)return}// 2. 系统默认验证var result = SecTrustResultType.invalidguard SecTrustEvaluate(trust, &result) == errSecSuccess,(result == .unspecified || result == .proceed) else {completionHandler(.cancelAuthenticationChallenge, nil)return}// 3. 提取服务器证书并获取公钥数据guard let cert = SecTrustGetCertificateAtIndex(trust, 0),let pubKey = SecCertificateCopyKey(cert),let pubData = SecKeyCopyExternalRepresentation(pubKey, nil) as Data? else {completionHandler(.cancelAuthenticationChallenge, nil)return}// 4. 计算 SHA-256 哈希let hash = sha256(pubData).base64EncodedString()// 5. 与预置哈希对比if hash == pinnedHash {completionHandler(.useCredential, URLCredential(trust: trust))} else {completionHandler(.cancelAuthenticationChallenge, nil)print("🚨 公钥哈希不匹配,证书固定校验失败")}}private func sha256(_ data: Data) -> Data {var buf = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))data.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &buf) }return Data(buf)}
}// 使用方式
let session = URLSession(configuration: .default,delegate: PinnedDelegate(),delegateQueue: nil)
session.dataTask(with: URL(string: "https://api.yourdomain.com/data")!).resume()

2. Alamofire 整证书固定

import Alamofire// 1. 从 Bundle 加载本地 .cer
let url = Bundle.main.url(forResource: "server", withExtension: "cer")!
let cert = SecCertificateCreateWithData(nil, try! Data(contentsOf: url) as CFData)!// 2. 配置 ServerTrustManager
let evaluators: [String: ServerTrustEvaluating] = ["api.yourdomain.com":PinnedCertificatesTrustEvaluator(certificates: [cert],acceptSelfSignedCertificates: false,performDefaultValidation: true,validateHost: true)
]let manager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: manager)// 3. 发起请求
session.request("https://api.yourdomain.com/data").validate().responseJSON { response inswitch response.result {case .success:print("✔️ 证书固定验证通过")case .failure(let error):print("❌ 验证失败:\(error)")}}

3. AFNetworking 公钥固定

#import "AFNetworking.h"// 1. 确保将 server.cer 放入 App Bundle
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer  = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];// 2. 配置公钥固定(Public Key Pinning)
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
// 不允许无效证书
policy.allowInvalidCertificates = NO;
// 必须校验域名
policy.validatesDomainName = YES;
manager.securityPolicy = policy;// 3. 发起 GET 请求
[manager GET:@"https://api.yourdomain.com/data"parameters:nilheaders:nilprogress:nilsuccess:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {NSLog(@"✔️ 请求成功并通过公钥固定校验:%@", responseObject);} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"❌ 请求失败或证书固定校验失败:%@", error);}];

实践建议

  • 优先使用公钥固定:对证书更新更具兼容性。
  • 结合远程配置动态更新:如 Firebase Remote Config,下发最新哈希,降低 App 发布频率。
  • 限定固定规则数量:仅对核心域名或关键 CA 进行固定,避免过度复杂。
  • 完整测试与监控:在预发布环境模拟证书换新,确保校验逻辑可用,并对失败情况设置告警。

结语

本文从“为什么要做证书固定”到“核心验证流程”,再到三种主流网络框架(原生 NSURLSession、Alamofire、AFNetworking)的实战示例,帮助初学者系统掌握 iOS 证书固定的落地方案。做好 Certificate Pinning,为你的应用网络通信再添一道牢不可破的安全防线。

扩展阅读

  • 苹果官方文档:Networking and Security → Secure Connections
  • OWASP Mobile Top 10 → M3: Insufficient Cryptography

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机的各个种类及其详细介绍

一、按架构分类的深度解析 1. ARM Cortex-M系列 核心优势: 统一架构:ARM生态完善,工具链(Keil、IAR、GCC)通用。 性能分层:M0(低功耗)、M3(平衡)、M4/M7&am…

5.7/Q1,GBD数据库最新文章解读

文章题目:Global, regional, and national burden and trends of rheumatoid arthritis among the elderly population: an analysis based on the 2021 Global Burden of Disease study DOI:10.3389/fimmu.2025.1547763 中文标题:全球、区域…

从微服务到AI服务:Nacos 3.0如何重构下一代动态治理体系?

在现代微服务架构的浪潮中,Nacos早已成为开发者手中的“瑞士军刀”。作为阿里巴巴开源的核心中间件,它通过动态服务发现、统一配置管理和服务治理能力,为云原生应用提供了坚实的基石。从初创公司到全球500强企业,Nacos凭借其开箱即…

Unity与Unreal Engine(UE)的深度解析及高级用法

以下是Unity与Unreal Engine(UE)的深度解析及高级用法对比,结合技术特性、行业应用与未来发展进行综合阐述: 一、核心差异与适用场景对比 1. 技术架构与编程模式 Unity 语言与脚本:主要使用C#,语法简洁且易于学习,适合快速原型开发和中小型项目。支持可视化脚本工具(如…

李沐动手深度学习(pycharm中运行笔记)——05.线性代数

05.线性代数(与课程对应) 1、导入torch import torch2、 标量由只有一个元素的张量表示 x torch.tensor([3.0]) y torch.tensor([2.0]) print("x y:", x y, "\nx * y:", x * y, "\nx / y:", x / y, "\nx ** y…

Python3与Dubbo3.1通讯解决方案(dubbo-python)

【文章非VIP可读,如果发现阅读限制为系统自动修改阅读权限,请留言我改回】 概述 最近AI项目需要java与python通讯,两边都是比较新的版本。因此需要双方进行通讯,在这里记录一下所采用的方案和关键点。 JAVA调用Python python通…

使用 DBeaver 将数据从 PostgreSQL 导出到 SQLite

使用 DBeaver 将数据从 PostgreSQL 导出到 SQLite,可按以下步骤进行: 1、连接到 PostgreSQL 数据库:打开 DBeaver,点击 “新建连接”,选择 “PostgreSQL”,输入数据库的地址、端口、用户名和密码等信息&am…

介词:连接名词与句子其他成分的桥梁

文章目录 1. with伴随1.表示“跟人或物”的伴随2.“行为”和“状态”的伴随2. of所属关系1. 人或物的所属关系2. 比较抽象的所属关系3. in1. 在......中,在......范围里2. 在某一段时间4. on1. 表示地点:在......上2. 表示时间:在某一天3. 关于某个主题5. at1. at + 具体时间…

FastApi快速实践

文章目录 一、主要功能:二、安装 FastAPI 和 Uvicorn(运行服务器)三、示例代码:四、运行服务器:1. 方式一:2. 方式二: 五、访问接口六、如果需要跨域(CORS)七、总结 下面…

深度学习中保存最优模型的实践与探索:以食物图像分类为例

深度学习中保存最优模型的实践与探索:以食物图像分类为例 在深度学习的模型训练过程中,训练一个性能良好的模型往往需要耗费大量的时间和计算资源。而保存最优模型不仅可以避免重复训练,还能方便后续使用和部署。本文将结合食物图像分类的代…

护理岗位技能比赛主持稿串词

男:尊敬的各位老师 女:亲爱的各位同学 合:大家下午好。 男:在这鸟语花香,诗意盎然的季节里 女:在这阳光灿烂,激情似火的日子里 合:我们欢聚一堂,共同庆祝五一二国际护士节…

【翻译、转载】MCP 核心架构

核心架构 了解 MCP 如何连接客户端、服务器和 LLM 模型上下文协议 (MCP) 构建在一个灵活、可扩展的架构之上,能够实现 LLM 应用程序与集成之间的无缝通信。本文档涵盖了核心的架构组件和概念。 概述 MCP 遵循客户端-服务器 (client-server) 架构,其中…

Python 数据智能实战 (11):LLM如何解决模型可解释性

写在前面 —— 不只知其然,更要知其所以然:借助 LLM,揭开复杂模型决策的神秘面纱 在前面的篇章中,我们学习了如何利用 LLM 赋能用户分群、购物篮分析、流失预测以及个性化内容生成。我们看到了 LLM 在理解数据、生成特征、提升模型效果和自动化内容方面的巨大潜力。 然而…

Linux:进程优先级及环境

一:孤儿进程 在Linux系统中,当一个进程创建了子进程后,如果父进程执行完毕或者提前退出而子进程还在运行,那么子进程就会成为孤儿进程。子进程就会被systemd(系统)进程收养,其pid为1 myproces…

Java大厂面试:Java技术栈中的核心知识点

Java技术栈中的核心知识点 第一轮提问:基础概念与原理 技术总监:郑薪苦,你对JVM内存模型了解多少?能简单说说吗?郑薪苦:嗯……我记得JVM有堆、栈、方法区这些区域,堆是存放对象的地方&#xf…

CF1000E We Need More Bosses

CF1000E We Need More Bosses 题目描述 题目大意: 给定一个 n n n 个点 m m m 条边的无向图,保证图连通。找到两个点 s , t s,t s,t,使得 s s s到 t t t必须经过的边最多(一条边无论走哪条路线都经过ta,这条边就是…

imx6uLL应用-v4l2

Linux V4L2 视频采集 JPEG 解码 LCD 显示实践 本文记录一个完整的嵌入式视频处理项目:使用 V4L2 接口从摄像头采集 MJPEG 图像,使用 libjpeg 解码为 RGB 格式,并通过 framebuffer 显示在 LCD 屏幕上。适用于使用 ARM Cortex-A 系列开发板进…

强化学习机器人模拟器——QAgent:一个支持多种强化学习算法的 Python 实现

QAgent 是一个灵活的 Python 类,专为实现经典的强化学习(Reinforcement Learning, RL)算法而设计,支持 Q-learning、SARSA 和 SARSA(λ) 三种算法。本篇博客将基于提供的 q_agent.py 代码,详细介绍 QAgent 类的功能、结构和使用方法,帮助您理解其在强化学习任务中的应用,…

Feign的原理

为什么 SpringCloud 中的Feign,可以帮助我们像使用本地接口一样调用远程 HTTP服务? Feign底层是如何实现的?这篇文章,我们一起来聊一聊。 1. Feign 的基本原理 Feign 的核心思想是通过接口和注解定义 HTTP 请求,将接…

探索正态分布:交互式实验带你体验统计之美

探索正态分布:交互式实验带你体验统计之美 正态分布,这条优美的钟形曲线,可以说是统计学中最重要、最无处不在的概率分布。从自然现象(如身高、测量误差)到金融市场,再到机器学习,它的身影随处…