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

iOS使用SecureEnclave保护密钥对生成无效签名问题排查

iOS签名验证异常问题(0.01%用户受影响,iOS 17-18.1)

我们有0.01%的iOS用户(系统版本覆盖17至18.1)遇到签名异常:用户可正常生成密钥对,但使用对应公钥验证签名时,结果始终无效。


签名生成Swift代码

@objc public func signData(
    _ text: String,
    resolver resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
) -> Void {
    guard let privateKey = self.getPrivateKey() else {
        reject(nil, "error fetching private key", nil)
        return
    }

    let data =  text.data(using: .utf8)!

    var error: Unmanaged<CFError>?
    let signature = SecKeyCreateSignature(privateKey, .ecdsaSignatureMessageX962SHA256, data as CFData, &error) as Data?
    if error != nil {
        reject(nil, "Error in creating signature", nil)
        return
    }
    
    resolve(signature!.base64EncodedString())
}

密钥对生成Swift代码

@objc public func generateKeyPair(
    _ resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
) -> Void {
    let access = SecAccessControlCreateWithFlags(
        kCFAllocatorDefault,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        .privateKeyUsage,
        nil)!

    let attributes: NSDictionary = [
        kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
        kSecAttrKeySizeInBits: 256,
        kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, // Enable secure enclave
        kSecPrivateKeyAttrs: [
            kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, // Do not share across devices with same appleid
            kSecAttrIsPermanent: true,
            kSecAttrApplicationTag: privateKeyTag,
            kSecAttrAccessControl: access
        ],
        kSecPublicKeyAttrs: [
            kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, // Do not share across devices with same appleid
            kSecAttrIsPermanent: true,
            kSecAttrApplicationTag: publicKeyTag,
        ]
    ]
    
    var error: Unmanaged<CFError>?
    guard let privateKey = SecKeyCreateRandomKey(attributes, &error) else {
        reject(nil, "error creating private key", error!.takeRetainedValue() as Error)
        return
    }
    
    let publicKey = SecKeyCopyPublicKey(privateKey)
    if publicKey == nil {
        reject(nil, "error creating public key", nil)
        return
    }

    resolve(self.PEMFormattedPublicKey(key: publicKey!))
}

公钥PEM格式化代码

private func PEMFormattedPublicKey(key: SecKey) -> String? {
    guard let publicKeyData = self.dataForKey(key: key) else {
        return nil
    }
    
    // ASN.1 头部格式,适用于256位EC密钥
    let ecHeader: [UInt8] = [
        0x30, 0x59, // Sequence
        0x30, 0x13, // Algorithm identifier
        0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, // (ANSI X9.62 public key type)
        0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, // (ANSI X9.62 named elliptic curve)
        0x07, 0x03, 0x42, 0x00 // bit headers
    ]

    var asn1 = Data()
    asn1.append(Data(ecHeader))
    asn1.append(publicKeyData as Data)
    
    let encoded = asn1.base64EncodedString(options: .lineLength64Characters)
    let pemString = "-----BEGIN PUBLIC KEY-----\r\n\(encoded)\r\n-----END PUBLIC KEY-----\r\n"
    
    return pemString
}

后端验证Node.js代码

const publicKey = Buffer.from(publicKeyB64, 'base64').toString('utf-8');
const key = {
    key: publicKey,
};
const verifier = createVerify('SHA256');
verifier.update(challenge);

return verifier.verify(key, digest, 'base64');

异常示例数据

  • challenge: ac13288a-3ef7-4eb8-9198-5cb7f3a18665
  • signature: MEUCIEQMq6unQLES+vBMhalDG0ZxTX9TNWukb63Qqd7W4ReRAiEAyqTwMMQC/HkU4AbLmwbBTm6DBFDpA8yo+3Cigg0H0VA=
  • publicKeyB64: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXJENjNrSXdRaC83bVNia1R1QllUUEJtSGdOVmwNCnJueE4rNGVER2pHSDNRZmdVaGhKNlZYTjVSWTZhVW9iRjd3MjdFQXMwZ0JHaTdjOXdPdndQbmhxNEE9PQ0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:00:58