跳转至

Insecure Keychain Storage

不安全的 Keychain 访问控制

描述

Keychain 是一个专门用于在 iOS 设备上安全存储敏感数据的数据库。密码、加密密钥或证书等数据都可以保存在 Keychain 中。 在创建或更新 Keychain 项目时,开发人员可以使用 kSecAttrAccessible 属性来决定其可被访问的条件。使用弱访问控制值(如 kSecAttrAccessibleAlwayskSecAttrAccessibleAlwaysThisDeviceOnly)会导致即使在设备锁定状态下,Keychain 项目依然可以被访问。

由于安全风险,kSecAttrAccessibleAlwayskSecAttrAccessibleAlwaysThisDeviceOnly 已从 iOS 12.0 开始被弃用。使用它们可能导致在设备丢失、被盗或被入侵时,敏感数据暴露给未经授权的个体。

在以下示例中,由于未完整指定访问控制标志(SecAccessControlCreateFlags),导致 Keychain 项目的存储不安全:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleAlways,
        .or,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()
import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleAlways,
        .and,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

在这些示例中,正确指定访问控制标志以确保安全的 Keychain 项目存储至关重要。如果没有诸如 kSecAccessControlUserPresence 之类的特定标志,Keychain 项目仍容易受到未授权访问。

建议

当简单的访问控制要求已足够时,您可以直接使用 kSecAttrAccessible 指定访问级别。在这些情况下省略 kSecAttrAccessControl 是可以接受的。

场景 保护
存储始终可访问的数据 kSecAttrAccessibleAlways
存储首次解锁后可访问的数据 kSecAttrAccessibleAfterFirstUnlock
存储仅在设备解锁时可访问的数据 kSecAttrAccessibleWhenUnlocked

示例:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessible as String: kSecAttrAccessibleAlways
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

对于更复杂的安全要求(例如需要用户在场或实施特定于应用程序的密码),请使用 SecAccessControlCreateWithFlags 创建带有附加标志的访问控制。

场景 保护 标志
处理需要用户在场的敏感数据 kSecAttrAccessibleWhenUnlocked kSecAccessControlUserPresence
处理需要特定密码以提供额外安全性的敏感数据 kSecAttrAccessibleWhenPasscodeSet kSecAccessControlApplicationPassword

示例:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlocked,
        kSecAccessControlUserPresence,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

链接

标准

  • OWASP_MASVS_L2:
    • MSTG_AUTH_8
  • GDPR:
    • ART_5
    • ART_25
    • ART_32
  • PCI_STANDARDS:
    • REQ_6_2
    • REQ_6_3
    • REQ_8_3
    • REQ_11_3
  • SOC2_CONTROLS:
    • CC_2_1
    • CC_4_1
    • CC_6_1
    • CC_7_1
    • CC_7_2
    • CC_7_4
    • CC_7_5
  • CNIL_FOR_EDITORS:
    • EDITORS_1_3_1
  • HIPAA_CONTROLS:
    • SECURITY251
    • SECURITY212
    • SECURITY213