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

iOS开发中Secure Enclave添加生物识别标志时密钥生成失败求助

问题:Secure Enclave添加生物识别标志生成密钥失败(错误码-25293)

我是iOS开发新手,近期开发一款需在Secure Enclave中创建密钥并签名数据的应用。遇到以下问题:

  • 当在SecAccessControlCreateWithFlags中添加.biometryAny或.biometryCurrentSet标志时,密钥生成失败,返回错误码-25293
  • 虽然触发了生物识别认证流程,但认证完成后仍抛出错误
  • 开发环境为Xcode iPhone15模拟器,已录入FaceID且动画正常
  • 移除生物识别标志后可正常生成密钥,但希望保留最高安全性
  • Info.plist已添加FaceID隐私权限(NSFaceIDUsageDescription)

可正常运行的代码

import UIKit
import Security
import LocalAuthentication
import CommonCrypto

func sha256(data: Data) -> Data {
    var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    data.withUnsafeBytes {
        _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
    }
    return Data(hash)
}

class ViewController: UIViewController {
    
    let inputTextField = UITextField()
    let encryptButton = UIButton()
    let outputLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    func setupUI() {
        // Input TextField setup
        inputTextField.placeholder = "Enter a random number"
        inputTextField.borderStyle = .roundedRect
        inputTextField.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(inputTextField)
        
        // Encrypt Button setup
        encryptButton.setTitle("Encrypt", for: .normal)
        encryptButton.backgroundColor = .blue
        encryptButton.translatesAutoresizingMaskIntoConstraints = false
        encryptButton.addTarget(self, action: #selector(encryptAction), for: .touchUpInside)
        view.addSubview(encryptButton)
        
        // Output Label setup
        outputLabel.text = "Encrypted output will appear here"
        outputLabel.numberOfLines = 0
        outputLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(outputLabel)
        
        NSLayoutConstraint.activate([
            inputTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            inputTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            inputTextField.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
            
            encryptButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            encryptButton.topAnchor.constraint(equalTo: inputTextField.bottomAnchor, constant: 20),
            
            outputLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            outputLabel.topAnchor.constraint(equalTo: encryptButton.bottomAnchor, constant: 20),
            outputLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8)
        ])
    }
    
    func authenticateUser(completion: @escaping (Bool, Error?) -> Void) {
        let context = LAContext()
        var error: NSError?
        
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            let reason = "Biometric authentication is needed to access your secure data."
            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                DispatchQueue.main.async {
                    completion(success, authenticationError)
                }
            }
        } else {
            // Biometry is not available or not enrolled.
            DispatchQueue.main.async {
                completion(false, error)
            }
        }
    }
    
    @objc func encryptAction() {
        authenticateUser { [weak self] (success, error) in
            guard success else {
                self?.outputLabel.text = "Authentication failed: \(error?.localizedDescription ?? \"Unknown error\")"
                return
            }
            
            guard let randomNumber = self?.inputTextField.text, !randomNumber.isEmpty,
                  let dataToSign = randomNumber.data(using: .utf8),
                  let privateKey = self?.generatePrivateKey() else {
                self?.outputLabel.text = "Error: Could not generate private key."
                return
            }
            
            if let signature = self?.signData(privateKey: privateKey, data: dataToSign) {
                self?.outputLabel.text = "Signature: \(signature.base64EncodedString())"
            } else {
                self?.outputLabel.text = "Error: Could not sign data."
            }
        }
    }
    
    func generatePrivateKey() -> SecKey? {
        // 1. Create Keys Access Control
        guard let accessControl =
            SecAccessControlCreateWithFlags(
                nil,
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                [.privateKeyUsage],
                nil)
        else {
            fatalError("cannot set access control")
        }
        
        // 2. Create Key Attributes
        guard let tag = "com.example.keys.mykey".data(using: .utf8) else {
            fatalError("cannot set tag")
        }
        let attributes: [String: Any] = [
             kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
             kSecAttrKeySizeInBits as String: 256,
             kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
             kSecPrivateKeyAttrs as String: [
                 kSecAttrIsPermanent as String: true,
                 kSecAttrApplicationTag as String: tag,
                 kSecAttrAccessControl as String: accessControl
             ]
         ]
        
        // 3. Generate Key Pairs
        var error: Unmanaged<CFError>?
        guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
            if let error = error?.takeRetainedValue() {
                        print("Error creating a key: \(error)")
                    }
            return nil
        }

        return privateKey
    }
    
    func signData(privateKey: SecKey, data: Data) -> Data? {
        let digest = sha256(data: data)

        var error: Unmanaged<CFError>?
        guard let signature = SecKeyCreateSignature(privateKey,
                                                    .ecdsaSignatureMessageX962SHA256,
                                                    digest as CFData,
                                                    &error) as Data? else {
            print(error!.takeRetainedValue() as Error)
            return nil
        }
        
        return signature
    }

}

