Сохранить и загрузить из KeyChain | Свифт [дубликат]

На этот вопрос уже есть ответ здесь:

Сохранить и получить значение через KeyChain 6 ответов

Как просто хранить String в связке ключей и загружать при необходимости. Существует несколько SO-решений, которые в основном относятся к Git-репо. Но мне нужно самое маленькое и простое решение на последнем Swift. Конечно, я не хочу добавлять git framework для простого хранения пароля в моем проекте.

Есть похожее решениеСохранить и получить значение через 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" }
    }
}
 GameDev14 июл. 2017 г., 16:49
Обновление () не работает! Обновление не удалось: -50.
 Saranjith12 июл. 2017 г., 08:55
Я получаю код ошибки -25300
 Sazzad Hissain Khan20 июн. 2017 г., 06:58
Пожалуйста, проверьте, существует ли ключ или нет
 Parv Bhasker26 апр. 2018 г., 15:36
этот код отлично работает на моем конце. Как я могу получить тот же код в цель с
 Krunal25 окт. 2017 г., 12:11
импорт какао & SecCopyErrorMessageString показывает ошибку с сообщением «Неразрешенный идентификатор»
 Asi Givati26 июн. 2017 г., 12:03
кто-то исправлял методы удаления и обновления? у меня та же проблема (код ошибки -50)
 Mitesh Dobareeya12 мая 2018 г., 07:28
Есть ли вероятность того, что сохраненные данные в KeyChain будут затронуты? Я имею ввиду обновление ОС или тд?
 Navy Seal10 апр. 2019 г., 16:44
да ... вы не должны хранить пароль и использовать вместо этого токены доступа, еще хуже, если вы сделаете что-то вроде KeychainService.savePassword ("Pa55worD"), чтобы его можно было прочитать, по крайней мере, используйте md5 или что-то еще, но да ... это пример так что ... просто предупреждаю людей
 Florin Odagiu25 авг. 2017 г., 16:56
Что делать, если loadPassword () не работает? Я имею в виду ... это работает, и работает, и работает бесчисленное количество раз, а затем однажды это терпит неудачу (в моем случае, один пользователь испытывает это один раз каждые 10 дней). Будет ли достаточно простой повторной попытки? Или он мог потерпеть неудачу по более серьезной причине, требующей чего-то более сложного?
 Sazzad Hissain Khan12 июл. 2017 г., 12:42
когда ты получишь этот код?
 sketchyTech16 июл. 2017 г., 17:32
@GameDev Сейчас у меня нет времени, чтобы переделать мой код обратно в пример, но я скопировал код из простого приложения, созданного мной, со всеми необходимыми элементами, которые не должны быть слишком сложными для адаптации и делают код более портативный.
 helloWorld19 июн. 2017 г., 23:52
removePassword () и Update не удаляют пароль для меня. Я получаю код состояния «-50». Я не изменил код, тестировал его.
 GameDev14 июл. 2017 г., 16:50
Я обновил до последней версии swift, которая изменила эту строку в функции Update: let dataFromString: NSData = data.data (используя: String.Encoding.utf8.rawValue, allowLossyConversion: false)! как NSData. Это все еще правильно или неправильно сейчас?
 Krunal25 окт. 2017 г., 12:13
как я могу использовать этот код в iOS? (Посмотрите здесь -stackoverflow.com/questions/34053049/...)
 Matthew Barker07 июл. 2017 г., 20:43
Я нашел лучшее решение, которое работает:github.com/dagostini/DAKeychain/blob/master/DAKeychain/Classes/... @AsiGivati
 kikettas21 янв. 2018 г., 11:41
Спасибо за ответ, довольно полезный. Кстати, я бы отметил, чтоSecCopyErrorMessageString доступно только в MacOS.
 Boaz Saragossi24 авг. 2017 г., 15:15
imjohnking прав, в обновлении и удалении строки запроса должны быть - пусть keychainQuery: NSMutableDictionary = NSMutableDictionary (объекты: [kSecClassGenericPasswordValue, обслуживание, счета, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
 GameDev14 июл. 2017 г., 17:12
@sketchyTech Пожалуйста, вы можете исправить обновление и удалить функции для правильной работы?
 CRey25 июн. 2017 г., 06:48
Здравствуйте @SazzadHissainKhan, я использую ваш код для сохранения пароля. Я также хочу сохранить электронную почту в то же время. Можете ли вы дать мне совет, как это сделать? мне нужен целый новый набор частных методов только для электронной почты учетной записи? Пожалуйста, и спасибо, сэр.
 Vaibhav Misra21 апр. 2017 г., 11:43
Большое спасибо за это! У меня только один вопрос здесь. Для сохранения пароля вы сначала удаляете, а затем добавляете. Не лучше ли сначала проверить, существует ли пароль, а затем либо обновить, либо добавить его в связку ключей? Каковы плюсы / минусы этого подхода по сравнению с другим?
 ScottyBlades25 нояб. 2018 г., 20:15
Невозможно преобразовать значение типа 'String.Encoding' в ожидаемый тип аргумента 'UInt', замените 'String.Encoding.utf8' на 'String.Encoding.utf8.rawValue'
 vntstudy04 окт. 2018 г., 13:05
@GameDev Удалить kSecReturnDataValue из вызова обновления.
 imjohnking11 авг. 2017 г., 15:08
Я столкнулся с ошибками -50 OSStatus в функции удаления и исправил ее, удалив элементы kSecReturnDataValue и kSecMatchLimitValue из запроса цепочки для ключей перед вызовом SecItemDelete. -50 указывает на проблему с параметрами, которые были переданы в функцию. Удаление этих двух исправило это для меня. Я предполагаю, что та же проблема существует в функции обновления, но я еще не пробовал работать с ней.
 GameDev19 июл. 2017 г., 14:40
@sketchyTech спасибо, я попробую это!
 sketchyTech07 июн. 2017 г., 11:17
Я нахожу это намного проще для понимания, чем ответы на двойной вопрос. Я отредактировал включение обновления, которое теперь выполняет код сохранения автоматически, если он не может перезаписать существующий пароль (вместо того, чтобы пытаться удалить заранее). Существует также добавленный метод удаления пароля. В Swift 3 вы обнаружите, что вам нужно выполнить приведение к NSString, где это больше не происходит автоматически, но кроме этого все должно работать.
 sketchyTech19 июл. 2017 г., 14:48
@GameDev нет проблем. Не должно быть никаких сообщений об ошибках, если элемент, который извлекается, удаляется или обновляется, не существует. Одной из причин, по которой могли быть сообщения об ошибках с исходным примером кода, является то, что часто при сохранении данных вы не можете сразу же получить или изменить их, потому что сохранение происходит в фоновом режиме и занимает несколько минут. Лучший тест - использовать кнопки, чтобы сделать небольшую паузу, как при реальном использовании.

Ваш ответ на вопрос