So this is my first question on Stack Overflow and it's about keychain so, I read apple documentation about keychain and they mention that the key can be generated or obtain by other means "which is my case" then I created "query dictionary" like they said ,but when I tried to add it I get an error and my status is equal to -50 I don't know what does than mean also the reason I am trying to store my key is so I can use it with "SecKeyDecrypt" to decrypt messages which require SecKey as parameter
UPDATE: I found that what the return code means -50 errSecParam which mean
"One or more parameters passed to the function are not valid." then I try to remove my kSecValueRef as String: privateKey
and it works but still, I want that key to be stored ??
let privateKey = "myKey"
let tag = "mybunlde.com".data(using: .utf8)!
let addPrivateKey: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecValueRef as String: privateKey]
let status = SecItemAdd(addPrivateKey as CFDictionary, nil)
guard status == errSecSuccess else { print("error while creating the key")
return
}
let getPrivateKey: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
print("getPrivateKey \(getPrivateKey)")
} else {
print("no key found ")
}
https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_keychain?language=objc
https://apple.stackexchange.com/questions/8993/how-can-i-add-a-private-key-to-my-keychain
Adding private key into iOS Keychain
use above link.....
you can use this lib for store & retrieve data from keychain
https://github.com/jrendel/SwiftKeychainWrapper
e.g
let save: Bool = KeychainWrapper.standard.set("hello keychain", forKey: "key_name")
let get: String? = KeychainWrapper.standard.string(forKey: "key_name")
Related
I'm looking for the simples way to store/load an accessToken and refreshToken in the iOS Keychain.
So far I've come to this:
enum Key: String {
case accessToken = "some.keys.accessToken"
case refreshToken = "some.keys.refreshToken"
fileprivate var tag: Data {
rawValue.data(using: .utf8)!
}
}
enum KeychainError: Error {
case storeFailed
case loadFailed
}
func store(key: Key, value: String) throws {
let addQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
kSecValueRef as String: value
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print("Store key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.storeFailed
}
}
func load(key: Key) throws -> String? {
let getQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
guard status == errSecSuccess else {
print("Load key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.loadFailed
}
return item as? String
}
But this fail with messages:
When running store:
Store key: 'some.keys.accessToken' in Keychain failed with status: -50
When running load:
Load key: 'some.keys.accessToken' in Keychain failed with status: -25300
What am I doing wrong here?
As per recommendations of Apple, you should use a kSecClassGenericPassword class to store arbitrary data, i.e. tokens, securely. To do that properly, you'll need a String identifier to store under the kSecAttrAccount key and a Data representation of the secure value to store under the kSecValueData key. You can easily transform your string value to Data by doing the following (assuming the token contains UTF8 data).
tokenString.data(using: .utf8)
// or
Data(tokenString.utf8)
First some nice to haves.
/// Errors that can be thrown when the Keychain is queried.
enum KeychainError: LocalizedError {
/// The requested item was not found in the Keychain.
case itemNotFound
/// Attempted to save an item that already exists.
/// Update the item instead.
case duplicateItem
/// The operation resulted in an unexpected status.
case unexpectedStatus(OSStatus)
}
/// A service that can be used to group the tokens
/// as the kSecAttrAccount should be unique.
let service = "com.bundle.stuff.token-service"
Inserting a token into the Keychain.
func insertToken(_ token: Data, identifier: String, service: String = service) throws {
let attributes = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier,
kSecValueData: token
] as CFDictionary
let status = SecItemAdd(attributes, nil)
guard status == errSecSuccess else {
if status == errSecDuplicateItem {
throw KeychainError.duplicateItem
}
throw KeychainError.unexpectedStatus(status)
}
}
The retrieval will be done as follows.
func getToken(identifier: String, service: String = service) throws -> String {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier,
kSecMatchLimit: kSecMatchLimitOne,
kSecReturnData: true
] as CFDictionary
var result: AnyObject?
let status = SecItemCopyMatching(query, &result)
guard status == errSecSuccess else {
if status == errSecItemNotFound {
// Technically could make the return optional and return nil here
// depending on how you like this to be taken care of
throw KeychainError.itemNotFound
}
throw KeychainError.unexpectedStatus(status)
}
// Lots of bang operators here, due to the nature of Keychain functionality.
// You could work with more guards/if let or others.
return String(data: result as! Data, encoding: .utf8)!
}
Note that a generic password has certain specifications, as mentioned before, and I guess the most important one, is that the kSecAttrAccount flag must be unique for each token you store. You cannot store access token A and access token B for the same identifier. This will cause the .duplicateItem error to trigger.
I'd also like to point out that the OSStatus website is very useful for getting more info about your error code. Besides the website there is also the SecCopyErrorMessageString(OSStatus, UnsafeMutableRawPointer?) function that can get you more information about your error code.
Now that technically answers your question, but below I've added some more nice to haves. Update updates a token value for an existing item, make sure the item exists before calling update!. Upsert inserts a token when it doesn't already exist, if it does, it will update the token value. Delete will remove the token value from the Keychain.
func updateToken(_ token: Data, identifier: String, service: String = service) throws {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier
] as CFDictionary
let attributes = [
kSecValueData: token
] as CFDictionary
let status = SecItemUpdate(query, attributes)
guard status == errSecSuccess else {
if status == errSecItemNotFound {
throw KeychainError.itemNotFound
}
throw KeychainError.unexpectedStatus(status)
}
}
func upsertToken(_ token: Data, identifier: String, service: String = service) throws {
do {
_ = try getToken(identifier: identifier, service: service)
try updateToken(token, identifier: identifier, service: service)
} catch KeychainError.itemNotFound {
try insertToken(token, identifier: identifier, service: service)
}
}
func deleteToken(identifier: String, service: String = service) throws {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier
] as CFDictionary
let status = SecItemDelete(query)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.unexpectedStatus(status)
}
}
Please try the following edits, marked with comments:
enum KeychainError: Error {
case storeFailed
case loadFailed
}
func store(key: Key, value: String) throws {
let addQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
// use kSecValueData, converting your String to Data
kSecValueData as Data: value.data(using: .utf8)
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print("Store key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.storeFailed
}
}
func load(key: Key) throws -> String? {
let getQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
// remove kSecAttrKeyType and add kSecReturnData
kSecReturnData as String: kCFBooleanTrue
]
// this is different
var item: AnyObject?
let status: OSStatus = withUnsafeMutablePointer(to:&item)
{ (result: UnsafeMutablePointer<AnyObject?>?) -> OSStatus in
return SecItemCopyMatching(getQuery as CFDictionary, result)
}
guard status == errSecSuccess else {
print("Load key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.loadFailed
}
// convert Data to String
guard let itemData = item as? Data else { throw KeychainError.loadFailed }
return String(decoding: itemData, as: UTF8.self)
}
how does this change the behavior?
A couple comments:
I'm not sure why you're using kSecAttrKeyType property to store a
String
I'm unsure of the use of kSecValueRef vs. kSecValueData, only
making this suggestion based on what has worked for me
Hope this helps!?
I have a problem updating and deleting keychain.
public func clearKeychain(username: String) throws -> Any?{
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username
]
let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess {
throw KeychainError.unhandledError(status: status)
}
print("Clear Keychain")
return status
}
public func updateKeychain(username: String, password: String) throws -> Any?{
let credentials = Credentials.init(username: username, password: password)
let data = credentials.password.data(using: .utf8)!
// store password as data and if you want to store username
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecValueData as String: data]
let fields: [String: Any] = [
kSecAttrAccount as String: username,
kSecValueData as String: data
]
let status = SecItemUpdate(query as CFDictionary, fields as CFDictionary)
guard status == errSecSuccess else {
throw KeychainError.unhandledError(status: status) }
print("Updated Password")
return status
}
Usage:
let _ = (try? keychain.clearKeychain(username: "KeychainUser")) as Any?
let _ = (try? keychain.updateKeychain(username: "KeychainUser", password: "123456789")) as Any?
Can't delete or update keychain. I have no problem creating and getting the value of the keychain, but I have a problem when updating or deleting. No error is shown. And status returns nil.
To update & delete your item, you need to search particular type of data. So you don't require to pass data in query. Just update your query as follow will fix issue on updating data.
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword]
By default, keychain services deletes all keychain items that match
the search parameters. If you want to delete a specific item that you
already have a reference or persistent reference to, add that to the
search dictionary as the value for the kSecMatchItemList key. In this
way, you limit the deletion to only the specified item.
I was trying to store two keys private and public in KeyChain and when I try to do so my result valuable return 0 which I am assuming that mean it was store ,but when I try to get back to decrypt a message I am getting it back as nil so if there is a way to check if the keys were store based SecItemCopyMatching? but I am not getting any error while creating them
let tagName = "PrivateKeyTag"
let privkey = "key"
let privkeyData = Data(privkey!.utf8)
let privateFilter: [String : Any] = [
(kSecClass as String) : kSecClassKey,
(kSecAttrKeyType as String) : kSecAttrKeyTypeRSA,
(kSecAttrApplicationTag as String) : tagName,
(kSecValueData as String) : privkeyData,
(kSecAttrKeyClass as String) : kSecAttrKeyClassPrivate,
// kSecAttrKeySizeInBits as String: 2048,
(kSecReturnPersistentRef as String): true,
] as [String : Any]
let result = SecItemAdd(privateFilter as CFDictionary, nil)
if ((result != noErr) && (result != errSecDuplicateItem)) {
NSLog("Cannot add key to keychain, status \(result).")
}
let getquery: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
print("key not found")
return
}
let key = item as! SecKey
When you create a cryptographic key, you can set the parameter kSecAttrIsPermanent to true which will automatically store the key in the default keychain. This will clean your code a bit so you no longer have to deal with the SecItemAdd() and all the error handling with that. So here is a simpler way to do what you're trying to do.
To create a key and query a key
let tag = "com.example.keys.mykey".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil)
let query: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
var item: CFTypeRef?
let status2 = SecItemCopyMatching(query as CFDictionary, &item)
guard status2 == errSecSuccess else { print("error1"); return }
let key = item as! SecKey
Running this code I believe will accomplish what you are trying to do in the code you provided in the question. I tried running the code you gave and the version of Xcode and swift I am using gives compiler errors.
If you run the code in this answer you will notice that no errors are printed to the console, indicating that the key was successfully found in the default keychain.
I am using SecureEnclave on iOS to encrypt/decrypt my confidential key and save it in UserPreferences. It has been setup and encrypting the data successfully. But whenever I try to decrypt the data, it gives me the following error:
Error Domain=NSOSStatusErrorDomain Code=-50 \"ECIES: Failed to aes-gcm decrypt data\" UserInfo={NSDescription=ECIES: Failed to aes-gcm decrypt data}
After a lot of searching, I found some links but they are not of any help. This Github issue talks about the issue. It states,
Additionally, on 10.3 there was a problem with decrypting large amounts of data with kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM. I filed a bug report for it, and it got fixed in iOS 11. :)
But I am using iPhone 8 with iOS 12.2 and there is still a problem.
These two SO questions, here and here provide some details but I am not able to decipher it. Plus, I am using Swift 4.
Below is the relevant code which I am using to encrypt/decrypt the data.
To Generate Key Pair
func generateKeyPair(accessControl: SecAccessControl) throws -> (`public`: SecureEnclaveKeyReference, `private`: SecureEnclaveKeyReference) {
let publicKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String: "com.xxx.xxx" as AnyObject,
kSecAttrLabel as String: "PublicKey" as AnyObject
]
let privateKeyParameters: [String: AnyObject] = [
(kSecAttrCanDecrypt as CFString) as String: true as CFBoolean,
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrAccessControl as String: accessControl,
kSecAttrApplicationTag as String: "com.xxx.xxx" as AnyObject,
kSecAttrLabel as String: "PrivateKey" as AnyObject
]
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256 as AnyObject,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPublicKeyAttrs as String: publicKeyParameters as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParameters as AnyObject
]
var publicKey, privateKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &publicKey, &privateKey)
print("Result = \(status) - Public Key = \(publicKey) - Private Key = \(privateKey)")
guard status == errSecSuccess else {
throw SecureEnclaveHelperError(message: "Could not generate keypair", osStatus: status)
}
return (public: SecureEnclaveKeyReference(publicKey!), private: SecureEnclaveKeyReference(privateKey!))
}
Encryption
#available(iOS 10.3, *)
func encrypt(_ digest: Data, publicKey: SecureEnclaveKeyReference) throws -> Data {
var error : Unmanaged<CFError>?
let result = SecKeyCreateEncryptedData(publicKey.underlying, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, digest as CFData, &error)
if result == nil {
throw SecureEnclaveHelperError(message: "\(error)", osStatus: 0)
}
return result as! Data
}
Decryption
#available(iOS 10.3, *)
func decrypt(_ digest: Data, privateKey: SecureEnclaveKeyReference) throws -> Data {
var error : Unmanaged<CFError>?
let result = SecKeyCreateDecryptedData(privateKey.underlying, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, digest as CFData, &error)
if result == nil {
throw SecureEnclaveHelperError(message: "\(error)", osStatus: 0)
}
return result as! Data
}
Any help is highly appreciated. Thanks.
I got the same error and found out that I generated two different key pairs where I encrypted my data with one public key but performed decryption using the other wrong private key. So make sure that the decryption operation uses the right private key corresponding to the public key used in encryption.
I am creating Unwanted Communication Extension extension for my app, But when i try to access the keychain for login details, it is not able to access the keychain.
Note: Entitlements with keychain sharing is added and keychain sharing is on for my app.
I tried using this code on my extension view controller and it returns error code -25291 on SecItemCopyMatching
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: keychainAccessGroupName as AnyObject
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad == noErr {
if let result = result as? Data,
let keyValue = NSString(data: result,
encoding: String.Encoding.utf8.rawValue) as? String {
// Found successfully
print(keyValue)
}
} else {
print("Error loading from Keychain: \(resultCodeLoad)")
}