iOS 数据存储¶
概述¶
敏感数据(如认证令牌和私人信息)的保护是移动安全的关键。在本章中,你将了解 iOS 本地数据存储的 API,以及使用它们的最佳实践。
应尽可能少地将敏感数据保存在永久本地存储中。然而,在大多数实际场景中,至少有一些用户数据必须存储。幸运的是,iOS 提供了安全的存储 API,允许开发者使用每个 iOS 设备上可用的加密硬件。如果正确使用这些 API,敏感数据和文件可以通过硬件支持的 256 位 AES 加密来保护。
NSData 和 NSMutableData¶
NSData
(静态数据对象)和 NSMutableData
(动态数据对象)通常用于数据存储,但它们也适用于分布式对象应用程序,其中数据对象中包含的数据可以在应用程序之间复制或移动。以下是用于写入 NSData
对象的方法
NSDataWritingWithoutOverwriting
NSDataWritingFileProtectionNone
NSDataWritingFileProtectionComplete
NSDataWritingFileProtectionCompleteUnlessOpen
NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication
writeToFile
: 将数据作为NSData
类的一部分存储NSSearchPathForDirectoriesInDomains, NSTemporaryDirectory
: 用于管理文件路径NSFileManager
: 允许你检查和更改文件系统内容。你可以使用createFileAtPath
创建文件并写入其中。
以下示例展示了如何使用 FileManager
类创建加密的 complete
文件。你可以在 Apple 开发者文档 “加密你的应用程序文件” 中找到更多信息
Swift
FileManager.default.createFile(
atPath: filePath,
contents: "secret text".data(using: .utf8),
attributes: [FileAttributeKey.protectionKey: FileProtectionType.complete]
)
Objective-C
[[NSFileManager defaultManager] createFileAtPath:[self filePath]
contents:[@"secret text" dataUsingEncoding:NSUTF8StringEncoding]
attributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey]];
NSUserDefaults¶
NSUserDefaults
类提供了与默认系统交互的程序接口。默认系统允许应用程序根据用户偏好设置自定义其行为。通过 NSUserDefaults
保存的数据可以在应用程序包中查看。此类将数据存储在 plist 文件中,但它旨在用于少量数据。
数据库¶
CoreData¶
Core Data
是一个用于管理应用程序中对象模型层的框架。它为对象生命周期和对象图管理(包括持久性)相关的常见任务提供了通用和自动化的解决方案。Core Data 可以使用 SQLite 作为其持久存储,但框架本身不是数据库。
CoreData 默认不加密其数据。作为 MITRE 公司一项专注于开源 iOS 安全控制的研究项目 (iMAS) 的一部分,可以为 CoreData 添加额外的加密层。更多详情请参见 GitHub 仓库。
SQLite 数据库¶
如果应用程序要使用 SQLite,则必须将 SQLite 3 库添加到应用程序中。该库是一个 C++ 包装器,为 SQLite 命令提供 API。
Firebase 实时数据库¶
Firebase 是一个拥有超过 15 种产品的开发平台,其中之一就是 Firebase 实时数据库。应用程序开发人员可以利用它将数据存储并同步到 NoSQL 云托管数据库。数据以 JSON 格式存储,并实时同步到每个连接的客户端,即使应用程序离线也仍然可用。
可以通过进行以下网络调用来识别配置错误的 Firebase 实例
https://<firebaseProjectName>.firebaseio.com/.json
firebaseProjectName 可以从属性列表 (.plist) 文件中检索。例如,PROJECT_ID
键在 GoogleService-Info.plist 文件中存储相应的 Firebase 项目名称。
或者,分析师可以使用 Firebase Scanner,这是一个自动执行上述任务的 python 脚本,如下所示
python FirebaseScanner.py -f <commaSeparatedFirebaseProjectNames>
Realm 数据库¶
Realm Objective-C 和 Realm Swift 并非由 Apple 提供,但仍值得注意。除非配置启用了加密,否则它们将所有内容未加密存储。
以下示例演示了如何将加密与 Realm 数据库一起使用
// Open the encrypted Realm file where getKey() is a method to obtain a key from the Keychain or a server
let config = Realm.Configuration(encryptionKey: getKey())
do {
let realm = try Realm(configuration: config)
// Use the Realm as normal
} catch let error as NSError {
// If the encryption key is wrong, `error` will say that it's an invalid database
fatalError("Error opening realm: \(error)")
}
对数据的访问取决于加密:未加密的数据库很容易访问,而加密的数据库则需要调查密钥如何管理——是硬编码的还是未加密地存储在不安全的位置(如共享偏好设置),还是安全地存储在平台的 KeyStore 中(这是最佳实践)。但是,如果攻击者有足够的设备访问权限(例如越狱访问)或可以重新打包应用程序,他们仍然可以使用 Frida 等工具在运行时检索加密密钥。以下 Frida 脚本演示了如何拦截 Realm 加密密钥并访问加密数据库的内容。
function nsdataToHex(data) {
var hexStr = '';
for (var i = 0; i < data.length(); i++) {
var byte = Memory.readU8(data.bytes().add(i));
hexStr += ('0' + (byte & 0xFF).toString(16)).slice(-2);
}
return hexStr;
}
function HookRealm() {
if (ObjC.available) {
console.log("ObjC is available. Attempting to intercept Realm classes...");
const RLMRealmConfiguration = ObjC.classes.RLMRealmConfiguration;
Interceptor.attach(ObjC.classes.RLMRealmConfiguration['- setEncryptionKey:'].implementation, {
onEnter: function(args) {
var encryptionKeyData = new ObjC.Object(args[2]);
console.log(`Encryption Key Length: ${encryptionKeyData.length()}`);
// Hexdump the encryption key
var encryptionKeyBytes = encryptionKeyData.bytes();
console.log(hexdump(encryptionKeyBytes, {
offset: 0,
length: encryptionKeyData.length(),
header: true,
ansi: true
}));
// Convert the encryption key bytes to a hex string
var encryptionKeyHex = nsdataToHex(encryptionKeyData);
console.log(`Encryption Key Hex: ${encryptionKeyHex}`);
},
onLeave: function(retval) {
console.log('Leaving RLMRealmConfiguration.- setEncryptionKey:');
}
});
}
}
Couchbase Lite 数据库¶
Couchbase Lite 是一个轻量级、嵌入式、面向文档 (NoSQL) 的数据库引擎,可以同步。它为 iOS 和 macOS 本地编译。
YapDatabase¶
YapDatabase 是一个基于 SQLite 构建的键/值存储。
用户界面¶
UI 组件¶
在注册账户或进行支付等操作时输入敏感信息,是许多应用程序使用的重要部分。这些数据可能是财务信息,如信用卡数据或用户账户密码。如果应用程序在输入时未能正确遮盖数据,则数据可能会泄露。
为了防止信息泄露和减轻诸如肩窥等风险,你应该验证用户界面中没有暴露敏感数据,除非明确要求(例如,正在输入的密码)。对于需要显示的数据,应进行适当的遮盖,通常通过显示星号或点而不是明文。
仔细检查所有显示此类信息或将其作为输入的所有 UI 组件。查找任何敏感信息的痕迹,并评估是否应该遮盖或完全删除。
截屏¶
制造商为了在应用程序启动或退出时为设备用户提供美观的效果,引入了在应用程序进入后台时保存截屏的概念。此功能可能会带来安全风险,因为截屏(可能显示敏感信息,如电子邮件或公司文档)被写入本地存储,恶意应用程序可以通过沙盒绕过漏洞或设备被盗后进行恢复。
键盘缓存¶
用户可以使用多种选项,例如自动更正和拼写检查,以简化键盘输入,这些选项默认缓存到 /private/var/mobile/Library/Keyboard/
及其子目录中的 .dat
文件中。
UITextInputTraits 协议用于键盘缓存。UITextField
、UITextView
和 UISearchBar
类自动支持此协议,并提供以下属性
var autocorrectionType: UITextAutocorrectionType
决定了在输入时是否启用自动更正。启用自动更正时,文本对象会跟踪未知单词并建议合适的替换,除非用户覆盖替换,否则会自动替换输入的文本。此属性的默认值为UITextAutocorrectionTypeDefault
,对于大多数输入法,它会启用自动更正。var secureTextEntry: BOOL
决定是否禁用文本复制和文本缓存,并隐藏UITextField
中正在输入的文本。此属性的默认值为NO
。
内部存储¶
数据保护 API¶
应用程序开发者可以利用 iOS 的数据保护API,为存储在闪存中的用户数据实现精细的访问控制。这些 API 构建在安全隔离区处理器(Secure Enclave Processor,SEP)之上,该处理器随 iPhone 5S 引入。SEP 是一个协处理器,为数据保护和密钥管理提供加密操作。设备特定的硬件密钥——设备 UID(唯一 ID)——嵌入在安全隔离区中,即使操作系统内核受到威胁,也能确保数据保护的完整性。
您可以在 Tarjei Mandt、Mathew Solnik 和 David Wang 的这篇 BlackHat 演示文稿 “揭秘安全隔离区处理器” 中了解更多关于安全隔离区的信息。
数据保护架构基于密钥层级。UID 和用户密码密钥(通过 PBKDF2 算法从用户密码短语派生)位于此层级的顶部。它们可以共同用于“解锁”所谓的类密钥,这些密钥与不同的设备状态(例如,设备锁定/解锁)相关联。
iOS 文件系统上存储的每个文件都用其自己的每文件密钥加密,该密钥包含在文件元数据中。元数据使用文件系统密钥加密,并用应用程序在创建文件时选择的保护类别对应的类别密钥进行封装。
下图显示了 iOS 数据保护密钥层级。
文件可以分配到四种不同的保护类别之一,这些类别在 iOS 安全指南中进行了更详细的解释
-
完全保护 (NSFileProtectionComplete):从此类别密钥派生的密钥受到用户密码和设备 UID 的保护。派生密钥在设备锁定后不久从内存中清除,使数据在用户解锁设备之前无法访问。
-
除非打开受保护 (NSFileProtectionCompleteUnlessOpen):此保护类别类似于“完全保护”,但是,如果文件在解锁时打开,即使用户锁定设备,应用程序也可以继续访问该文件。此保护类别用于例如邮件附件在后台下载时。
-
直到首次用户认证受保护 (NSFileProtectionCompleteUntilFirstUserAuthentication):文件在用户启动后首次解锁设备时即可访问。即使用户随后锁定设备并且类密钥未从内存中移除,它也可以被访问。
-
无保护 (NSFileProtectionNone):此保护类别的密钥仅受 UID 保护。该类密钥存储在“可擦除存储”中,这是 iOS 设备闪存的一个区域,允许存储少量数据。此保护类别用于快速远程擦除(立即删除类密钥,使数据无法访问)。
除 NSFileProtectionNone
外,所有类别密钥都使用从设备 UID 和用户密码派生的密钥进行加密。因此,解密只能在设备本身上进行,并且需要正确的密码。
自 iOS 7 以来,默认的数据保护类是“直到首次用户认证受保护”。
外部存储¶
钥匙串¶
iOS 钥匙串可用于安全地存储短小敏感数据位,例如加密密钥和会话令牌。它作为 SQLite 数据库实现,只能通过钥匙串 API 访问。
在 macOS 上,每个用户应用程序都可以根据需要创建任意数量的钥匙串,每个登录帐户都有自己的钥匙串。iOS 上的钥匙串结构不同:只有一个钥匙串可供所有应用程序使用。通过属性 kSecAttrAccessGroup
的访问组功能,同一开发者签名的应用程序可以共享对项目(items)的访问权限。对钥匙串的访问由 securityd
守护进程管理,该守护进程根据应用程序的 Keychain-access-groups
、application-identifier
和 application-group
权限授予访问权限。
钥匙串 API 包含以下主要操作
SecItemAdd
SecItemUpdate
SecItemCopyMatching
SecItemDelete
存储在钥匙串中的数据通过类似于文件加密所用类结构的类结构进行保护。添加到钥匙串中的项目以二进制 plist 编码,并使用 Galois/计数器模式 (GCM) 的 128 位 AES 每项密钥加密。请注意,较大的数据块不应直接保存在钥匙串中——那是数据保护 API 的用途。您可以通过在调用 SecItemAdd
或 SecItemUpdate
时设置 kSecAttrAccessible
键来配置钥匙串项目的数据保护。以下可配置的 kSecAttrAccessible 可访问性值是钥匙串数据保护类
kSecAttrAccessibleAlways
:钥匙串项目中的数据始终可访问,无论设备是否锁定。kSecAttrAccessibleAlwaysThisDeviceOnly
:钥匙串项目中的数据始终可访问,无论设备是否锁定。数据不会包含在 iCloud 或本地备份中。kSecAttrAccessibleAfterFirstUnlock
:钥匙串项目中的数据在重启后无法访问,直到用户首次解锁设备。kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
:钥匙串项目中的数据在重启后无法访问,直到用户首次解锁设备。具有此属性的项目不会迁移到新设备。因此,从不同设备的备份恢复后,这些项目将不存在。kSecAttrAccessibleWhenUnlocked
:钥匙串项目中的数据仅在设备被用户解锁时才能访问。kSecAttrAccessibleWhenUnlockedThisDeviceOnly
:钥匙串项目中的数据仅在设备被用户解锁时才能访问。数据不会包含在 iCloud 或本地备份中。kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
:钥匙串中的数据仅在设备解锁时才能访问。此保护类别仅在设备上设置密码时可用。数据不会包含在 iCloud 或本地备份中。
AccessControlFlags
定义了用户可以认证密钥的机制(SecAccessControlCreateFlags
)
kSecAccessControlDevicePasscode
:通过密码访问项目。kSecAccessControlBiometryAny
:通过 Touch ID 注册的指纹之一访问项目。添加或移除指纹不会使项目失效。kSecAccessControlBiometryCurrentSet
:通过 Touch ID 注册的指纹之一访问项目。添加或移除指纹会使项目失效。kSecAccessControlUserPresence
:通过注册的指纹之一(使用 Touch ID)或默认使用密码访问项目。
请注意,受 Touch ID 保护的密钥(通过 kSecAccessControlBiometryAny
或 kSecAccessControlBiometryCurrentSet
)由安全隔离区保护:钥匙串只保存一个令牌,而不是实际的密钥。密钥位于安全隔离区中。
从 iOS 9 开始,您可以在安全隔离区中执行基于 ECC 的签名操作。在这种情况下,私钥和加密操作都驻留在安全隔离区内。有关创建 ECC 密钥的更多信息,请参阅静态分析部分。iOS 9 仅支持 256 位 ECC。此外,您需要将公钥存储在钥匙串中,因为它不能存储在安全隔离区中。创建密钥后,您可以使用 kSecAttrKeyType
来指示您希望将密钥用于哪种算法。
如果您想使用这些机制,建议测试是否已设置密码。在 iOS 8 中,您需要检查是否可以从受 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
属性保护的钥匙串项中读取/写入。从 iOS 9 开始,您可以使用 LAContext
检查是否设置了锁定屏幕
Swift
public func devicePasscodeEnabled() -> Bool {
return LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
}
Objective-C
-(BOOL)devicePasscodeEnabled:(LAContext)context{
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil]) {
return true;
} else {
return false;
}
}
以下是您可以用来创建密钥的 Swift 代码示例(请注意 kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave
:这表示我们希望直接使用安全隔离区。)
// private key parameters
let privateKeyParams = [
kSecAttrLabel as String: "privateLabel",
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "applicationTag",
] as CFDictionary
// public key parameters
let publicKeyParams = [
kSecAttrLabel as String: "publicLabel",
kSecAttrIsPermanent as String: false,
kSecAttrApplicationTag as String: "applicationTag",
] as CFDictionary
// global parameters
let parameters = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPublicKeyAttrs as String: publicKeyParams,
kSecPrivateKeyAttrs as String: privateKeyParams,
] as CFDictionary
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters, &pubKey, &privKey)
if status != errSecSuccess {
// Keys created successfully
}
钥匙串数据持久性¶
在 iOS 上,当应用程序被卸载时,应用程序使用的钥匙串数据会保留在设备上,这与应用程序沙盒存储的数据会被清除不同。如果用户在未执行出厂重置的情况下出售其设备,设备的购买者可能可以通过重新安装前用户使用的相同应用程序来访问前用户的应用程序账户和数据。这不需要任何技术能力即可执行。
评估 iOS 应用程序时,您应该检查钥匙串数据的持久性。这通常通过使用应用程序生成可能存储在钥匙串中的示例数据,然后卸载应用程序,再重新安装应用程序以查看数据是否在应用程序安装之间保留来完成。使用 objection 运行时移动探索工具来转储钥匙串数据。以下 objection
命令演示了此过程
...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios keychain dump
Note: You may be asked to authenticate using the devices passcode or TouchID
Save the output by adding `--json keychain.json` to this command
Dumping the iOS keychain...
Created Accessible ACL Type Account Service Data
------------------------- ------------------------------ ----- -------- ------------------------- ------------------------------------------------------------- ------------------------------------
2020-02-11 13:26:52 +0000 WhenUnlocked None Password keychainValue com.highaltitudehacks.DVIAswiftv2.develop mysecretpass123
没有 iOS API 允许开发者在应用程序卸载时强制清除数据。相反,开发者应该采取以下步骤来防止钥匙串数据在应用程序安装之间持久化
- 应用程序安装后首次启动时,清除所有与该应用程序关联的钥匙串数据。这将防止设备的第二个用户意外访问前一个用户的账户。以下 Swift 示例是此清除过程的基本演示
let userDefaults = UserDefaults.standard
if userDefaults.bool(forKey: "hasRunBefore") == false {
// Remove Keychain items here
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
}
- 在开发 iOS 应用程序的注销功能时,请确保钥匙串数据作为账户注销的一部分被清除。这将允许用户在卸载应用程序之前清除其账户。
日志¶
在移动设备上创建日志文件有许多合理的原因,包括跟踪在设备离线时本地存储的崩溃或错误(以便在设备在线后可以发送给应用程序开发者),以及存储使用统计信息。然而,记录敏感数据,如信用卡号和会话信息,可能会将数据暴露给攻击者或恶意应用程序。日志文件可以通过多种方式创建。以下列表显示了 iOS 上可用的方法
- NSLog 方法
- printf-like 函数
- NSAssert-like 函数
- 宏
备份¶
iOS 包含自动备份功能,可以创建设备上存储数据的副本。您可以通过使用 iTunes(直到 macOS Catalina)或 Finder(从 macOS Catalina 起),或者通过 iCloud 备份功能,从您的主机计算机制作 iOS 备份。在这两种情况下,备份都包括 iOS 设备上存储的几乎所有数据,除了高度敏感的数据,如 Apple Pay 信息和触控 ID 设置。
由于 iOS 会备份已安装的应用程序及其数据,一个明显的担忧是应用程序存储的敏感用户数据是否可能通过备份无意中泄露。另一个担忧,尽管不那么明显,是用于保护数据或限制应用程序功能的敏感配置设置是否可能被篡改,以便在恢复修改后的备份后更改应用程序行为。这两个担忧都是有效的,并且这些漏洞已被证明存在于当今大量的应用程序中。
钥匙串如何备份¶
当用户备份他们的 iOS 设备时,钥匙串数据也会被备份,但钥匙串中的秘密保持加密状态。解密钥匙串数据所需的类别密钥不包含在备份中。恢复钥匙串数据需要将备份恢复到设备并使用用户密码解锁设备。
设置为 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
属性的钥匙串项目只有在备份恢复到备份设备时才能解密。试图从备份中提取此钥匙串数据的人,如果没有访问原始设备内部加密硬件的权限,就无法解密它。
然而,使用钥匙串的一个注意事项是,它最初仅设计用于存储少量用户数据或短笔记(根据 Apple 关于钥匙串服务的文档)。这意味着有较大本地安全存储需求的应用程序(例如,消息应用程序等)应该在应用程序容器内加密数据,但使用钥匙串存储密钥材料。在敏感配置设置(例如,数据丢失预防策略、密码策略、合规性策略等)必须在应用程序容器内保持未加密的情况下,您可以考虑在钥匙串中存储策略的哈希值以进行完整性检查。如果没有完整性检查,这些设置可能会在备份中被修改,然后恢复到设备以修改应用程序行为(例如,更改配置的远程端点)或安全设置(例如,越狱检测、证书固定、最大 UI 登录尝试次数等)。
结论:如果敏感数据按照本章前面建议的方式处理(例如,存储在钥匙串中,通过钥匙串支持的完整性检查,或使用钥匙串中锁定的密钥进行加密),则备份不应成为安全问题。
进程内存¶
分析内存可以帮助开发人员识别应用程序崩溃等问题的根本原因。然而,它也可以用于访问敏感数据。本节描述了如何检查进程内存是否存在数据泄露。
首先,识别存储在内存中的敏感信息。敏感资产很可能会在某个时候加载到内存中。目标是确保这些信息暴露的时间尽可能短。
要调查应用程序的内存,首先创建一个内存转储。或者,您可以使用调试器等工具实时分析内存。无论您使用哪种方法,这都是一个非常容易出错的过程,因为转储提供了已执行函数留下的数据,您可能会错过执行关键步骤。此外,在分析过程中很容易忽略数据,除非您知道正在寻找的数据的特征(无论是其确切值还是其格式)。例如,如果应用程序根据随机生成的对称密钥进行加密,您不太可能在内存中发现该密钥,除非您通过其他方式找到其值。
在查看源代码之前,检查文档并识别应用程序组件可以提供数据可能暴露位置的概述。例如,虽然从后端接收的敏感数据存在于最终的模型对象中,但 HTTP 客户端或 XML 解析器中也可能存在多个副本。所有这些副本都应尽快从内存中移除。
了解应用程序的架构及其与操作系统的交互将帮助您识别根本无需在内存中暴露的敏感信息。例如,假设您的应用程序从一个服务器接收数据并将其传输到另一个服务器,而无需任何额外的处理。该数据可以以加密形式接收和处理,从而防止通过内存暴露。
然而,如果敏感数据确实需要在内存中暴露,请确保您的应用程序尽可能少地暴露此数据的副本,且暴露时间尽可能短。换句话说,您希望对敏感数据进行集中处理,基于原始和可变的数据结构。
此类数据结构允许开发人员直接访问内存。确保此访问用于将敏感数据和加密密钥用零覆盖。Apple 安全编码指南建议在使用后将敏感数据归零,但未提供推荐的方法。
首选的数据类型示例包括 char []
和 int []
,但不包括 NSString
或 String
。每当您尝试修改一个不可变对象(如 String
)时,您实际上是创建了一个副本并修改了该副本。考虑使用 NSMutableData
来存储 Swift/Objective-C 中的秘密,并使用 resetBytes(in:)
方法进行归零。此外,请参阅 清除秘密数据内存作为参考。
避免使用集合之外的 Swift 数据类型,无论它们是否被认为是可变的。许多 Swift 数据类型以值而非引用方式持有其数据。尽管这允许修改分配给简单类型(如 char
和 int
)的内存,但按值处理复杂类型(如 String
)涉及一个隐藏的对象层、结构或原始数组,其内存无法直接访问或修改。某些类型的用法可能看起来会创建一个可变数据对象(甚至有文档记录),但它们实际上创建的是一个可变标识符(变量),而不是一个不可变标识符(常量)。例如,许多人认为以下 Swift 代码会生成一个可变的 String
,但实际上这是一个变量,其复杂值可以更改(替换,而非原地修改)的例子
var str1 = "Goodbye" // "Goodbye", base address: 0x0001039e8dd0
str1.append(" ") // "Goodbye ", base address: 0x608000064ae0
str1.append("cruel world!") // "Goodbye cruel world", base address: 0x6080000338a0
str1.removeAll() // "", base address 0x00010bd66180
请注意,底层值的基地址随每次字符串操作而变化。问题在于:要安全地从内存中擦除敏感信息,我们不希望简单地更改变量的值;我们希望更改为当前值分配的内存的实际内容。Swift 不提供这样的函数。
另一方面,Swift 集合(Array
、Set
和 Dictionary
)如果它们收集基本数据类型(例如 char
或 int
)并且被定义为可变(即作为变量而不是常量),那么它们可能是可以接受的,在这种情况下,它们或多或少等同于原始数组(例如 char []
)。这些集合提供内存管理,如果集合需要将底层缓冲区复制到其他位置以进行扩展,则可能导致内存中出现未识别的敏感数据副本。
使用可变的 Objective-C 数据类型,例如 NSMutableString
,也可能是可以接受的,但这些类型与 Swift 集合存在相同的内存问题。使用 Objective-C 集合时请注意;它们通过引用持有数据,并且只允许 Objective-C 数据类型。因此,我们寻找的不是一个可变的集合,而是一个引用可变对象的集合。
如前所述,使用 Swift 或 Objective-C 数据类型需要对语言实现有深入的理解。此外,在主要 Swift 版本之间进行了一些核心重构,导致许多数据类型的行为与其他类型不兼容。为避免这些问题,我们建议在需要从内存中安全擦除数据时使用原始数据类型。
不幸的是,很少有库和框架被设计为允许覆盖敏感数据。甚至 Apple 也在官方 iOS SDK API 中没有考虑这个问题。例如,大多数数据转换 API(解析器、序列化器等)都在非原始数据类型上操作。同样,无论您是否将某个 UITextField
标记为“安全文本输入”,它始终以 String
或 NSString
的形式返回数据。
IPC¶
进程间通信 (IPC) 允许进程相互发送消息和数据。对于需要相互通信的进程,iOS 上有不同的 IPC 实现方式
- XPC 服务:XPC 是一个结构化、异步的库,提供基本的进程间通信。它由
launchd
管理。它是 iOS 上最安全、最灵活的 IPC 实现,应作为首选方法。它在最受限制的环境中运行:沙盒化,没有 root 权限提升,最小的文件系统访问和网络访问。XPC 服务使用两种不同的 API- NSXPCConnection API
- XPC Services API
- Mach 端口:所有 IPC 通信最终都依赖于 Mach 内核 API。Mach 端口只允许本地通信(设备内通信)。它们可以通过原生方式或通过 Core Foundation (CFMachPort) 和 Foundation (NSMachPort) 封装器实现。
- NSFileCoordinator:
NSFileCoordinator
类可用于通过本地文件系统上可供各种进程访问的文件,管理应用程序之间的数据发送和接收。NSFileCoordinator 方法同步运行,因此您的代码将被阻塞,直到它们停止执行。这很方便,因为您无需等待异步块回调,但这也意味着这些方法会阻塞正在运行的线程。