什么使得iOS中的钥匙串项目独特?

128

我的问题涉及iOS(iPhone、iPad等)中的钥匙串。我认为(但不确定)在Mac OS X下实现钥匙串会引发同样的问题,并得出同样的答案。


iOS提供了五种类型(类)的钥匙串项目。你必须选择这五个值之一作为键kSecClass来确定该类型:

kSecClassGenericPassword  used to store a generic password
kSecClassInternetPassword used to store an internet password
kSecClassCertificate      used to store a certificate
kSecClassKey              used to store a kryptographic key
kSecClassIdentity         used to store an identity (certificate + private key)

经过长时间阅读苹果文档、博客和论坛文章,我发现类型为kSecClassGenericPassword的钥匙串项从属性kSecAttrAccessGroupkSecAttrAccountkSecAttrService中获得其唯一性。

如果请求1中的这三个属性与请求2中的相同,则无论其他属性如何,您都会收到相同的通用密码钥匙串项。如果一个(或两个或所有)这些属性改变了其值,那么您就会得到不同的条目。

但是kSecAttrService仅适用于kSecClassGenericPassword类型的条目,因此它不能成为任何其他类型的项目的“唯一键”的一部分,而且似乎没有文件指出哪些属性唯一确定了钥匙串项。

“GenericKeychain”类的“KeychainItemWrapper”中的示例代码使用属性kSecAttrGeneric来使项目唯一,但这是一个错误。这个例子中的两个条目只存储为两个不同的条目,因为它们的kSecAttrAccessGroup不同(一个设置了访问组,另一个不设置)。如果你尝试使用苹果的KeychainItemWrapper添加第二个没有访问组的密码,你会失败。

所以,请回答我的问题:

  • 组合kSecAttrAccessGroupkSecAttrAccountkSecAttrService是否是kSecClass为kSecClassGenericPassword的钥匙串项的“唯一键”?
  • 如果kSecClass不是kSecClassGenericPassword,哪些属性会使一个钥匙串项成为唯一的?

2
这里有一篇关于此事的博客文章 - bobobobo
4个回答

