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

如何提取或比较SecCertificate中的kSecPropertyKeyValue并处理CFData解析问题

解决钥匙串证书Data类型属性解析与展示问题

我之前在做类似Keychain Access的证书展示功能时,也碰到过这种部分属性返回CFData数组没法直接解析成字符串的情况。其实核心就是要针对不同类型的Data做针对性处理,结合系统Security框架的API来还原成可读信息,咱们一步步来实现:

第一步:先获取钥匙串里的证书

首先得把证书从钥匙串里取出来,这里补全你给出的查询代码,确保能拿到证书的引用:

import Foundation
import Security

func fetchCertificates() -> [SecCertificate]? {
    let query: [CFString: Any] = [
        kSecClass: kSecClassCertificate,
        kSecReturnRef: kCFBooleanTrue,
        kSecMatchLimit: kSecMatchLimitAll // 获取所有证书
    ]
    
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    
    guard status == errSecSuccess, let certificates = result as? [SecCertificate] else {
        print("获取证书失败: \(status)")
        return nil
    }
    return certificates
}

第二步:获取证书的完整属性

拿到证书引用后,用SecCertificateCopyValues获取所有属性,包括那些Data类型的字段:

func getCertificateAttributes(for certificate: SecCertificate) -> [CFString: Any]? {
    var attributes: CFDictionary?
    let status = SecCertificateCopyValues(certificate, nil, &attributes)
    
    guard status == errSecSuccess, let attrs = attributes as? [CFString: Any] else {
        print("获取证书属性失败: \(status)")
        return nil
    }
    return attrs
}

第三步:解析并展示属性(重点处理Data类型)

这一步是核心,我们要区分属性类型,对Data做不同的解析逻辑,尽量还原成Keychain Access里的可读格式:

func displayCertificateInfo(certificate: SecCertificate) {
    // 先展示系统提供的基础可读信息(不用自己解析复杂数据)
    if let subject = SecCertificateCopySubjectSummary(certificate) {
        print("主体: \(subject)")
    }
    if let issuer = SecCertificateCopyIssuerSummary(certificate) {
        print("颁发者: \(issuer)")
    }
    
    // 处理有效期
    var validFrom: CFDate?
    var validTo: CFDate?
    SecCertificateCopyValidity(certificate, &validFrom, &validTo)
    if let from = validFrom, let to = validTo {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .long
        dateFormatter.timeStyle = .long
        print("生效日期: \(dateFormatter.string(from: from as Date))")
        print("失效日期: \(dateFormatter.string(from: to as Date))")
    }
    
    // 遍历所有属性,处理Data类型
    guard let attributes = getCertificateAttributes(for: certificate) else { return }
    
    print("\n完整属性详情:")
    for (key, value) in attributes {
        let keyName = key as String
        switch value {
        case let data as Data:
            // 先尝试解析成PropertyList(部分钥匙串属性是plist编码的Data)
            if let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) {
                print("\(keyName): \(plist)")
            } else {
                // 解析失败就转成十六进制字符串(显示前50个字符避免太长)
                let hexStr = data.map { String(format: "%02x", $0) }.joined()
                print("\(keyName): [二进制数据] 十六进制前缀: \(hexStr.prefix(50))...")
                
                // 如果是证书本身的DER数据,转成PEM格式展示(和Keychain Access一致)
                if keyName == kSecPropertyKeyValue as String {
                    let pemContent = "-----BEGIN CERTIFICATE-----\n" + data.base64EncodedString(options: .lineLength64Characters) + "\n-----END CERTIFICATE-----"
                    print("\(keyName) (PEM格式):\n\(pemContent)")
                }
            }
        case let array as [Any]:
            // 处理CFData数组(比如kSecPropertyKeyValue可能是数组)
            print("\(keyName):")
            for (index, item) in array.enumerated() {
                if let itemData = item as? Data {
                    let hexStr = itemData.map { String(format: "%02x", $0) }.joined()
                    print("  元素\(index): [二进制数据] 十六进制前缀: \(hexStr.prefix(50))...")
                } else {
                    print("  元素\(index): \(item)")
                }
            }
        default:
            // 普通字符串/数字直接展示
            print("\(keyName): \(value)")
        }
    }
}

第四步:调用函数展示所有证书信息

最后把这些函数串起来,就能看到所有证书的详细信息了:

if let certificates = fetchCertificates() {
    for (index, cert) in certificates.enumerated() {
        print("\n=== 第\(index+1)个证书信息 ===")
        displayCertificateInfo(certificate: cert)
    }
}

进阶:解析复杂的ASN.1数据

如果需要解析证书扩展这类更复杂的ASN.1编码Data,可以用Security框架的SecAsn1Decode函数。比如解析证书的扩展密钥用法:

func parseCertificateExtendedKeyUsage(certificate: SecCertificate) {
    let extOID = kSecOIDX509V3ExtendedKeyUsage
    var extData: CFData?
    if SecCertificateCopyExtension(certificate, extOID, &extData, nil) {
        if let data = extData as Data? {
            var decodedOIDs: CFArray?
            // 用系统提供的ASN.1模板解析OID数组
            if SecAsn1Decode(data as CFData, SecAsn1TemplateArray(kSecAsn1OIDArrayTemplate), &decodedOIDs) == errSecSuccess {
                if let oids = decodedOIDs as? [CFString] {
                    print("\n扩展密钥用法: \(oids)")
                }
            }
        }
    }
}

这样处理下来,就能实现和Keychain Access类似的证书信息展示效果,不管是普通字符串属性还是Data类型的属性都能正确解析并展示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:17:23