2 // KeychainWrapper.swift
5 // Created by Jason Rendel on 9/23/14.
6 // Copyright (c) 2014 Jason Rendel. All rights reserved.
8 // The MIT License (MIT)
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
17 // The above copyright notice and this permission notice shall be included in all
18 // copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 private let SecMatchLimit: String! = kSecMatchLimit as String
32 private let SecReturnData: String! = kSecReturnData as String
33 private let SecReturnPersistentRef: String! = kSecReturnPersistentRef as String
34 private let SecValueData: String! = kSecValueData as String
35 private let SecAttrAccessible: String! = kSecAttrAccessible as String
36 private let SecClass: String! = kSecClass as String
37 private let SecAttrService: String! = kSecAttrService as String
38 private let SecAttrGeneric: String! = kSecAttrGeneric as String
39 private let SecAttrAccount: String! = kSecAttrAccount as String
40 private let SecAttrAccessGroup: String! = kSecAttrAccessGroup as String
41 private let SecReturnAttributes: String = kSecReturnAttributes as String
43 /// KeychainWrapper is a class to help make Keychain access in Swift more straightforward. It is designed to make accessing the Keychain services more like using NSUserDefaults, which is much more familiar to people.
44 open class KeychainWrapper {
46 @available(*, deprecated: 2.2.1, message: "KeychainWrapper.defaultKeychainWrapper is deprecated, use KeychainWrapper.standard instead")
47 public static let defaultKeychainWrapper = KeychainWrapper.standard
49 /// Default keychain wrapper access
50 public static let standard = KeychainWrapper()
52 /// ServiceName is used for the kSecAttrService property to uniquely identify this keychain accessor. If no service name is specified, KeychainWrapper will default to using the bundleIdentifier.
53 private (set) public var serviceName: String
55 /// AccessGroup is used for the kSecAttrAccessGroup property to identify which Keychain Access Group this entry belongs to. This allows you to use the KeychainWrapper with shared keychain access between different applications.
56 private (set) public var accessGroup: String?
58 private static let defaultServiceName: String = {
59 return Bundle.main.bundleIdentifier ?? "SwiftKeychainWrapper"
62 private convenience init() {
63 self.init(serviceName: KeychainWrapper.defaultServiceName)
66 /// Create a custom instance of KeychainWrapper with a custom Service Name and optional custom access group.
68 /// - parameter serviceName: The ServiceName for this instance. Used to uniquely identify all keys stored using this keychain wrapper instance.
69 /// - parameter accessGroup: Optional unique AccessGroup for this instance. Use a matching AccessGroup between applications to allow shared keychain access.
70 public init(serviceName: String, accessGroup: String? = nil) {
71 self.serviceName = serviceName
72 self.accessGroup = accessGroup
75 // MARK:- Public Methods
77 /// Checks if keychain data exists for a specified key.
79 /// - parameter forKey: The key to check for.
80 /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
81 /// - returns: True if a value exists for the key. False otherwise.
82 open func hasValue(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
83 if let _ = data(forKey: key, withAccessibility: accessibility) {
90 open func accessibilityOfKey(_ key: String) -> KeychainItemAccessibility? {
91 var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key)
92 var result: AnyObject?
94 // Remove accessibility attribute
95 keychainQueryDictionary.removeValue(forKey: SecAttrAccessible)
97 // Limit search results to one
98 keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
100 // Specify we want SecAttrAccessible returned
101 keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue
104 let status = withUnsafeMutablePointer(to: &result) {
105 SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
109 if let resultsDictionary = result as? [String:AnyObject], let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String {
110 return KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString)
117 // MARK: Public Getters
119 open func integer(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Int? {
120 guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
124 return numberValue.intValue
127 open func float(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Float? {
128 guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
132 return numberValue.floatValue
135 open func double(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Double? {
136 guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
140 return numberValue.doubleValue
143 open func bool(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool? {
144 guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
148 return numberValue.boolValue
151 /// Returns a string value for a specified key.
153 /// - parameter forKey: The key to lookup data for.
154 /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
155 /// - returns: The String associated with the key if it exists. If no data exists, or the data found cannot be encoded as a string, returns nil.
156 open func string(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> String? {
157 guard let keychainData = data(forKey: key, withAccessibility: accessibility) else {
161 return String(data: keychainData, encoding: String.Encoding.utf8) as String?
164 /// Returns an object that conforms to NSCoding for a specified key.
166 /// - parameter forKey: The key to lookup data for.
167 /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
168 /// - returns: The decoded object associated with the key if it exists. If no data exists, or the data found cannot be decoded, returns nil.
169 open func object(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> NSCoding? {
170 guard let keychainData = data(forKey: key, withAccessibility: accessibility) else {
174 return NSKeyedUnarchiver.unarchiveObject(with: keychainData) as? NSCoding
178 /// Returns a Data object for a specified key.
180 /// - parameter forKey: The key to lookup data for.
181 /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
182 /// - returns: The Data object associated with the key if it exists. If no data exists, returns nil.
183 open func data(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? {
184 var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
185 var result: AnyObject?
187 // Limit search results to one
188 keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
190 // Specify we want Data/CFData returned
191 keychainQueryDictionary[SecReturnData] = kCFBooleanTrue
194 let status = withUnsafeMutablePointer(to: &result) {
195 SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
198 return status == noErr ? result as? Data : nil
202 /// Returns a persistent data reference object for a specified key.
204 /// - parameter forKey: The key to lookup data for.
205 /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
206 /// - returns: The persistent data reference object associated with the key if it exists. If no data exists, returns nil.
207 open func dataRef(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? {
208 var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
209 var result: AnyObject?
211 // Limit search results to one
212 keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
214 // Specify we want persistent Data/CFData reference returned
215 keychainQueryDictionary[SecReturnPersistentRef] = kCFBooleanTrue
218 let status = withUnsafeMutablePointer(to: &result) {
219 SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
222 return status == noErr ? result as? Data : nil
225 // MARK: Public Setters
227 @discardableResult open func set(_ value: Int, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
228 return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
231 @discardableResult open func set(_ value: Float, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
232 return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
235 @discardableResult open func set(_ value: Double, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
236 return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
239 @discardableResult open func set(_ value: Bool, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
240 return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
243 /// Save a String value to the keychain associated with a specified key. If a String value already exists for the given key, the string will be overwritten with the new value.
245 /// - parameter value: The String value to save.
246 /// - parameter forKey: The key to save the String under.
247 /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
248 /// - returns: True if the save was successful, false otherwise.
249 @discardableResult open func set(_ value: String, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
250 if let data = value.data(using: .utf8) {
251 return set(data, forKey: key, withAccessibility: accessibility)
259 /// Save an NSCoding compliant object to the keychain associated with a specified key. If an object already exists for the given key, the object will be overwritten with the new value.
261 /// - parameter value: The NSCoding compliant object to save.
262 /// - parameter forKey: The key to save the object under.
263 /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
264 /// - returns: True if the save was successful, false otherwise.
265 @discardableResult open func set(_ value: NSCoding, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
266 let data = NSKeyedArchiver.archivedData(withRootObject: value)
268 return set(data, forKey: key, withAccessibility: accessibility)
271 /// Save a Data object to the keychain associated with a specified key. If data already exists for the given key, the data will be overwritten with the new value.
273 /// - parameter value: The Data object to save.
274 /// - parameter forKey: The key to save the object under.
275 /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
276 /// - returns: True if the save was successful, false otherwise.
277 @discardableResult open func set(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
278 var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
280 keychainQueryDictionary[SecValueData] = value
282 if let accessibility = accessibility {
283 keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
285 // Assign default protection - Protect the keychain entry so it's only valid when the device is unlocked
286 keychainQueryDictionary[SecAttrAccessible] = KeychainItemAccessibility.whenUnlocked.keychainAttrValue
289 let status: OSStatus = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)
291 if status == errSecSuccess {
293 } else if status == errSecDuplicateItem {
294 return update(value, forKey: key, withAccessibility: accessibility)
300 @available(*, deprecated: 2.2.1, message: "remove is deprecated, use removeObject instead")
301 @discardableResult open func remove(key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
302 return removeObject(forKey: key, withAccessibility: accessibility)
305 /// Remove an object associated with a specified key. If re-using a key but with a different accessibility, first remove the previous key value using removeObjectForKey(:withAccessibility) using the same accessibilty it was saved with.
307 /// - parameter forKey: The key value to remove data for.
308 /// - parameter withAccessibility: Optional accessibility level to use when looking up the keychain item.
309 /// - returns: True if successful, false otherwise.
310 @discardableResult open func removeObject(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
311 let keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
314 let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary)
316 if status == errSecSuccess {
323 /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set.
324 open func removeAllKeys() -> Bool {
325 // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
326 var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword]
328 // Uniquely identify this keychain accessor
329 keychainQueryDictionary[SecAttrService] = serviceName
331 // Set the keychain access group if defined
332 if let accessGroup = self.accessGroup {
333 keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
336 let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary)
338 if status == errSecSuccess {
345 /// Remove all keychain data, including data not added through keychain wrapper.
347 /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper.
349 open class func wipeKeychain() {
350 deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items
351 deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items
352 deleteKeychainSecClass(kSecClassCertificate) // Certificate items
353 deleteKeychainSecClass(kSecClassKey) // Cryptographic key items
354 deleteKeychainSecClass(kSecClassIdentity) // Identity items
357 // MARK:- Private Methods
359 /// Remove all items for a given Keychain Item Class
362 @discardableResult private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Bool {
363 let query = [SecClass: secClass]
364 let status: OSStatus = SecItemDelete(query as CFDictionary)
366 if status == errSecSuccess {
373 /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data
374 private func update(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
375 var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
376 let updateDictionary = [SecValueData:value]
378 // on update, only set accessibility if passed in
379 if let accessibility = accessibility {
380 keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
384 let status: OSStatus = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary)
386 if status == errSecSuccess {
393 /// Setup the keychain query dictionary used to access the keychain on iOS for a specified key name. Takes into account the Service Name and Access Group if one is set.
395 /// - parameter forKey: The key this query is for
396 /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. If none is provided, will default to .WhenUnlocked
397 /// - returns: A dictionary with all the needed properties setup to access the keychain on iOS
398 private func setupKeychainQueryDictionary(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> [String:Any] {
399 // Setup default access as generic password (rather than a certificate, internet password, etc)
400 var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword]
402 // Uniquely identify this keychain accessor
403 keychainQueryDictionary[SecAttrService] = serviceName
405 // Only set accessibiilty if its passed in, we don't want to default it here in case the user didn't want it set
406 if let accessibility = accessibility {
407 keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
410 // Set the keychain access group if defined
411 if let accessGroup = self.accessGroup {
412 keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
415 // Uniquely identify the account who will be accessing the keychain
416 let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8)
418 keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier
420 keychainQueryDictionary[SecAttrAccount] = encodedIdentifier
422 return keychainQueryDictionary