【问题标题】:Save and Load from KeyChain | Swift [duplicate]从 KeyChain 保存和加载 |斯威夫特 [重复]
【发布时间】:2016-09-29 03:06:33
【问题描述】:

如何简单地将字符串存储在钥匙串中并在需要时加载。 有几个 SO 解决方案主要是指 Git repo。但我需要最新的 Swift 上最小和最简单的解决方案。当然,我不想添加 git 框架来简单地在我的项目中存储密码。

有类似的解决方案 Save and retrieve value via KeyChain ,这对我不起作用。厌倦了编译器错误。

【问题讨论】:

    标签: ios swift security keychain


    【解决方案1】:

    ##最简单的来源##

    import Foundation
    import Security
    
    // Constant Identifiers
    let userAccount = "AuthenticatedUser"
    let accessGroup = "SecuritySerivice"
    
    
    /** 
     *  User defined keys for new entry
     *  Note: add new keys for new secure item and use them in load and save methods
     */
    
    let passwordKey = "KeyForPassword"
    
    // Arguments for the keychain queries
    let kSecClassValue = NSString(format: kSecClass)
    let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
    let kSecValueDataValue = NSString(format: kSecValueData)
    let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
    let kSecAttrServiceValue = NSString(format: kSecAttrService)
    let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
    let kSecReturnDataValue = NSString(format: kSecReturnData)
    let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
    
    public class KeychainService: NSObject {
    
        /**
         * Exposed methods to perform save and load queries.
         */
    
        public class func savePassword(token: NSString) {
            self.save(passwordKey, data: token)
        }
    
        public class func loadPassword() -> NSString? {
            return self.load(passwordKey)
        }
        
        /**
         * Internal methods for querying the keychain.
         */
    
        private class func save(service: NSString, data: NSString) {
            let dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    
            // Instantiate a new default keychain query
            let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
    
            // Delete any existing items
            SecItemDelete(keychainQuery as CFDictionaryRef)
    
            // Add the new keychain item
            SecItemAdd(keychainQuery as CFDictionaryRef, nil)
        }
    
        private class func load(service: NSString) -> NSString? {
            // Instantiate a new default keychain query
            // Tell the query to return a result
            // Limit our results to one item
            let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
    
            var dataTypeRef :AnyObject?
    
            // Search for the keychain items
            let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
            var contentsOfKeychain: NSString? = nil
    
            if status == errSecSuccess {
                if let retrievedData = dataTypeRef as? NSData {
                    contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
                }
            } else {
                print("Nothing was retrieved from the keychain. Status code \(status)")
            }
    
            return contentsOfKeychain
        }
    }
    

    ##调用示例##

    KeychainService.savePassword("Pa55worD")
    let password = KeychainService.loadPassword() // password = "Pa55worD"
    

    ##SWIFT 4:具有更新和删除密码的版本

    import Cocoa
    import Security
    
    // see https://stackoverflow.com/a/37539998/1694526
    // Arguments for the keychain queries
    let kSecClassValue = NSString(format: kSecClass)
    let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
    let kSecValueDataValue = NSString(format: kSecValueData)
    let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
    let kSecAttrServiceValue = NSString(format: kSecAttrService)
    let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
    let kSecReturnDataValue = NSString(format: kSecReturnData)
    let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
    
    public class KeychainService: NSObject {
        
        class func updatePassword(service: String, account:String, data: String) {
            if let dataFromString: Data = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
                
                // Instantiate a new default keychain query
                let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue])
                
                let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue:dataFromString] as CFDictionary)
                
                if (status != errSecSuccess) {
                    if let err = SecCopyErrorMessageString(status, nil) {
                        print("Read failed: \(err)")
                    }
                }
            }
        }
        
        
        class func removePassword(service: String, account:String) {
            
            // Instantiate a new default keychain query
            let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue])
            
            // Delete any existing items
            let status = SecItemDelete(keychainQuery as CFDictionary)
            if (status != errSecSuccess) {
                if let err = SecCopyErrorMessageString(status, nil) {
                    print("Remove failed: \(err)")
                }
            }
            
        }
        
        
        class func savePassword(service: String, account:String, data: String) {
            if let dataFromString = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
                
                // Instantiate a new default keychain query
                let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
                
                // Add the new keychain item
                let status = SecItemAdd(keychainQuery as CFDictionary, nil)
                
                if (status != errSecSuccess) {    // Always check the status
                    if let err = SecCopyErrorMessageString(status, nil) {
                        print("Write failed: \(err)")
                    }
                }
            }
        }
        
        class func loadPassword(service: String, account:String) -> String? {
            // Instantiate a new default keychain query
            // Tell the query to return a result
            // Limit our results to one item
            let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
            
            var dataTypeRef :AnyObject?
            
            // Search for the keychain items
            let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
            var contentsOfKeychain: String?
            
            if status == errSecSuccess {
                if let retrievedData = dataTypeRef as? Data {
                    contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8)
                }
            } else {
                print("Nothing was retrieved from the keychain. Status code \(status)")
            }
            
            return contentsOfKeychain
        }
        
    }
    

    您需要想象以下连接到一个文本输入字段和一个标签,然后连接四个按钮,一个用于每个方法。

    class ViewController: NSViewController {
        @IBOutlet weak var enterPassword: NSTextField!
        @IBOutlet weak var retrievedPassword: NSTextField!
        
        let service = "myService"
        let account = "myAccount"
        
        // will only work after
        @IBAction func updatePassword(_ sender: Any) {
            KeychainService.updatePassword(service: service, account: account, data: enterPassword.stringValue)
        }
        
        @IBAction func removePassword(_ sender: Any) {
            KeychainService.removePassword(service: service, account: account)
        }
        
        @IBAction func passwordSet(_ sender: Any) {
            let password = enterPassword.stringValue
            KeychainService.savePassword(service: service, account: account, data: password)
        }
        
        @IBAction func passwordGet(_ sender: Any) {
            if let str = KeychainService.loadPassword(service: service, account: account) {
                retrievedPassword.stringValue = str
            }
            else {retrievedPassword.stringValue = "Password does not exist" }
        }
    }
    

    ##斯威夫特5## Kosuke 的version for swift 5

    import Security
    import UIKit
    
    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) }
        }
    }
    

    示例用法:

    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)
    }
    

    【讨论】:

    • 我发现这比重复问题的答案更容易理解。我进行了编辑以包括更新,如果保存代码无法覆盖现有密码(而不是提前尝试删除),它现在会自动实现。还有一种删除密码的附加方法。你会发现在 Swift 3 中你需要对 NSString 做一些转换,这不会再自动发生,但除此之外一切都应该工作。
    • 有人修复了删除和更新方法吗?我有同样的问题(错误代码-50)
    • 我找到了一个更好的解决方案:github.com/dagostini/DAKeychain/blob/master/DAKeychain/Classes/…@AsiGivati
    • 我在删除函数上遇到了 -50 OSStatus 错误,并通过在调用 SecItemDelete 之前从钥匙串查询中删除 kSecReturnDataValue 和 kSecMatchLimitValue 项来纠正它。 -50 表示传入函数的参数有问题。删除这两个为我修复了它。我假设更新函数中也存在同样的问题,但我还没有尝试过使用那个。
    • 如果 loadPassword() 失败怎么办?我的意思是......它工作,工作,工作无数次,然后有一天它失败了(在我的例子中,一个用户每 10 天体验一次)。一个简单的重试就足够了吗?或者它可能由于需要更多参与的更严重的原因而失败?
    猜你喜欢
    • 2021-07-05
    • 2017-07-22
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-31
    • 2014-09-14
    相关资源
    最近更新 更多