221
以下是主键列表(从苹果公司的开源文件中提取,参见 Schema.m4KeySchema.m4SecItem.cpp):
  • 对于类别为 kSecClassGenericPassword 的钥匙串项目,主键为 kSecAttrAccountkSecAttrService 的组合。
  • 对于类别为 kSecClassInternetPassword 的钥匙串项目,主键为 kSecAttrAccountkSecAttrSecurityDomainkSecAttrServerkSecAttrProtocolkSecAttrAuthenticationTypekSecAttrPortkSecAttrPath 的组合。
  • 对于类别为 kSecClassCertificate 的钥匙串项目,主键为 kSecAttrCertificateTypekSecAttrIssuerkSecAttrSerialNumber 的组合。
  • 对于类别为 kSecClassKey 的钥匙串项目,主键为 kSecAttrApplicationLabelkSecAttrApplicationTagkSecAttrKeyTypekSecAttrKeySizeInBitskSecAttrEffectiveKeySize,以及创建者、起始日期和截止日期(这些信息目前还未由 SecItem 公开)的组合。
  • 对于类kSecClassIdentity的钥匙串项目,我在开源文件中没有找到有关主键字段的信息,但由于身份是私钥和证书的组合,我认为主键是kSecClassKeykSecClassCertificate的主键字段的组合。
  • 由于每个钥匙串项目都属于一个钥匙串访问组,因此感觉钥匙串访问组(字段kSecAttrAccessGroup)是添加到所有这些主键的字段。


    3
    好的回答!我已经花了几天时间实现一个通用的钥匙串包装器,用于证书和私钥。这与苹果的示例代码有很大不同,后者仅存储字符串凭据(用户名/密码)。但是,我已经发现当你将 kSecClass 设置为 kSecClassCertificatekSecClassKey 时,钥匙串还会检查条目(即value)是否已经被存储。这可以防止重复添加同一证书或密钥。此外,如果您为一个密钥指定了不同的 kSecAttrApplicationTag(必须是唯一的,关于上面的帖子),则它将失败。 - Chris
    @Chris 很高兴知道这个额外的检查,谢谢! - Tammo Freese
    1
    可以将kSecClass属性视为__表名__,上面指定的值只是相应表的主键 - bobobobo
    3
    kSecAttrAccountkSecAttrService 的语义是什么?或者程序员可以选择任何她决定的语义吗? - wcochran
    1
    kSecAttrService 用于存储服务,kSecAttrAccount 用于存储账户名称。你可以在它们中存储不同的内容,但这可能会让人感到困惑。 - Tammo Freese
    显示剩余4条评论

    10

    前几天我遇到了一个与这个问题有关的错误(iOS 7.1)。我正在使用SecItemCopyMatching读取一个kSecClassGenericPassword项目,但它一直返回errSecItemNotFound(-25300),即使kSecAttrAccessGroupkSecAttrAccountkSecAttrService都与钥匙链中的项目匹配。

    最终我发现kSecAttrAccessible不匹配。钥匙链中的值为pdmn = dk(kSecAttrAccessibleAlways),但我使用的是kSecAttrAccessibleWhenUnlocked

    当然,对于SecItemCopyMatching来说,这个值本身就不需要,但OSStatus既不是errSecParam,也不是errSecBadReq,而是仅仅是errSecItemNotFound(-25300),这让它有点难以找到。

    对于SecItemUpdate,我也曾经遇到过同样的问题,但是在这个方法中,即使在query参数中使用相同的kSecAttrAccessible也不起作用。只有完全删除这个属性才能解决问题。

    我希望这个评论能为你们中的一些人节省一些宝贵的调试时间。


    8
    @Tammo Freese提供的答案似乎是正确的(但没有提到所有主键)。我在文档中搜索了一些证据。最后找到了:

    Apple 文档提到每个密钥链类别的主键(如下所述):

    系统认为给定密钥链中存在具有相同组合主键集的相同类别项时,该项为重复项。每个密钥链项类别都有不同的主键集,尽管一些属性在所有类别之间都是通用的。特别地,在适用的情况下,kSecAttrSynchronizable 和 kSecAttrAccessGroup 是主键集的一部分。以下是每个类别额外的主键:
    - 对于通用密码,主键包括 kSecAttrAccount 和 kSecAttrService。
    - 对于互联网密码,主键包括 kSecAttrAccount、kSecAttrSecurityDomain、kSecAttrServer、kSecAttrProtocol、kSecAttrAuthenticationType、kSecAttrPort 和 kSecAttrPath。
    - 对于证书,主键包括 kSecAttrCertificateType、kSecAttrIssuer 和 kSecAttrSerialNumber。
    - 对于密钥项,主键包括 kSecAttrKeyClass、kSecAttrKeyType、kSecAttrApplicationLabel、kSecAttrApplicationTag、kSecAttrKeySizeInBits 和 kSecAttrEffectiveKeySize。
    - 对于身份证明项(包括绑定在一起的证书和私钥),主键与证书相同。因为私钥可能会被多次认证,所以证书的唯一性决定了身份证明的唯一性。

    虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供该链接作为参考。仅有链接的答案如果链接页面更改可能会失效。-【来自评论】 - pwc
    同意,尽管在这种情况下它意味着复制整个链接。 - Julian

    0

    这是关于密钥链项唯一性的另一个有用信息,可以在此苹果文档页面的“确保可搜索性”部分找到。

    为了能够在之后找到该项,您将会使用对其属性的了解。在这个例子中,服务器和账户是该项的区分特征。对于常量属性(例如这里的服务器),在查找过程中应保持相同的值。相反,账户属性是动态的,因为它保存了用户在运行时提供的值。只要您的应用程序从未添加过具有不同属性的类似项(例如同一服务器上不同账户的密码),就可以省略这些动态属性作为搜索参数,并将它们与该项一起检索。结果是,在查找密码时,您也会得到相应的用户名。

    如果您的应用程序确实添加了具有不同动态属性的项,则需要一种在检索过程中进行选择的方法。其中一个选项是以另一种方式记录有关项的信息。例如,如果您在 Core Data 模型中保存用户记录,则使用钥匙串服务存储密码字段后,将用户名存储在那里。然后,您可以使用从数据模型中提取的用户名来设置密码的搜索条件。

    在其他情况下,通过添加更多属性来进一步描述该项可能是有意义的。例如,您可以在原始添加查询中包括kSecAttrLabel属性,提供一个标记该项特定目的的字符串。然后,您就可以使用此属性来缩小之后的搜索范围。

    在示例中使用了kSecClassInternetPassword类的项目,但有一个注释说:

    钥匙串服务还提供了相关的kSecClassGenericPassword项目类。通用密码在大多数方面与Internet密码相似,但它们缺少特定于远程访问的某些属性(例如,它们没有kSecAttrServer属性)。当您不需要这些额外的属性时,请改用通用密码。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接