SecItemAdd() 使用 kSecAttrAccessibleWhenUnlocked 成功但使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly 失败
SecItemAdd() Succeeds with kSecAttrAccessibleWhenUnlocked But Fails with kSecAttrAccessibleWhenUnlockedThisDeviceOnly
到目前为止的故事
四个月前,我发布了 因为升级到 iOS 13 破坏了我的钥匙串相关代码。
我的代码使用 class kSecClassGenericPassword
和访问属性 kSecAttrAccessibleWhenUnlocked
将用户密码存储在钥匙串中。正如我自己对那个问题的回答所解释的那样,我最终通过稍微清理查询字典让我的代码也可以在 iOS 13 上工作。
目前的问题
几周前,我被要求禁用密码数据备份以增强安全性,因此我将访问属性更改为kSecAttrAccessibleWhenUnlockedThisDeviceOnly
(与kSecAttrAccessibleWhenUnlocked
,钥匙串中的密码在备份期间未传输到另一台设备)。
现在,我的代码失败了,用户每次都必须输入密码。(在 iOS 13.0,iPhone 8 Plus 上测试)
当用户使用他们的密码登录时,我的代码首先使用 SecItemDelete()
删除任何先前存储的密码,然后使用 SecItemMatch()
.
继续存储输入的密码
由于将访问属性更改为 kSecAttrAccessibleWhenUnlockedThisDeviceOnly
、SecItemDelete()
"succeeds" 和 errSecItemNotFound
(即 "Nothing to delete"),但是 SecItemAdd()
失败 errSecDuplicateItem
!
请注意,这不是尝试使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly
检索以前用 kSecAttrAccessibleWhenUnlocked
存储的密码的问题(即,用于存储和加载的不同查询字典);我从设备上删除了该应用程序并从头开始尝试使用新代码,但 SecItemAdd()
总是失败。
这是怎么回事?
我想你已经明白了,但为了完整起见:
Note, this isn't an issue of trying to retrieve a password previously stored with kSecAttrAccessibleWhenUnlocked using kSecAttrAccessibleWhenUnlockedThisDeviceOnly (i.e., different query dictionaries for store and load); I deleted the app from the device and tried from the start with the new code, and SecItemAdd() always fails.
这正是发生在你身上的事情。钥匙串内容在应用程序删除和 re-install 后仍然存在。要解决此问题,您需要提出一种迁移策略,该策略将删除使用相同主键保存的项目,但在尝试保存新项目之前使用不同的访问控制设置
如果您使用已弃用的可访问密钥,例如:“kSecAttrAccessibleAlwaysThisDeviceOnly”或 'kSecAttrAccessibleAlways',那么处理钥匙串中保存的项目的最佳方法是制定迁移策略,将使用这些已弃用密钥保存的钥匙串项目隐藏到 convert/save/copy 使用新密钥(如“kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly”。
没有更好的方法,因为钥匙串项目总是在应用程序删除和重新安装后仍然存在。
这是有效的代码:
注意:应该为所有键触发(对于每个 key/value 对)
+ (void)updateNewchainDataForKey:(NSString*)key {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
//Our OLD keychain items were saved with 'kSecAttrAccessibleAlwaysThisDeviceOnly'(DEPRECATED) accessible key
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
CFDictionaryRef resultDataRef = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, (CFTypeRef *)&resultDataRef);
NSDictionary *resultDict = (__bridge_transfer NSDictionary *)resultDataRef;
NSLog(@"resultDict %@", resultDict);
if( status != errSecSuccess) {
NSLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
return;
}
if (status == errSecSuccess && resultDict) {
// Check if we have the old attribute type(s)
if ([[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlways)]
|| [[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlwaysThisDeviceOnly)]) {
// Update the deviceID attribute to kSecAttrAccessibleAlwaysThisDeviceOnly
NSMutableDictionary *updateQuery = [NSMutableDictionary dictionary];
//Our keychain items are now being saved with 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly' accessible key
// Set the new attribute
[updateQuery setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
// Perform the update
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dict, (__bridge CFDictionaryRef)updateQuery);
if (status != errSecSuccess) {
NSLog(@"status failed %d", (int)status);
} else {
NSLog(@"status PASS %d", (int)status);
}
}
}
}
注意:执行上述代码后,与old/deprecated可访问密钥一起保存的key/value对将被删除,只有key/value 用新的可访问密钥更新的对将存在于钥匙串中
希望,这有助于挽救某人的一天!
到目前为止的故事
四个月前,我发布了
我的代码使用 class kSecClassGenericPassword
和访问属性 kSecAttrAccessibleWhenUnlocked
将用户密码存储在钥匙串中。正如我自己对那个问题的回答所解释的那样,我最终通过稍微清理查询字典让我的代码也可以在 iOS 13 上工作。
目前的问题
几周前,我被要求禁用密码数据备份以增强安全性,因此我将访问属性更改为kSecAttrAccessibleWhenUnlockedThisDeviceOnly
(与kSecAttrAccessibleWhenUnlocked
,钥匙串中的密码在备份期间未传输到另一台设备)。
现在,我的代码失败了,用户每次都必须输入密码。(在 iOS 13.0,iPhone 8 Plus 上测试)
当用户使用他们的密码登录时,我的代码首先使用 SecItemDelete()
删除任何先前存储的密码,然后使用 SecItemMatch()
.
由于将访问属性更改为 kSecAttrAccessibleWhenUnlockedThisDeviceOnly
、SecItemDelete()
"succeeds" 和 errSecItemNotFound
(即 "Nothing to delete"),但是 SecItemAdd()
失败 errSecDuplicateItem
!
请注意,这不是尝试使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly
检索以前用 kSecAttrAccessibleWhenUnlocked
存储的密码的问题(即,用于存储和加载的不同查询字典);我从设备上删除了该应用程序并从头开始尝试使用新代码,但 SecItemAdd()
总是失败。
这是怎么回事?
我想你已经明白了,但为了完整起见:
Note, this isn't an issue of trying to retrieve a password previously stored with kSecAttrAccessibleWhenUnlocked using kSecAttrAccessibleWhenUnlockedThisDeviceOnly (i.e., different query dictionaries for store and load); I deleted the app from the device and tried from the start with the new code, and SecItemAdd() always fails.
这正是发生在你身上的事情。钥匙串内容在应用程序删除和 re-install 后仍然存在。要解决此问题,您需要提出一种迁移策略,该策略将删除使用相同主键保存的项目,但在尝试保存新项目之前使用不同的访问控制设置
如果您使用已弃用的可访问密钥,例如:“kSecAttrAccessibleAlwaysThisDeviceOnly”或 'kSecAttrAccessibleAlways',那么处理钥匙串中保存的项目的最佳方法是制定迁移策略,将使用这些已弃用密钥保存的钥匙串项目隐藏到 convert/save/copy 使用新密钥(如“kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly”。
没有更好的方法,因为钥匙串项目总是在应用程序删除和重新安装后仍然存在。
这是有效的代码: 注意:应该为所有键触发(对于每个 key/value 对)
+ (void)updateNewchainDataForKey:(NSString*)key {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
//Our OLD keychain items were saved with 'kSecAttrAccessibleAlwaysThisDeviceOnly'(DEPRECATED) accessible key
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
CFDictionaryRef resultDataRef = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, (CFTypeRef *)&resultDataRef);
NSDictionary *resultDict = (__bridge_transfer NSDictionary *)resultDataRef;
NSLog(@"resultDict %@", resultDict);
if( status != errSecSuccess) {
NSLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
return;
}
if (status == errSecSuccess && resultDict) {
// Check if we have the old attribute type(s)
if ([[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlways)]
|| [[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlwaysThisDeviceOnly)]) {
// Update the deviceID attribute to kSecAttrAccessibleAlwaysThisDeviceOnly
NSMutableDictionary *updateQuery = [NSMutableDictionary dictionary];
//Our keychain items are now being saved with 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly' accessible key
// Set the new attribute
[updateQuery setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
// Perform the update
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dict, (__bridge CFDictionaryRef)updateQuery);
if (status != errSecSuccess) {
NSLog(@"status failed %d", (int)status);
} else {
NSLog(@"status PASS %d", (int)status);
}
}
}
}
注意:执行上述代码后,与old/deprecated可访问密钥一起保存的key/value对将被删除,只有key/value 用新的可访问密钥更新的对将存在于钥匙串中
希望,这有助于挽救某人的一天!