19、iOS数据安全:XML库、钥匙串与数据保护API全解析

iOS数据安全:XML库、钥匙串与数据保护API全解析

1. 替代XML库的问题

在各种iOS项目中,你可能会遇到替代XML库。这些库通常因其比NSXMLParser更好的性能特性以及对XPath等功能的支持而被选用。当检查使用替代XML库的代码时,需遵循以下步骤确保安全:
1. 禁用外部实体扩展 :使用该库的标准方法禁用外部实体扩展。
2. 输入清理 :确认任何集成外部提供输入的XPath查询都先对输入进行清理,就像防止跨站脚本攻击那样。
3. 参数化查询 :XPath查询应像SQL查询一样进行参数化,但具体方法可能因涉及的第三方库而异。

2. 钥匙串的使用

钥匙串用于存储小段敏感数据,如密码、个人数据等。它使用设备密钥(若有用户密码则结合使用)进行加密。钥匙串的API主要包含四个操作:
- SecItemAdd :向钥匙串添加项目。
- SecItemUpdate :更新现有项目。
- SecItemCopyMatching :检索项目。
- SecItemDelete :从钥匙串中删除项目。

2.1 用户备份中的钥匙串

用户对设备进行完整备份时,有两种与安全相关的选项:
- 未加密备份 :只能恢复到接收备份的同一设备。
- 加密备份 :用户可选择密码短语对备份数据进行加密,可恢复到任何设备(标记为ThisDeviceOnly的项目除外),并备份钥匙串的全部内容。

如果不想让钥匙串项目存储在备份中,可以使用钥匙串的数据保护属性。

2.2 钥匙串保护属性

钥匙串保护属性指定了钥匙串数据何时可以存储在内存中并被操作系统或应用程序请求。添加密码或个人数据等项目到钥匙串时,指定保护属性很重要,因为这明确说明了数据何时可用。不指定保护属性应被视为一个错误。

可以通过 SecItemAdd 方法在首次将项目存储到钥匙串时指定属性,需要传入 kSecAttrAccessible 的预定义值之一,主要有三种访问类型:
| 钥匙串保护属性 | 含义 |
| — | — |
| kSecAttrAccessibleAfterFirstUnlock | 设备启动后,直到用户首次输入密码,密钥不可访问。 |
| kSecAttrAccessibleAlways | 只要设备启动,密钥始终可访问。此属性在iOS 9中已弃用,因为它相比kSecAttrAccessibleAfterFirstUnlock没有实际优势。 |
| kSecAttrAccessibleAlwaysThisDeviceOnly | 密钥始终可访问,但不能移植到其他iOS设备。 |
| kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | 与上一个密钥相同,但此密钥仅保留在本设备上。 |
| kSecAttrAccessibleWhenUnlocked | 设备解锁后(即用户输入密码后),密钥可访问。 |
| kSecAttrAccessibleWhenUnlockedThisDeviceOnly | 与上一个密钥相同,但此密钥仅保留在本设备上(完整加密备份除外)。 |
| kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | 与上一个密钥相同,但此密钥仅对设置了密码的用户可用,如果密码被取消设置,将从设备中移除,且不会包含在任何备份中。 |

目前,默认属性是 kSecAttrAccessibleWhenUnlocked ,这是一个合理的限制性默认值。但由于苹果的公开文档对此默认属性的定义存在分歧,所以应在所有钥匙串项目上显式设置此属性。对于自己的代码,可考虑使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly ;检查第三方源代码时,确保使用了限制性保护属性。

2.3 基本钥匙串使用

钥匙串项目有几种类型,如下表所示:
| 项目类 | 含义 |
| — | — |
| kSecClassGenericPassword | 普通密码 |
| kSecClassInternetPassword | 专门用于互联网服务的密码 |
| kSecClassCertificate | 加密证书 |
| kSecClassKey | 加密密钥 |
| kSecClassIdentity | 由公共证书和私钥组成的密钥对 |

除非处理证书,否则 kSecClassGenericPassword 通常可用于大多数敏感数据。以下是一些常用方法的示例:

