You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 8中如何获取单主机多证书的公钥哈希以实现证书固定?

iOS 8下获取单主机多证书公钥哈希的解决方案

针对你遇到的iOS 8与iOS 9+在多证书公钥获取上的差异问题,其实Apple有兼容iOS 8的API可以实现需求——核心是获取完整的服务器证书链,再逐个提取公钥计算哈希。下面是具体的实现思路和代码示例:

关键思路:遍历完整证书链

iOS 9+可能你能直接拿到所有证书,但iOS 8需要通过SecTrustCopyCertificateChain(iOS 3.0+就已支持)来获取服务器返回的完整证书链数组,之后遍历数组里的每个证书,单独提取公钥并计算哈希,再和你APP内存储的哈希字典对比,只要有一个匹配即可通过验证。

具体实现步骤(Objective-C)

  1. 获取完整证书链:从SecTrustRef中提取所有证书
  2. 遍历每个证书:逐个获取证书的公钥
  3. 计算公钥哈希:用你存储哈希时对应的算法(比如SHA-256)生成哈希值
  4. 对比验证:检查哈希是否在你的预存字典中
BOOL isValid = NO;
// 假设你已经有了serverTrust对象和预存的公钥哈希字典yourPublicKeyHashesDict

CFArrayRef certificateChain = SecTrustCopyCertificateChain(serverTrust);
if (certificateChain != NULL) {
    CFIndex certCount = CFArrayGetCount(certificateChain);
    
    for (CFIndex i = 0; i < certCount; i++) {
        // 取出链中的单个证书
        SecCertificateRef cert = CFArrayGetValueAtIndex(certificateChain, i);
        
        // 提取该证书的公钥
        SecKeyRef publicKey = SecCertificateCopyPublicKey(cert);
        if (publicKey != NULL) {
            // 将公钥转为外部表示数据
            CFDataRef publicKeyData = SecKeyCopyExternalRepresentation(publicKey, NULL);
            if (publicKeyData != NULL) {
                // 计算SHA-256哈希(和你存储哈希时的算法保持一致)
                uint8_t digest[CC_SHA256_DIGEST_LENGTH];
                CC_SHA256(CFDataGetBytePtr(publicKeyData), CFDataGetLength(publicKeyData), digest);
                
                // 转换为可对比的格式(这里用Base64字符串,和你存储的格式匹配即可)
                NSData *hashData = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
                NSString *hashString = [hashData base64EncodedStringWithOptions:0];
                
                // 检查是否在预存字典中
                if ([yourPublicKeyHashesDict objectForKey:hashString] != nil) {
                    isValid = YES;
                    // 找到匹配项,提前退出循环
                    goto cleanup;
                }
                
                CFRelease(publicKeyData);
            }
            CFRelease(publicKey);
        }
    }
    
cleanup:
    CFRelease(certificateChain);
}

// 最后根据isValid的值处理验证结果
if (isValid) {
    // 验证通过
} else {
    // 验证失败
}

替代方案:用SecTrustGetCertificateAtIndex遍历证书

除了SecTrustCopyCertificateChain,你也可以用SecTrustGetCertificateAtIndex逐个获取证书,直到返回NULL,效果是一样的:

BOOL isValid = NO;
CFIndex certCount = SecTrustGetCertificateCount(serverTrust);

for (CFIndex i = 0; i < certCount; i++) {
    SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i);
    // 后续提取公钥、计算哈希的逻辑和上面一致
}

注意事项

  • 内存管理:所有CF类型的对象(CFArrayRef、SecCertificateRef等)都需要手动调用CFRelease释放,避免内存泄漏。
  • 算法一致性:计算公钥哈希的算法必须和你预存哈希时使用的算法完全一致(比如SHA-1或SHA-256),否则对比会失败。
  • 证书链顺序:证书链通常是从服务器证书(索引0)到根证书,你可以根据需求决定是否需要遍历全部证书,或者只检查服务器证书层级的几个。

内容的提问来源于stack exchange,提问作者thoughtbreaker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:34:40