iOS 证书校验

iOS HTTPS 防抓包原理与实现(Objective-C)

一、我们为什么要防抓包?

App 在跟服务器通信时,如果数据被第三方截获,就可能泄露密码、隐私、金融信息等。通过抓包工具(如 Charles、Fiddler、mitmproxy),攻击者可以在局域网内轻松窥探甚至修改你 App 的网络请求。所以我们需要保证 客户端只信任合法的服务器,拒绝任何中间人代理。

二、HTTPS 的基本保护机制

HTTPS = HTTP + TLS/SSL。它的核心保护:

  • 加密:传输内容被加密,窃听者看到的是乱码。

  • 认证:确保你连接的是真实的服务器,而不是假冒的。

  • 完整性:防止数据被篡改。

但为什么开了 HTTPS,抓包工具依然能抓到明文内容呢?

三、抓包工具是如何工作的(中间人攻击原理)

当你给手机安装并信任了 Charles 的根证书后,抓包的流程如下:

  1. 你的 App 向 https://api.example.com 发起请求。

  2. 请求被 Charles 代理截获,Charles 伪装成服务器,把自己的证书(由 Charles 根证书签发)返回给 App。

  3. App 因为系统信任了 Charles 的根证书,就认为这个"假证书"是合法的,然后用它加密通信。

  4. Charles 解密 App 发来的数据,再用真正的服务器证书去和 api.example.com 建立连接。

  5. 最后,Charles 把响应原路返回。至此,明文数据就被完整拿到了。

核心问题 :iOS 系统的默认证书验证,只检查 证书链是不是由设备信任的根证书签发,并不检查证书的"唯一身份"。你只要导入一个自制的根证书,就可以随意伪造任意域名的证书。

四、防抓包的核心思路:证书固定(SSL Pinning)

要防止中间人攻击,必须锁定真正的服务器证书 ,拒绝其他假证书。这就是 SSL Pinning

通俗解释:你第一次约会之前,对方给了你一张照片。系统默认的做法是:只要有警察(CA)担保说这个人就是他,你就信。但抓包工具相当于找了一个假扮者,警察却被你买通了(导入了假 CA)。SSL Pinning 相当于 你记住了对方脸上的痣、声音、指纹等唯一特征,即使有人担保,你也会仔细对比这些特征,不相符就立即识破。

SSL Pinning 有两种形式:

  • 证书固定(Certificate Pinning) :把服务器的 .cer 证书直接打包进 App,连接时与服务器返回的证书做 逐字节比对

  • 公钥固定(Public Key Pinning):比对接收到证书里的公钥,与预先存好的公钥是否一致。这种方式更灵活,因为证书更新时只要公钥不变,App 无需升级。

五、iOS 中的证书验证机制

iOS 的 TLS 验证通过 NSURLSessionNSURLConnection 的 **认证挑战(Authentication Challenge)**回调实现。当服务器返回证书后,系统会调用代理方法,让你有机会自定义验证逻辑。

关键方法(NSURLSessionDelegate):

objc

复制代码
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler;

在这个方法里,我们可以拿到 challenge.protectionSpace.serverTrust 对象,提取服务器证书链,再和内置的证书做对比。如果不匹配,则拒绝连接。

六、在 Objective-C 中实现 SSL Pinning

下面以两种常见场景为例:原生 NSURLSessionAFNetworking

1. 准备工作:将服务器证书放入 App

从服务器导出受信任的 .der.cer 格式证书文件,拖入 Xcode 工程,确保在 Copy Bundle Resources 中。

2. 原生 NSURLSession 实现(证书固定)

objc

复制代码
// 创建 NSURLSession 并设置代理
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config 
                                                      delegate:self 
                                                 delegateQueue:[NSOperationQueue mainQueue]];

// 实现代理方法
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
    
    // 只处理服务器信任认证
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
        // 系统默认的证书链验证已通过,但我们还要进行自定义固定
        // 从 bundle 中加载内置证书
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"your_server" ofType:@"cer"];
        NSData *certData = [NSData dataWithContentsOfFile:cerPath];
        SecCertificateRef pinnedCert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
        
        // 设置要匹配的证书数组(可以放多个)
        NSArray *pinnedCerts = @[(__bridge id)pinnedCert];
        
        // 告诉系统只信任这些证书
        SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);
        // 关键:只使用锚点证书验证,不信任系统根证书
        SecTrustSetAnchorCertificatesOnly(serverTrust, YES);
        
        // 异步评估信任
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            SecTrustResultType result;
            OSStatus status = SecTrustEvaluate(serverTrust, &result);
            if (status == errSecSuccess && (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed)) {
                // 信任验证成功
                NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
                completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            } else {
                // 验证失败,拒绝连接
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
            }
            if (pinnedCert) CFRelease(pinnedCert);
        });
    } else {
        // 其他认证方式使用默认处理
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}