添加项目到钥匙串

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSData *passwordData = [@"mypassword" dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"Conglomco login" forKey:(__bridge id)kSecAttrLabel];
[dict setObject:@"This is your password for the Conglomco service." forKey:(__bridge id)kSecAttrDescription];
[dict setObject:@"dthiel" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"com.isecpartners.SampleKeychain" forKey:(__bridge id)kSecAttrService];
[dict setObject:passwordData forKey:(__bridge id)kSecValueData];
[dict setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
OSStatus error = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if (error == errSecSuccess) {
    NSLog(@"Yay");
}

更新钥匙串项目

NSString *newPassword = @"";
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"dthiel" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"com.isecpartners.SampleKeychain" forKey:(__bridge id)kSecAttrService];
NSDictionary *updatedAttribute = [NSDictionary dictionaryWithObject:[newPassword dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
OSStatus error = SecItemUpdate((__bridge CFDictionaryRef)dict, (__bridge CFDictionaryRef)updatedAttribute);

查询钥匙串

[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"dthiel" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"com.isecpartners.SampleKeychain" forKey:(__bridge id)kSecAttrService];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *result = nil;
OSStatus error = SecItemCopyMatching((__bridge CFDictionaryRef)dict, (void *)&result);
NSLog(@"Yay %@", result);

删除钥匙串项目

NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[searchDictionary setObject:@"dthiel" forKey:(__bridge id)kSecAttrAccount];
[searchDictionary setObject:@"com.isecpartners.SampleKeychain" forKey:(__bridge id)kSecAttrService];
OSStatus error = SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
2.4 钥匙串包装器

使用钥匙串时,可能需要编写一些包装函数以方便使用,因为大多数应用程序只使用钥匙串API功能的一部分。有许多第三方预编写的钥匙串包装器,例如Lockbox,它提供了一组用于存储字符串、日期、数组和集合的类方法。

使用Lockbox设置钥匙串项目

#import "Lockbox.h"
NSString *keyname = @"KeyForMyApp";
NSString *secret = @"secretstring";
[Lockbox setString:secret forKey:keyname accessibility:kSecAttrAccessibleWhenUnlocked];

使用Lockbox从钥匙串中检索字符串

NSString *result = [Lockbox stringForKey:secret];

无论选择或编写哪种包装器,都要确保它能够设置 kSecAttrAccessible 属性,因为许多可用的示例代码都忽略了这个功能。

2.5 共享钥匙串

iOS允许同一开发者的多个应用程序通过钥匙串访问组共享钥匙串数据。例如,在线市场的“买家”应用和“卖家”应用可以让用户在两个应用之间共享相同的用户名和密码。

要使用钥匙串访问组,应用程序必须共享相同的捆绑种子ID,这只能在创建新的App ID时指定。应用程序要利用访问组,需要创建一个Entitlements属性列表,其中包含一个名为 keychain-access-groups 的数组,每个共享钥匙串项目都有一个字符串条目。

2.6 iCloud同步

iOS 7引入了一种机制,允许钥匙串项目与iCloud同步,使用户可以在多个设备之间共享钥匙串项目。默认情况下,应用程序创建的钥匙串项目未启用此功能,但可以通过将 kSecAttrSynchronizable 设置为 true 来启用。

[query setObject:(id)kCFBooleanTrue forKey:(id)kSecAttrSynchronizable];

由于此项目现在可能在多个钥匙串之间同步,对项目的更新(包括删除)也会传播到所有其他位置。因此,要确保应用程序能够处理系统对钥匙串项目的移除或更改。同时,使用此选项时不能指定不兼容的 kSecAttrAccessible 属性,例如 kSecAttrAccessibleWhenUnlockedThisDeviceOnly 就不适用,因为 ThisDeviceOnly 指定该项目永远不应备份到iCloud、笔记本电脑或桌面,或任何其他同步提供商。

3. 数据保护API

作为额外的保护层,苹果引入了数据保护API(不要与微软的数据保护API混淆),它允许开发者指定文件解密密钥何时可用,从而控制对文件本身的访问,类似于钥匙串项目的 kSecAttrAccessible 属性。数据保护API使用用户的密码结合类密钥来加密每个受保护文件的特定密钥,并在文件不可访问时(即设备锁定时)在内存中丢弃类密钥。当启用PIN时,密码设置屏幕将显示数据保护已启用。

3.1 保护级别

开发者可以使用数据保护API请求几种保护级别,这些级别大致类似于为钥匙串项目设置的 kSecAttrAccessible 属性。下面来详细探讨这些级别:

保护级别 描述
CompleteUntilFirstUserAuthentication 这是iOS 5及更高版本的默认文件保护属性。除非明确指定其他属性,否则它将应用于所有适用的文件。其功能类似于FileProtectionComplete,但文件在用户首次重启设备并解锁后始终可用。如果有人在运行的设备上获得远程代码执行权限或存在沙盒绕过漏洞,此级别提供的保护有限,但它可以防止一些需要重启的攻击。
Complete 如果可以使用,这是最安全的文件保护类。完全保护确保在短暂延迟后,锁定设备会从内存中丢弃类密钥,使文件内容不可读。此保护级别通过NSFileManager的 NSFileProtectionComplete 属性和NSData对象的 NSDataWritingFileProtectionComplete 标志来表示。
CompleteUnlessOpen 此保护级别稍微复杂一些。使用NSFileManager时设置 NSFileProtectionCompleteUnlessOpen 标志,操作NSData存储时设置 NSDataWritingFileProtectionCompleteUnlessOpen 。它并非如名称所示,在文件被应用程序打开时禁用文件保护。实际上,它确保在设备锁定后,打开的文件仍可写入,并允许新文件写入磁盘。设备锁定时,任何具有此类别的现有文件除非事先已打开,否则无法打开。

以下是设置不同保护级别的代码示例:

设置NSData对象的Complete保护级别

NSData *data = [request responseData];
if (data) {
    NSError *error = nil;
    NSString *downloadFilePath = [NSString stringWithFormat:@"%@mydoc.pdf", NSTemporaryDirectory()];
    [data writeToFile:downloadFilePath options:NSDataWritingFileProtectionComplete error:&error];
}

使用NSFileManager设置Complete保护级别

NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *applicationDocumentsDirectory = [searchPaths lastObject];
NSString *filePath = [applicationDocumentsDirectory stringByAppendingPathComponent:@"mySensitivedata.txt"];
NSError *error = nil;
NSDictionary *attr = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
[[NSFileManager defaultManager] setAttributes:attr ofItemAtPath:filePath error:&error];

设置SQLite数据库的保护属性

NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:@"MyNewDB.sqlite"];
sqlite3_open_v2([databasePath UTF8String], &handle, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE_SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN,NULL);
3.2 CompleteUnlessOpen保护级别工作流程

CompleteUnlessOpen 保护级别的工作流程如下:

graph LR
    A[生成文件密钥fK] --> B[生成额外密钥对]
    B --> C[生成文件公钥fpubK和文件私钥fprvK]
    C --> D[计算共享密钥ss]
    D --> E[使用SHA-1哈希加密文件密钥]
    E --> F[存储加密文件密钥和文件公钥到文件元数据]
    F --> G[丢弃文件私钥]
    G --> H[关闭文件时擦除未加密文件密钥]

具体步骤如下:
1. 与所有文件一样,生成一个文件密钥 fK 来加密文件内容。
2. 生成一个额外的密钥对,以产生文件公钥 fpubK 和文件私钥 fprvK
3. 使用文件私钥 fprvK 和“受保护除非打开”类公钥 cpubK 计算共享密钥 ss
4. 对该共享密钥进行SHA - 1哈希计算,使用哈希结果加密文件密钥。
5. 将加密后的文件密钥存储在文件的元数据中,同时存储文件公钥。
6. 系统丢弃文件私钥。
7. 关闭文件时,从内存中擦除未加密的文件密钥。

综上所述,在iOS开发中,无论是处理替代XML库、使用钥匙串存储敏感数据,还是利用数据保护API保护文件,都需要仔细考虑安全问题。通过正确设置各种属性和使用相应的API,可以有效提高应用程序的数据安全性,保护用户的敏感信息不被非法获取。

**高校专业实习管理平台设计实现** 本设计项目旨在构建一个服务于高等院校专业实习环节的综合性管理平台。该系统采用当前主流的Web开发架构,基于Python编程语言,结合Django后端框架Vue.js前端框架进行开发,实现了前后端逻辑的分离。数据存储层选用广泛应用的MySQL关系型数据,确保了系统的稳定性和数据处理的效率。 平台设计了多角色协同工作的管理模型,具体包括系统管理员、院系负责人、指导教师、实习单位对接人以及参实习的学生。各角色依据权限访问不同的功能模块,共同构成完整的实习管理流程。核心功能模块涵盖:基础信息管理(如院系、专业、人员信息)、实习过程管理(包括实习公告发布、实习内容规划、实习申请安排)、双向反馈机制(单位评价学生反馈)、实习支持保障、以及贯穿始终的成绩评定综合成绩管理。 在技术实现层面,后端服务依托Django框架的高效性构建业务逻辑;前端界面则利用Vue.js的组件化特性LayUI的样式,致力于提供清晰、友好的用户交互体验。数据设计充分考虑了实习管理业务的实体关系数据一致性要求,并保留了未来功能扩展的灵活性。 整个系统遵循规范的软件开发流程,从需求分析、系统设计、编码实现到测试验证,均进行了多轮迭代优化,力求在功能完备性、系统性能及用户使用体验方面达到较高标准。 **核心术语**:实习管理平台;Django框架;MySQL数据;Vue.js前端;Python语言。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
在电磁散射雷达技术的研究中,涉及粗糙表面电磁特性模拟的核心概念包括统计参数化建模方法、不同电场矢量方向的极化模式、特定方向的能量反射现象、理想化波前模型以及具有随机起伏特征的界面。以下是对这些要点的系统阐述: 统计参数化建模是一种基于表面统计特征描述其不规则性的电磁散射计算方法,尤其适用于均方根高度较小的粗糙界面在微波至毫米波频段的散射特性分析。 水平极化垂直极化分别指电场矢量平行于地面和垂直于地面的振动状态。在雷达探测中,采用不同的极化模式有助于提升目标辨识度并抑制环境干扰。 当电磁波物体相互作用时,部分能量沿接近入射方向返回,这种现象称为反向散射。其在雷达系统的探测灵敏度目标特征分析中具有关键作用。 平面波是在均匀介质中传播的理想波型,其电场磁场分布保持一致的相位关系,常作为理论简化模型用于电磁问题的解析数值计算。 粗糙界面指具有随机起伏特征的表面,其不规则程度可通过均方根高度进行量化。这种结构特性会改变电磁波的传播路径能量分布,进而影响信号的接收处理。 相关压缩文件可能包含了实现上述建模方法的程序代码,通常采用数值计算语言编写,用于模拟不同极化状态下粗糙表面对平面波的反向散射响应。通过此类仿真,能够预测各类场景下的散射参数,为雷达系统设计遥感数据解译提供理论依据。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值