iOS Keychain Services: only specific values allowed for kSecAttrGeneric Key?
Okay, I found the solution in this blog post Keychain duplicate item when adding password
To sum it up, the issue is that the GenericKeychain sample app uses the value stored in kSecAttrGeneric key as the identifier for the keychain item when in fact that is not what the API uses to determine a unique keychain item. The keys you need to set with unique values are the kSecAttrAccount key and/or the kSecAttrService key.
You can rewrite the initilizer of KeychainItemWrapper so you don't need to change any other code by changing these lines:
Change:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
to:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];
and change:
[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
to:
[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];
Or, you could do what I did and write a new initilizer that takes both of the identifying keys:
Edit: For people using ARC (you should be nowadays) check nycynik's answer for all the correct bridging notation
- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
if (self = [super init])
{
NSAssert(account != nil || service != nil, @"Both account and service are nil. Must specifiy at least one.");
// Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
// kSecAttrService are used as unique identifiers differentiating keychain items from one another
genericPasswordQuery = [[NSMutableDictionary alloc] init];
[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
[genericPasswordQuery setObject:service forKey:(id)kSecAttrService];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
NSMutableDictionary *outDictionary = nil;
if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
//Adding the account and service identifiers to the keychain
[keychainItemData setObject:account forKey:(id)kSecAttrAccount];
[keychainItemData setObject:service forKey:(id)kSecAttrService];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
}
[outDictionary release];
}
return self;
}
Hope this helps someone else out!
What makes a keychain item unique (in iOS)?
The primary keys are as follows (derived from open source files from Apple, see Schema.m4, KeySchema.m4 and SecItem.cpp):
- For a keychain item of class
kSecClassGenericPassword
, the primary key is the combination ofkSecAttrAccount
andkSecAttrService
. - For a keychain item of class
kSecClassInternetPassword
, the primary key is the combination ofkSecAttrAccount
,kSecAttrSecurityDomain
,kSecAttrServer
,kSecAttrProtocol
,kSecAttrAuthenticationType
,kSecAttrPort
andkSecAttrPath
. - For a keychain item of class
kSecClassCertificate
, the primary key is the combination ofkSecAttrCertificateType
,kSecAttrIssuer
andkSecAttrSerialNumber
. - For a keychain item of class
kSecClassKey
, the primary key is the combination ofkSecAttrApplicationLabel
,kSecAttrApplicationTag
,kSecAttrKeyType
,kSecAttrKeySizeInBits
,kSecAttrEffectiveKeySize
, and the creator, start date and end date which are not exposed by SecItem yet. - For a keychain item of class
kSecClassIdentity
I haven't found info on the primary key fields in the open source files, but as an identity is the combination of a private key and a certificate, I assume the primary key is the combination of the primary key fields forkSecClassKey
andkSecClassCertificate
.
As each keychain item belongs to a keychain access group, it feels like the keychain access group (field kSecAttrAccessGroup
) is an added field to all these primary keys.
fetching all items in keychain in one shot?
I believe you're slightly misreading that example. In GenericKeychain, they're not fetching "all items." They're fetching just one item and its one value. A keychain item is made up of properties, which are semi-public, and a "value" which is protected. Reading out of the keychain is fantastically expensive (much, much slower than reading a file off of disk; it's really shockingly slow). So the example is avoiding re-reading it when not needed. But it's not reading the entire keychain; just the one item that stores its data.
Access all (user) accounts inside Keychain
Use kSecMatchLimitAll
to get all values in your query dictionary for kSecMatchLimit
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit
It will fetch all passwords in keychain for kSecClassGenericPassword
.You can use other keychain classes
as well like this
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass, //change your class in query
nil];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSLog(@"%@", (__bridge id)result);
if (result != NULL) CFRelease(result);
EDIT : For delting all keys of your app you can use
+(void)deleteAllKeychainItems{
NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass:secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
It will delete all keychain
items including all accounts password or values associated.
Why SecItemAdd return me -50 (invalid params)
The error is occurring because of kSecValueRef
, as per Apple's guideline kSecValueRef
accepts a cryptographic key which can be generated through SecKeyRef
, Please find below,
NSData* tag = [@"com.example.keys.mykey" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* attributes =
@{ (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @2048,
(id)kSecPrivateKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: tag,
},
};
CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes,
&error);
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
NSDictionary* addquery = @{ (id)kSecValueRef: (__bridge id)publicKey,
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addquery, NULL);
For more info please refer Storing Keys in the Keychain
How to add another password into iOS Keychain?
I doubt if that can be done.... at max you can create two username with the second userame havinge "_passcode" attached to it and save the passcode to it...
for eg:
username: ankit
password: 1234
save this to keychain
and then again save another pair using
username: ankit_passcode
passcode: 4321
you can then retrieve it using the username provided...
hoping this helps.
Xamarin.iOS Keychain hangs when trying to query for a key storing NSData
I've fixed the problem i was having, it seems to have been some kind of race condition that was locking the app as the call to save into keychain was being done on the main thread. Normally this call is very quick and never had a problem locking anything up in the past, but something in the iOS 12.1 update changed that. In any case, I simply explicitly ran code saving the encryption password into keychain in a background thread and that fixed the problem:
Task.Run(() =>
{
var keychain = new KeyChain();
keychain.SetValueForKey("securedvalue", "securedvaluekey");
}).ConfigureAwait(false);
Related Topics
How to Capture Picture with Avcapturesession in Swift
How to Get Current Longitude and Latitude Using Cllocationmanager-Swift
Nsattributedstring Inserting a Bullet Point
Move a View When Scrolling in Uitableview
Uicollectionview Sticky Header in Swift
Expanding and Collapsing Table View Cells in iOS
iOS Swift Mapkit Custom Annotation
How to Create an Ibinspectable of Type Enum
Show Search Bar in Navigation Bar Without Scrolling on iOS 11
How to Get Selected Value from Uipickerview
"Initialize" Class Method for Classes in Swift
--Resource-Rules Has Been Deprecated in MAC Os X >= 10.10
How to Check Where My App Is Using Idfa
How to Use Cocoapods When Creating a Cocoa Touch Framework
How to Debug "Invalid Bundle" Error Which Happens Only After Submitting to App Store