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

iOS 17中SecKeyCreateWithData始终返回nil问题求助

解决iOS 17中SecKeyCreateWithData返回nil的问题

问题根源

iOS 17对EC私钥的导入格式、属性配置的校验比iOS 16.4更严格,主要可能是以下原因:

  • 重复设置kSecAttrIsPermanent属性(顶层和kSecPrivateKeyAttrs中都配置)导致冲突
  • 私钥数据格式不符合iOS 17要求(比如非PKCS#8格式、包含额外字节)
  • 使用SecKeyCreateWithData创建永久存储密钥的方式在iOS 17中存在兼容性问题

解决方案

方案1:修正属性配置,移除重复设置

先移除顶层的kSecAttrIsPermanent,仅在kSecPrivateKeyAttrs中配置:

var attributes: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrKeySizeInBits as String: 256,
    kSecAttrAccessGroup as String: accessGroup,
    kSecPrivateKeyAttrs as String: [
        kSecAttrIsPermanent as String: true,
        kSecAttrAccessControl as String: access
    ]
]

var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(privateKey as CFData, attributes as CFDictionary, &error) else {
    print(error.debugDescription)
    throw EosioError(.keyManagementError, reason: error.debugDescription)
}

方案2:改用SecItemAdd直接导入密钥(推荐)

iOS 17中创建永久存储的密钥,更可靠的方式是通过SecItemAdd导入,再通过查询获取密钥引用:

// 1. 准备导入属性
let importAttributes: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrKeySizeInBits as String: 256,
    kSecAttrAccessGroup as String: accessGroup,
    kSecValueData as String: privateKey,
    kSecAttrIsPermanent as String: true,
    kSecAttrAccessControl as String: access
]

// 2. 导入到钥匙串
let importStatus = SecItemAdd(importAttributes as CFDictionary, nil)
guard importStatus == errSecSuccess else {
    let error = NSError(domain: NSOSStatusErrorDomain, code: Int(importStatus), userInfo: nil)
    throw EosioError(.keyManagementError, reason: error.debugDescription)
}

// 3. 查询获取SecKey引用
let query: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrAccessGroup as String: accessGroup,
    kSecReturnRef as String: true
]

var secKey: SecKey?
let queryStatus = SecItemCopyMatching(query as CFDictionary, &secKey)
guard queryStatus == errSecSuccess, let key = secKey else {
    throw EosioError(.keyManagementError, reason: "Failed to retrieve private key from keychain")
}

方案3:转换私钥为PKCS#8格式导入

如果私钥是原始32字节EC密钥(未经过PKCS#8包装),可以先转换为PKCS#8 DER格式再导入:

// 辅助方法:将原始32字节EC私钥转换为PKCS#8 DER格式
func convertRawECPrivateKeyToPKCS8(rawKey: Data) -> Data? {
    guard rawKey.count == 32 else { return nil }
    
    // ASN.1结构:PKCS#8 PrivateKeyInfo
    let version = Data([0x02, 0x01, 0x00]) // 版本0
    // AlgorithmIdentifier: ecPublicKey + secp256r1曲线
    let ecOID = Data([0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01])
    let secp256r1OID = Data([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A])
    let algorithmIdentifier = Data([0x30, 0x13]) + ecOID + Data([0x03, 0x0D, 0x00]) + secp256r1OID
    // 私钥内容(OCTET STRING包装)
    let privateKeyOctet = Data([0x04]) + rawKey
    // 组装完整序列
    let totalLength = version.count + algorithmIdentifier.count + privateKeyOctet.count
    guard totalLength <= 255 else { return nil } // 这里仅适配256位密钥
    let sequenceHeader = Data([0x30, UInt8(totalLength)])
    
    return sequenceHeader + version + algorithmIdentifier + privateKeyOctet
}

// 使用示例
// 1. 提取原始32字节私钥(需根据你的EOS私钥格式去除前缀、校验和等额外字节)
guard let rawPrivateKey = extractRaw32ByteKey(from: privateKey) else {
    throw EosioError(.keyManagementError, reason: "Invalid private key format")
}
// 2. 转换为PKCS#8
guard let pkcs8Data = convertRawECPrivateKeyToPKCS8(rawKey: rawPrivateKey) else {
    throw EosioError(.keyManagementError, reason: "Failed to convert key to PKCS#8 format")
}
// 3. 导入(使用方案1或方案2的代码)

额外排查点

  • 检查accessControl配置:如果使用了生物识别相关的控制策略,确保设备支持且权限配置正确,可临时改用kSecAccessControlDevicePasscode测试是否是权限问题
  • 验证私钥数据:确保解码后的私钥是32字节的纯EC密钥,无额外前缀、校验和字节(EOS私钥通常包含4字节校验和,需去除)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 21:44:53