Secure Enclave, ECIES: Failed to aes-gcm decrypt data (err -69)

84 views Asked by At

The decryption failure encountered while using iOS Secure Enclave, causing decryption failures for some users in the production environment, impacting the majority of the phone and system functionalities.

The error message is:

decrypt: decrypted data fail: Optional(Error Domain=NSOSStatusErrorDomain Code=-50 "ECIES: Failed to aes-gcm decrypt data (err -69)" UserInfo={numberOfErrorsDeep=0, NSDescription=ECIES: Failed to aes-gcm decrypt data (err -69)}), DataLength: 113

My code:

static func getPrivateKey(keyName: String) -> (key: SecKey?, errorMsg: String?) {
    var key: SecKey?
    key = loadKey(name: keyName)
    if let key = key { return (key, nil) }
    let keyInfo = makeAndStoreKey(keyName: keyName)
    if let key = keyInfo.key { return (key, nil) }
    return (nil, keyInfo.errorMsg)
}

static func makeAndStoreKey(keyName: String, requiresBiometry: Bool = false) -> (key: SecKey?, errorMsg: String?) {
    removeKey(name: keyName)
    let flags: SecAccessControlCreateFlags
    if #available(iOS 11.3, *) {
        flags = requiresBiometry ?
        [.privateKeyUsage, .biometryCurrentSet] : .privateKeyUsage
    } else {
        flags = requiresBiometry ?
        [.privateKeyUsage, .touchIDCurrentSet] : .privateKeyUsage
    }
    let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                    kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
                                                    flags,
                                                    nil)
    guard let tag = keyName.data(using: .utf8), let access = access else {
        return (nil, Crypto.Err.accessOrTagFail.errorDescription)
    }
    let attributes: [CFString: Any] = [
        kSecAttrKeyType: kSecAttrKeyTypeEC,
        kSecAttrKeySizeInBits: 256,
        kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
        kSecPrivateKeyAttrs: [
            kSecAttrIsPermanent: true,
            kSecAttrApplicationTag: tag,
            kSecAttrAccessControl: access
        ]
    ]
    var error: Unmanaged<CFError>?
    guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
        return (nil, "SecKeyCreateRandomKeyFail: \((error?.takeRetainedValue() as? Error)?.localizedDescription ?? "")")
    }
    return (privateKey, nil)
}

static func loadKey(name: String) -> SecKey? {
    guard let tag = name.data(using: .utf8) else { return nil }
    let query: [CFString: Any] = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: tag,
        kSecAttrKeyType: kSecAttrKeyTypeEC,
        kSecReturnRef: true
    ]
    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)
    guard status == errSecSuccess, let item = item else { return nil }
    return item as! SecKey
}

static func removeKey(name: String) {
    guard let tag = name.data(using: .utf8) else { return }
    let query: [CFString: Any] = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: tag
    ]
    SecItemDelete(query as CFDictionary)
}

static func encrypt(data: Data, keyName: String) -> Data? {
    var tmpData: Data?
    var errMsg: String?
    defer {
        if tmpData == nil {
            self.updateDeviceDecryptFail()
            self.errorCallBack?(.masterKey(state: .deviceEncryptErr(errMsg: errMsg)))
        }
    }
    guard #available(iOS 11.0, *) else { return nil }
    let keyInfo = getPrivateKey(keyName: keyName)
    guard let key = keyInfo.key else {
        errMsg = "encrypt: \(Crypto.Err.getPrivateKeyFail.errorDescription ?? "") \(keyInfo.errorMsg ?? "")"
        return nil
    }
    guard let publicKey = SecKeyCopyPublicKey(key) else {
        errMsg = "encrypt: \(Crypto.Err.getPublicKeyFail.errorDescription ?? "")"
        return nil
    }
    let algorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
    guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, algorithm) else {
        errMsg = "encrypt: \(Crypto.Err.isAlgorithmSupportedFail.errorDescription ?? "")"
        return nil
    }
    var error: Unmanaged<CFError>?
    let cipherData = SecKeyCreateEncryptedData(publicKey,
                                                algorithm,
                                                data as CFData,
                                                &error) as Data?
    guard let cipherData = cipherData else {
        errMsg = "encrypt: \(Crypto.Err.encryptedDataFail(error: (error?.takeRetainedValue() as? Error)).errorDescription ?? "")"
        return nil
    }
    tmpData = cipherData
    return cipherData
}

static func decrypt(encryptedData: Data, keyName: String) -> Data? {
    var tmpData: Data?
    var errMsg: String?
    defer { if tmpData == nil { self.errorCallBack?(.masterKey(state: .deviceDecryptErr(errMsg: errMsg))) } }
    guard #available(iOS 11.0, *) else { return nil }
    let keyInfo = getPrivateKey(keyName: keyName)
    guard let key = keyInfo.key else {
        errMsg = "decrypt: \(Crypto.Err.getPrivateKeyFail.errorDescription ?? "") \(keyInfo.errorMsg ?? "")"
        return nil
    }
    let algorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
    guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm) else {
        errMsg = "decrypt: \(Crypto.Err.isAlgorithmSupportedFail.errorDescription ?? "")"
        return nil
    }
    var error: Unmanaged<CFError>?
    let data = SecKeyCreateDecryptedData(key,
                                            algorithm,
                                            encryptedData as CFData,
                                            &error) as Data?
    guard let data = data else {
        errMsg = "decrypt: \(Crypto.Err.decryptedDataFail(error: (error?.takeRetainedValue() as? Error)).errorDescription ?? ""), DataLength: \(encryptedData.count)"
        return nil
    }
    tmpData = data
    return data
}

I am searching for a long time on net, but no use. Please help or try to give some ideas how to achieve this.

0

There are 0 answers