从 KeyChain 保存和加载 |斯威夫特 [重复]

Posted

技术标签:

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

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

有类似的解决方案 Save and retrieve value via 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://***.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 天体验一次)。一个简单的重试就足够了吗?或者它可能由于需要更多参与的更严重的原因而失败?

以上是关于从 KeyChain 保存和加载 |斯威夫特 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

笔试题

keychain

Swift 面向协议的 Keychain 类库 Locksmith

如何使用 segue 从 Xib 文件加载 viewController?斯威夫特 5

解析和斯威夫特。从 Parse 关系中设置 tableViewCell 附件类型

IOS开发之——keychain使用介绍