【问题标题】:Save and retrieve value via KeyChain通过 KeyChain 保存和检索值
【发布时间】:2015-08-23 12:56:02
【问题描述】:

我正在尝试存储一个整数并使用 KeyChain 检索它。

我是这样保存的:

func SaveNumberOfImagesTaken()
    {
        let key = "IMAGE_TAKEN"
        var taken = 10
        let data = NSKeyedArchiver.archivedDataWithRootObject(taken)
        let query : [String:AnyObject] = [
            kSecClass as String : kSecClassGenericPassword,
            kSecAttrAccount as String : key,
            kSecValueData as String : data
        ]
        let status : OSStatus = SecItemAdd(query as CFDictionaryRef, nil)

    }

这是我尝试检索它的方式:

func CheckIfKeyChainValueExitss() -> AnyObject? {
    var key = "IMAGE_TAKEN"
    let query : [String:AnyObject] = [
        kSecClass as String       : kSecClassGenericPassword,
        kSecAttrAccount as String : key,
        kSecReturnData as String  : kCFBooleanTrue,
        kSecMatchLimit as String  : kSecMatchLimitOne ]

    var dataTypeRef :Unmanaged<AnyObject>?

    let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)

    if let op = dataTypeRef?.toOpaque() {
        let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
        if let string: AnyObject? =  NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
            if key == "IMAGE_TAKEN"
            {
                return string as! String!

            }
            else if string == nil
            {
                return nil
            }
        }
    }
    return nil

}

我收到以下错误:

无法将“__NSCFNumber”类型的值转换为“NSString”

我尝试使用变量但没有成功。

【问题讨论】:

  • 那么我是保存错误还是检索错误?我很困惑哈哈@Paulw11
  • 这个插入式钥匙串包装器可能会给你一些想法……github.com/ashleymills/Keychain.swift
  • @AshleyMills 在下面看到我的回答

标签: ios swift keychain nskeyedarchiver


【解决方案1】:

我已经更新了 Eric 的 Swift 5 版本:

class KeyChain {

    class func save(key: String, data: Data) -> OSStatus {
        let query = [
            kSecClass as String       : kSecClassGenericPassword as String,
            kSecAttrAccount as String : key,
            kSecValueData as String   : data ] as [String : Any]

        SecItemDelete(query as CFDictionary)

        return SecItemAdd(query as CFDictionary, nil)
    }

    class func load(key: String) -> Data? {
        let query = [
            kSecClass as String       : kSecClassGenericPassword,
            kSecAttrAccount as String : key,
            kSecReturnData as String  : kCFBooleanTrue!,
            kSecMatchLimit as String  : kSecMatchLimitOne ] as [String : Any]

        var dataTypeRef: AnyObject? = nil

        let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

        if status == noErr {
            return dataTypeRef as! Data?
        } else {
            return nil
        }
    }

    class func createUniqueID() -> String {
        let uuid: CFUUID = CFUUIDCreate(nil)
        let cfStr: CFString = CFUUIDCreateString(nil, uuid)

        let swiftString: String = cfStr as String
        return swiftString
    }
}

extension Data {

    init<T>(from value: T) {
        var value = value
        self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.load(as: T.self) }
    }
}

我已经更新了 Eric 的 Swift 3 版本:

class KeyChain {

    class func save(key: String, data: Data) -> OSStatus {
        let query = [
            kSecClass as String       : kSecClassGenericPassword as String,
            kSecAttrAccount as String : key,
            kSecValueData as String   : data ] as [String : Any]

        SecItemDelete(query as CFDictionary)

        return SecItemAdd(query as CFDictionary, nil)
    }

    class func load(key: String) -> Data? {
        let query = [
            kSecClass as String       : kSecClassGenericPassword,
            kSecAttrAccount as String : key,
            kSecReturnData as String  : kCFBooleanTrue,
            kSecMatchLimit as String  : kSecMatchLimitOne ] as [String : Any]

        var dataTypeRef: AnyObject? = nil

        let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

        if status == noErr {
            return dataTypeRef as! Data?
        } else {
            return nil
        }
    }

    class func createUniqueID() -> String {
        let uuid: CFUUID = CFUUIDCreate(nil)
        let cfStr: CFString = CFUUIDCreateString(nil, uuid)

        let swiftString: String = cfStr as String
        return swiftString
    }
}

extension Data {

    init<T>(from value: T) {
        var value = value
        self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.pointee }
    }
}

示例用法:

let int: Int = 555
let data = Data(from: int)
let status = KeyChain.save(key: "MyNumber", data: data)
print("status: ", status)