报错的代码片段

guard let accessControl =
    SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        [.privateKeyUsage, .biometryCurrentSet],
        nil)
else {

触发错误的代码位置

var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
    if let error = error?.takeRetainedValue() {
                print("Error creating a key: \(error)")
            }
    return nil
}

错误信息

Error creating a key: Error Domain=NSOSStatusErrorDomain Code=-25293 "Key generation failed, error -25293" UserInfo={numberOfErrorsDeep=0, NSDescription=Key generation failed, error -25293}


解决方案

1. 核心原因:模拟器不支持带生物识别约束的Secure Enclave密钥

iPhone模拟器的FaceID仅为模拟交互,无法真正与Secure Enclave的生物验证机制绑定。带.biometryAny/.biometryCurrentSet标志的密钥生成只能在真实iOS设备上运行,这是错误码-25293的主要诱因。

2. 真实设备适配代码优化

在真实设备上实现该功能,需要将已通过生物识别的LAContext传入密钥生成方法,否则会触发权限校验失败:

修改密钥生成方法

func generatePrivateKey(with context: LAContext) -> SecKey? {
    // 1. Create Keys Access Control
    guard let accessControl =
        SecAccessControlCreateWithFlags(
            nil,
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
            [.privateKeyUsage, .biometryCurrentSet],
            nil)
    else {
        fatalError("cannot set access control")
    }
    
    // 2. Create Key Attributes
    guard let tag = "com.example.keys.mykey".data(using: .utf8) else {
        fatalError("cannot set tag")
    }
    let attributes: [String: Any] = [
         kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
         kSecAttrKeySizeInBits as String: 256,
         kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
         kSecPrivateKeyAttrs as String: [
             kSecAttrIsPermanent as String: true,
             kSecAttrApplicationTag as String: tag,
             kSecAttrAccessControl as String: accessControl
         ]
     ]
    
    // 3. 传入已认证的LAContext生成密钥
    var error: Unmanaged<CFError>?
    let parameters: [String: Any] = [kSecUseAuthenticationContext as String: context]
    guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, parameters as CFDictionary, &error) else {
        if let error = error?.takeRetainedValue() {
                    print("Error creating a key: \(error)")
                }
        return nil
    }

    return privateKey
}

修改认证与密钥调用逻辑

@objc func encryptAction() {
    let context = LAContext()
    authenticateUser(with: context) { [weak self] (success, error) in
        guard success else {
            self?.outputLabel.text = "Authentication failed: \(error?.localizedDescription ?? \"Unknown error\")"
            return
        }
        
        guard let randomNumber = self?.inputTextField.text, !randomNumber.isEmpty,
              let dataToSign = randomNumber.data(using: .utf8),
              let privateKey = self?.generatePrivateKey(with: context) else {
            self?.outputLabel.text = "Error: Could not generate private key."
            return
        }
        
        if let signature = self?.signData(privateKey: privateKey, data: dataToSign) {
            self?.outputLabel.text = "Signature: \(signature.base64EncodedString())"
        } else {
            self?.outputLabel.text = "Error: Could not sign data."
        }
    }
}

// 调整认证方法,传入LAContext
func authenticateUser(with context: LAContext, completion: @escaping (Bool, Error?) -> Void) {
    var error: NSError?
    
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        let reason = "Biometric authentication is needed to access your secure data."
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
            DispatchQueue.main.async {
                completion(success, authenticationError)
            }
        }
    } else {
        DispatchQueue.main.async {
            completion(false, error)
        }
    }
}

3. 模拟器调试临时方案

如果需要在模拟器上调试其他逻辑,可通过条件编译跳过生物识别约束:

let accessControlFlags: SecAccessControlCreateFlags
#if targetEnvironment(simulator)
accessControlFlags = [.privateKeyUsage]
#else
accessControlFlags = [.privateKeyUsage, .biometryCurrentSet]
#endif

guard let accessControl =
    SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        accessControlFlags,
        nil)
else {
    fatalError("cannot set access control")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:25:56