第一章:密码学基础与iOS安全概述
做iOS密码SDK,首先要明白两件事:密码学能干什么,iOS给我们提供了哪些安全基础。说白了,这块没搞懂,后面编译出来的SDK就跟纸糊的差不多。我刚开始接触iOS安全时,也踩过不少坑,今天把这些经验掰开揉碎讲给你。
1.1 密码学核心概念——你每天都在用,但未必真懂
密码学不是什么玄学。它就是你手机里那把看不见的锁。我举个例子:你用微信发一条消息,对方看到的是明文,中间任何人截获的是一堆乱码——这就是加密。
咱们做SDK,常用的密码学技术其实就这几种:
| 技术分类 | 典型算法 | 用途举例 | 我心里它的地位 |
|---|---|---|---|
| 对称加密 | AES-256, ChaCha20 | 本地数据加密、网络传输加密 | 主力,速度快 |
| 非对称加密 | RSA, ECDH, Ed25519 | 密钥协商、数字签名 | 安全但慢,只用在关键节点 |
| 哈希函数 | SHA-256, SHA-512 | 数据完整性校验、密码存储 | 不可逆,用来“核验”不是“保密” |
| 密钥派生 | PBKDF2, HKDF, Argon2 | 从密码生成加密密钥 | 防暴力破解的命根子 |
这里有个关键点:不要自己发明算法。永远使用标准库实现。我在项目里见过有人自己写了个“魔改AES”,结果安全审计直接被判死刑——连我们自己都不知道那算不算真AES。
核心原则:密码学里最难的不是算法实现,而是如何组合它们(比如“加密+MAC”到底谁先谁后?)。组合错了,整套方案等于没做。
1.2 iOS安全机制——苹果给你铺好的路
iOS不是裸奔的Linux。它从硬件到软件建了一整套安全墙。咱们做密码SDK,必须知道哪些墙可以靠,哪些墙不能碰。
1.2.1 硬件安全模块:Secure Enclave
从iPhone 5s开始,苹果给每台设备装了一个独立的协处理器——Secure Enclave。说白了就是一个专属安全芯片,有自己的ROM、RAM、真随机数发生器。它跟主CPU之间只通过一个隔离邮箱通信。我做过一个项目,需要生成一个永远不出芯片的私钥,就得用Secure Enclave。注意:它只支持特定的算法(如EC keys 256 bit,RSA 2048 bit),不支持AES。
1.2.2 数据保护:Data Protection API
苹果的Data Protection是基于文件级的加密。每个文件有独立的加密密钥,密钥又受设备锁屏密码保护。这个机制非常成熟,但有个大坑:如果你用NSFileManager直接写文件,默认可能只获得“直到设备解锁”级别的保护,重启后文件无法读取。我曾经写SDK时忘了设置completeUntilFirstUserAuthentication,结果用户重启后SDK启动报错,调试到崩溃才发现。
1.2.3 Keychain服务
Keychain是iOS上存储敏感数据的官方方案(密码、证书、私钥)。它会把数据加密后存到SQLite数据库里,该数据库的加密密钥受硬件保护。Keychain本身很稳定,但你得懂它的Access Group和Access Control。举个例子:你设置kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,那备份、迁移时这笔数据就没了——用户换手机怎么也恢复不了。我有个客户因为没有正确设置Accessibility,导致十多万用户的所有密钥在系统升级后全部丢失……那场面,唉。
避坑提醒:Keychain的kSecMatchLimit选项默认是kSecMatchLimitOne,如果你要查询多条记录,记得改成kSecMatchLimitAll。否则只会返回第一条,剩下的石沉大海——我曾在内部审计时发现SDK只存用到了第一条密钥,后面的全被忽略了,吓出一身冷汗。
1.2.4 应用沙箱与代码签名
每个iOS应用跑在独立的沙箱里,不能访问其他App的文件。但Keychain通过同一Team ID可以共享。这个特性对SDK很重要:如果你的密码SDK需要多个业务模块共享一个主密钥,可以用Keychain Access Group。但注意,一旦别人拿到你的Team ID,理论上也可以共享——所以权限要收得紧。
1.3 为什么要在SDK里封装密码学?
你可能想问:“苹果不是已经提供CommonCrypto和CryptoKit了吗?为什么还要自己编译SDK?”
说实话,我刚入行时也是一样的疑问。但做多了就发现三个理由:
- 统一接口——团队里有人用ObjC,有人用Swift,有人还要兼容C++。你提供一个统一的C/ObjC接口SDK,省去每个人学习不同API的功夫。
- 规避API歧义——CommonCrypto的一些老接口文档不全,不同iOS版本行为有细微差异。比如CCCryptorCreate在iOS 10上遇到无效密钥长度会返回错误,在iOS 13上却直接崩溃(我记得这是个已知bug)。你在SDK里封装好,上层调用就不怕踩坑。
- 性能优化和算法固化——有些业务需要高性能的GCM或ChaCha20,CommonCrypto不一定是最优的。你可以用libsodium或BoringSSL自己编译,把加速指令集(ARMv8.3的SHA扩展)踩满。
我的经验:如果项目需求只是简单的AES-CBC,直接用CryptoKit或CommonCrypto就好,别自己编译。只有当你需要跨平台、定制算法或极致性能时,才启动SDK编译。一句话:不要造轮子,除非轮子已经变形了。
1.4 本章知识结构一览
我把第一章的核心逻辑画成了一张图。你琢磨琢磨,密码SDK的根基就在这里。
1.5 一点私货——我过去犯过的三个错误
好,基础讲完了,说点我自己的血泪史,你记下来以后少走弯路。
- 第一个坑:信任用户输入当密钥——早期我做SDK时,让App直接传一段字符串给我当AES密钥。结果有人传了"123456"……后来加上了PBKDF2迭代,还把最小密钥熵检查做到SDK内部,再不给上层乱来。
- 第二个坑:忽视随机数质量——SecRandomCopyBytes确实比rand()强,但Secure Enclave的随机数发生器在设备刚启动时、熵池未填充状态会有短暂的低熵窗口。我在低版本iOS 11上遇到过重启后第一次调用返回的随机数有规律,后来加了“预搅拌”才解决。
- 第三个坑:Keychain同步问题——在真机上测试正常,但iCloud同步后,另一台设备的Keychain数据竟然拿不到正确的访问权限。排查了一天,原来是AccessGroup设置错了——我用了通配符,苹果在同步时认为不符合同一Team ID。