if let receivedData = KeyChain.load(key: "MyNumber") {
    let result = receivedData.to(type: Int.self)
    print("result: ", result)
}

【讨论】:

  • 使用它来存储/检索字符串值时出错。保存字符串没有错误,但接收它,receivedData 不是 nil(至少,它的 .count 为 24),但 receivedData.to(type: String.self) is以零返回。有什么想法吗?
  • @ConfusionTowers 我无法重现该错误。你能发布你的代码吗?
  • 不推荐使用警告“withUnsafeBytes”:在 Swift 5 中使用withUnsafeBytes&lt;R&gt;(_: (UnsafeRawBufferPointer) throws -&gt; R) rethrows -&gt; R 代替“return self.withUnsafeBytes { $0.pointee }”行。我该如何解决?谢谢
  • 这在您先保存数据然后加载时有效,但如果您在保存之前先加载,(例如)您正在检查钥匙串中的值并且第一次没有时间,它会崩溃。
  • 这一行的崩溃返回 self.withUnsafeBytes { $0.pointee }
【解决方案2】:

好吧,我只是用掉了源等,并让自己成为了一个好帮手: 享受吧!

 class func save(key: String, data: NSData) {
        let query = [
            kSecClass as String       : kSecClassGenericPassword as String,
            kSecAttrAccount as String : key,
            kSecValueData as String   : data ]

        SecItemDelete(query as CFDictionaryRef)

        let status: OSStatus = SecItemAdd(query as CFDictionaryRef, nil)

    }

    class func load(key: String) -> NSData? {
        let query = [
            kSecClass as String       : kSecClassGenericPassword,
            kSecAttrAccount as String : key,
            kSecReturnData as String  : kCFBooleanTrue,
            kSecMatchLimit as String  : kSecMatchLimitOne ]

        var dataTypeRef :Unmanaged<AnyObject>?

        let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)

        if status == noErr {
            return (dataTypeRef!.takeRetainedValue() as! NSData)
        } else {
            return nil
        }


    }

    class func stringToNSDATA(string : String)->NSData
    {
        let _Data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
        return _Data!

    }


    class func NSDATAtoString(data: NSData)->String
    {
        var returned_string : String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
        return returned_string
    }

    class func intToNSDATA(r_Integer : Int)->NSData
    {

            var SavedInt: Int = r_Integer
            let _Data = NSData(bytes: &SavedInt, length: sizeof(Int))
        return _Data

    }
    class func NSDATAtoInteger(_Data : NSData) -> Int
    {
            var RecievedValue : Int = 0
            _Data.getBytes(&RecievedValue, length: sizeof(Int))
            return RecievedValue

    }
    class func CreateUniqueID() -> String
    {
        var uuid: CFUUIDRef = CFUUIDCreate(nil)
        var cfStr:CFString = CFUUIDCreateString(nil, uuid)

        var nsTypeString = cfStr as NSString
        var swiftString:String = nsTypeString as String
        return swiftString
    }

    //EXAMPLES
//    
//    //Save And Parse Int


//    var Int_Data = KeyChain.intToNSDATA(555)
//    KeyChain.save("MAMA", data: Int_Data)
//    var RecievedDataAfterSave = KeyChain.load("MAMA")
//    var NSDataTooInt = KeyChain.NSDATAtoInteger(RecievedDataAfterSave!)
//    println(NSDataTooInt)
//    
//    
//    //Save And Parse String


//    var string_Data = KeyChain.stringToNSDATA("MANIAK")
//    KeyChain.save("ZAHAL", data: string_Data)
//    var RecievedDataStringAfterSave = KeyChain.load("ZAHAL")
//    var NSDATAtoString = KeyChain.NSDATAtoString(RecievedDataStringAfterSave!)
//    println(NSDATAtoString)