如果是公钥固定,则需要从证书中提取公钥,然后和服务器的公钥比对,代码稍微复杂,但原理相同。

3. 使用 AFNetworking 的 AFSecurityPolicy

AFNetworking 已经内置了 SSL Pinning 功能,只需配置即可。

objc

复制代码
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; // 证书固定模式

// AFSSLPinningModePublicKey 为公钥固定模式
policy.validatesDomainName = YES; // 是否验证域名
policy.allowInvalidCertificates = NO; // 禁止无效证书
manager.securityPolicy = policy;

AFNetworking 会自动在内部处理 didReceiveChallenge,并将内置证书与服务器返回的证书链进行比对。你需要把 .cer 文件打包进 App,AFNetworking 会从 mainBundle 读取所有 .cer 文件作为内置证书。

七、加强防护:双向认证(客户端证书)

上面都是服务端认证,即 App 验证服务器。如果还想防止别人伪造客户端请求,可以启用双向认证:服务器要求客户端提供证书,确认是合法 App 在访问。

实现步骤:

  1. 生成客户端证书(通常为 .p12 格式),打包进 App。

  2. didReceiveChallenge 中,当收到服务器的 NSURLAuthenticationMethodClientCertificate 挑战时,从 bundle 读取 p12 文件并创建身份(SecIdentityRef),构建 NSURLCredential 返回。

objc

复制代码
// 加载 p12 文件并提取身份
NSString *p12Path = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:p12Path];
NSDictionary *options = @{(__bridge id)kSecImportExportPassphrase : @"your_password"};
CFArrayRef items = NULL;
OSStatus status = SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options, &items);
if (status == errSecSuccess) {
    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
    SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    // 创建证书数组
    NSArray *certs = (__bridge NSArray *)CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
    NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity
                                                             certificates:certs
                                                              persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}

八、可能遇到的坑与绕过方式

  1. 证书过期:固定证书后,如果服务器证书更新且没有使用相同公钥,App 将无法连接。最佳实践是同时固定多张证书(当前和下一张),或使用公钥固定,保证兼容。

  2. App 被反编译:硬编码的证书文件可以被提取替换,所以安全敏感的 App 还需配合代码混淆、运行时校验等。

  3. 利用 jailbreak 绕过 :攻击者可以通过修改系统 API(如 hook SecTrustEvaluate)让验证直接通过。因此 SSL Pinning 不是绝对安全,但仍大幅提升了攻击门槛。

  4. 使用 OCSP/CRL 吊销检查:有些银行 App 还会实时查询证书是否被吊销,防止已泄漏的合法证书被滥用。

九、总结

  • HTTPS 默认的证书验证依赖系统根证书存储,容易因导入自签名 CA 而被抓包。

  • SSL Pinning(证书固定)将信任锚定到特定的证书或公钥,可有效防御中间人攻击。

  • iOS 中通过 NSURLSession 的认证挑战回调,可灵活实现证书或公钥级别的验证。

  • 结合双向认证、代码保护等多种手段,能构建更坚固的安全体系。

相关推荐
yuanyxh9 小时前
macOS 应用 - 纯对话生成
前端·macos·ai编程
AI创界者2 天前
PilotTTS 一键整合包(Win/Mac):8G 显存畅跑,实测解锁情绪与副语言的精准控制
人工智能·macos·aigc·音视频
AirDroid_cn2 天前
系统终端与iTerm2字体看起来不一样?macOS Sequoia统一渲染指南
macos
初级代码游戏2 天前
easy Photo Clean公测版:快速清理iPhone照片 邀请公测
ios·iphone
库奇噜啦呼2 天前
【iOS】RunLoop学习
学习·ios
黑科技iOS上架2 天前
iOS应用周末提交什么情况算卡审
经验分享·ios
JiaWen技术圈2 天前
2026 年的 macOS 磁盘清理方法
macos
lichong9512 天前
让AI自己用电脑!Cua:后台操作鼠标键盘,Mac/Windows/Linux全支持
人工智能·macos·ai·计算机外设·agent·提示词
A尘埃3 天前
批处理命令(Linux/Mac、Windows项目启动脚本)
linux·windows·macos