如何提取或比较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
相关产品推荐
相关产品推荐