【讨论】:

    【解决方案3】:

    这是为 iOS 重写的 Sazzad Hissain Khan's answer,没有非 Swifty NS 前缀属性和更简洁的代码。

    import Security
    
    class KeychainService {
        class func updatePassword(service: String, account: String, data: String) {
            guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
                return
            }
    
            let status = SecItemUpdate(modifierQuery(service: service, account: account), [kSecValueData: dataFromString] as CFDictionary)
    
            checkError(status)
        }
    
        class func removePassword(service: String, account: String) {
            let status = SecItemDelete(modifierQuery(service: service, account: account))
    
            checkError(status)
        }
    
        class func savePassword(service: String, account: String, data: String) {
            guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
                return
            }
    
            let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                                  kSecAttrService: service,
                                                  kSecAttrAccount: account,
                                                  kSecValueData: dataFromString]
    
            let status = SecItemAdd(keychainQuery as CFDictionary, nil)
    
            checkError(status)
        }
    
        class func loadPassword(service: String, account: String) -> String? {
            var dataTypeRef: CFTypeRef?
    
            let status = SecItemCopyMatching(modifierQuery(service: service, account: account), &dataTypeRef)
    
            if status == errSecSuccess,
                let retrievedData = dataTypeRef as? Data {
                return String(data: retrievedData, encoding: .utf8)
            } else {
                checkError(status)
    
                return nil
            }
        }
    
        fileprivate static func modifierQuery(service: String, account: String) -> CFDictionary {
            let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                                  kSecAttrService: service,
                                                  kSecAttrAccount: account,
                                                  kSecReturnData: kCFBooleanTrue]
    
            return keychainQuery as CFDictionary
        }
    
        fileprivate static func checkError(_ status: OSStatus) {
            if status != errSecSuccess {
                if #available(iOS 11.3, *),
                let err = SecCopyErrorMessageString(status, nil) {
                    print("Operation failed: \(err)")
                } else {
                    print("Operation failed: \(status). Check the error message through https://osstatus.com.")
                }
            }
        }
    }
    

    【讨论】:

    • KeyChain 中存储的数据是否有可能受到影响?我的意思是操作系统更新等?
    【解决方案4】:

    Roi Mulia 的 answer 运行良好,这是一个对 Swift 2 进行了一些小幅调整的版本:

    class KeyChain {
        class func save(key: String, data: NSData) -> OSStatus {
            let query = [
                kSecClass as String       : kSecClassGenericPassword as String,
                kSecAttrAccount as String : key,
                kSecValueData as String   : data ]
    
            SecItemDelete(query as CFDictionaryRef)
    
            return SecItemAdd(query as CFDictionaryRef, nil)
    
        }
    
        class func load(key: String) -> NSData? {
            let query = [
                kSecClass as String       : kSecClassGenericPassword,
                kSecAttrAccount as String : key,
                kSecReturnData as String  : kCFBooleanTrue,
                kSecMatchLimit as String  : kSecMatchLimitOne ]
    
            var dataTypeRef:AnyObject? = nil
    
            let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)
    
            if status == noErr {
                return (dataTypeRef! as! NSData)
            } else {
                return nil
            }
    
    
        }
    
        class func stringToNSDATA(string : String)->NSData
        {
            let _Data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
            return _Data!
    
        }
    
    
        class func NSDATAtoString(data: NSData)->String
        {
            let returned_string : String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
            return returned_string
        }
    
        class func intToNSDATA(r_Integer : Int)->NSData
        {
    
            var SavedInt: Int = r_Integer
            let _Data = NSData(bytes: &SavedInt, length: sizeof(Int))
            return _Data
    
        }
        class func NSDATAtoInteger(_Data : NSData) -> Int
        {
            var RecievedValue : Int = 0
            _Data.getBytes(&RecievedValue, length: sizeof(Int))
            return RecievedValue
    
        }
        class func CreateUniqueID() -> String
        {
            let uuid: CFUUIDRef = CFUUIDCreate(nil)
            let cfStr:CFString = CFUUIDCreateString(nil, uuid)
    
            let nsTypeString = cfStr as NSString
            let swiftString:String = nsTypeString as String
            return swiftString
        }
    }
    

    示例用法:

    let data = KeyChain.intToNSDATA(555)
    let status = KeyChain.save("MyNumber", data: data)
    print(status)
    
    if let receivedData = KeyChain.load("MyNumber") {
        let result = KeyChain.NSDATAtoInteger(receivedData)
        print(result)
    }
    

    【讨论】:

    • KeyChain 中存储的数据是否有可能受到影响?我的意思是操作系统更新等?
    【解决方案5】:

    我试图让它尽可能简单。

    fileprivate class KeychainService {
    
      static func updatePassword(_ password: String, serviceKey: String) {
        guard let dataFromString = password.data(using: .utf8) else { return }
    
        let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
                                               kSecAttrService: serviceKey,
                                               kSecValueData: dataFromString]
        SecItemDelete(keychainQuery as CFDictionary)
        SecItemAdd(keychainQuery as CFDictionary, nil)
      }
    
      static func removePassword(serviceKey: String) {
    
        let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
                                               kSecAttrService: serviceKey]
    
        SecItemDelete(keychainQuery as CFDictionary)
      }
    
      static func loadPassword(serviceKey: String) -> String? {
        let keychainQuery: [CFString : Any] = [kSecClass : kSecClassGenericPassword,
                                               kSecAttrService : serviceKey,
                                               kSecReturnData: kCFBooleanTrue,
                                               kSecMatchLimitOne: kSecMatchLimitOne]
    
        var dataTypeRef: AnyObject?
        SecItemCopyMatching(keychainQuery as CFDictionary, &dataTypeRef)
        guard let retrievedData = dataTypeRef as? Data else { return nil }
    
        return String(data: retrievedData, encoding: .utf8)
      }
    
      static func flush()  {
        let secItemClasses =  [kSecClassGenericPassword]
        for itemClass in secItemClasses {
          let spec: NSDictionary = [kSecClass: itemClass]
          SecItemDelete(spec)
        }
      }
    }
    

    【讨论】:

    • 这个怎么用?
    • @YogeshPatel KeychainService.updatePassword("password", serviceKey: "key");让密码 = KeychainService.loadPassword("key");
    【解决方案6】:

    示例如何保存和检索结构 User,这是一个非常常见的用例:

    import Security
    import UIKit
    
    class KeyChain {
        struct User {
            let identifier: Int64
            let password: String
        }
    
        private static let service = "MyService"
    
        static func save(user: User) -> Bool {
            let identifier = Data(from: user.identifier)
            let password = user.password.data(using: .utf8)!
            let query = [kSecClass as String : kSecClassGenericPassword as String,
                         kSecAttrService as String : service,
                         kSecAttrAccount as String : identifier,
                         kSecValueData as String : password]
                as [String : Any]
    
            let deleteStatus = SecItemDelete(query as CFDictionary)
    
            if deleteStatus == noErr || deleteStatus == errSecItemNotFound {
                return SecItemAdd(query as CFDictionary, nil) == noErr
            }
    
            return false
        }
    
        static func retrieveUser() -> User? {
            let query = [kSecClass as String : kSecClassGenericPassword,
                         kSecAttrService as String : service,
                         kSecReturnAttributes as String : kCFBooleanTrue!,
                         kSecReturnData as String: kCFBooleanTrue!]
                as [String : Any]
    
            var result: AnyObject? = nil
            let status = SecItemCopyMatching(query as CFDictionary, &result)
    
            if status == noErr,
                let dict = result as? [String: Any],
                let passwordData = dict[String(kSecValueData)] as? Data,
                let password = String(data: passwordData, encoding: .utf8),
                let identifier = (dict[String(kSecAttrAccount)] as? Data)?.to(type: Int64.self) {
    
                return User(identifier: identifier, password: password)
            } else {
                return nil
            }
        }
    }
    
    private extension Data {
        init<T>(from value: T) {
            var value = value
    
            self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
        }
    
        func to<T>(type: T.Type) -> T {
            withUnsafeBytes { $0.load(as: T.self) }
        }
    }
    

    【讨论】:

    • 我没有看到解决方案中使用了任何数据扩展方法。他们是必需的吗?如果是这样,具体是为了什么?
    【解决方案7】:

    你存储的是一个数字,而不是一个字符串,所以你得到的是一个 NSNumber,而不是一个字符串。例外很明显 - 你不能将 NSNumber 向下转换为字符串 - 你可以使用 stringValue() 来获取 NSNumber 的字符串表示

    if let op = dataTypeRef?.toOpaque() {
        let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
        if let string: AnyObject? =  NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
            if key == "IMAGE_TAKEN"
            {
                return string.stringValue() as! String!
            }
            else if string == nil
            {
                    return nil
            }
        }
    }
    

    【讨论】:

    • 那么为什么最后你添加了“作为字符串”而不是 NSNumber?
    • 因为我假设你想要一个字符串。它已经是一个 NSNumber。返回一个 int 可能更有意义,在这种情况下您将使用 intValue() - 但您需要更改您的函数签名。理论上你可以省略 as! String! 因为 stringValue() 总是返回一个字符串
    • 没有代码在我面前,您可能需要先将string 向下转换为NSNumber,以阻止编译器抱怨它不知道stringValue for AnyObject?
    • 最终我想要实现的是存储整数。当我需要时检索它,将金额添加到价值并再次存储它。我是否像上面发布的那样正确保存/存储它?问题出在检索的方法上还是两者兼而有之?这个钥匙扣真的让我很困惑哈哈,对不起。谢谢!
    • 有什么理由要将此数据存储在密钥链中吗? NSUserDefaults 对于一个简单的整数来说要简单得多。钥匙串最适合需要保护的密码之类的东西。还有很多包装库可以让钥匙串更容易使用。
    猜你喜欢
    • 2012-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-18
    相关资源
    最近更新 更多