关于 axiom-cryptokit
面向 iOS 开发者的 CryptoKit 实践指南,涵盖加密、签名、密钥协商和 Secure Enclave 管理,也介绍后量子算法及与服务端 Swift Crypto 的互操作。
主要功能
- 01使用 AES-GCM 加密与解密
- 02创建和验证数字签名
- 03通过 ECDH 与 HPKE 建立密钥
- 04管理 Secure Enclave 硬件密钥
- 05介绍 ML-KEM 和 ML-DSA
- 06处理跨平台签名格式互操作
适用场景
- 01保护静态或传输中的应用数据
- 02为请求或载荷签名并验证完整性
- 03为端到端加密协商密钥
- 04将 CommonCrypto 代码迁移至 CryptoKit
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-cryptokit安装后在 Claude Code 中输入/axiom-cryptokit确认已加载。
详细介绍
CryptoKit
为 iOS 应用提供经过身份验证的加密、数字签名、密钥协商、安全隔区密钥管理和抗量子密码学。
何时使用此技能
适用于以下需求:
- ☑ 加密静态或传输中的数据,提供 TLS/Data Protection 之外的保护
- ☑ 为载荷签名以验证完整性(收据、令牌、API 请求)
- ☑ 生成或管理加密密钥(包括安全隔区硬件密钥)
- ☑ 从 CommonCrypto C API 迁移到 CryptoKit
- ☑ 实现密钥协商(ECDH),用于端到端加密
- ☑ 使用 HPKE 实现现代非对称加密
- ☑ 采用抗量子算法(ML-KEM、ML-DSA),为后量子时代做好准备
- ☑ 与服务端 Swift Crypto 或非 Apple 平台互操作
示例提示词
“如何使用 AES-GCM 加密用户数据?” “如何为载荷签名并在我的服务端验证?” “如何使用安全隔区保护签名密钥?” “我在使用 CommonCrypto,应该迁移到 CryptoKit 吗?” “如何通过 ECDH 密钥协商实现端到端加密?” “如何让我的应用具备抗量子能力?” “我的服务端无法验证来自 iOS 应用的签名” “P256 和 Curve25519 有什么区别?”
危险信号
以下迹象表明,你让事情变得比必要的更复杂或更不安全:
- ❌ 在 CryptoKit 已可用时仍使用 CommonCrypto C API——缓冲区管理令人头疼,没有经过身份验证的加密,还要手动处理 IV。CryptoKit 只需一次调用,就能提供内存安全、经过身份验证的加密。每个调用点的迁移只需几分钟。
- ❌ 使用未经身份验证的 AES-CBC——密文可被篡改。填充预言攻击可让攻击者无需密钥,就能一次解密一个字节。AES-GCM(CryptoKit 的默认选项)提供经过身份验证的加密,可自动检测篡改。
- ❌ 自行设计加密协议——会引入计时攻击、填充预言和 nonce 重用等风险。除非你是专业密码学家,否则请使用 CryptoKit 的高级 API。Apple 的安全团队设计这些 API,正是为了防止此类错误。
- ❌ 忽视用于签名密钥的安全隔区——认为“太复杂”是错误的。SE API 与软件 P256 几乎相同。SE 密钥可在越狱后继续受到保护。如果密钥用于保护资金、健康数据或身份信息,那么 SE 就是正确选择,而且无需额外付出。
- ❌ 在使用 AES-256 不增加成本的情况下仍使用 AES-128——Grover 算法会使量子计算机对称密钥的安全强度减半。AES-128 会降至 64 位安全强度;AES-256 则降至 128 位,仍然安全。性能差异可以忽略不计。
- ❌ 对加密操作使用强制解包——
AES.GCM.open和签名验证失败时会抛出错误。强制解包会掩盖身份验证失败,并将安全错误变成崩溃。始终使用try/catch。 - ❌ 在 UserDefaults 中存储原始私钥——设备备份可读取这些密钥,越狱设备也能访问,并且它们会以明文形式显示在应用的 plist 中。软件密钥请使用 Keychain,硬件绑定密钥请使用安全隔区。
你真的需要 CryptoKit 吗?
digraph need_cryptokit {
"What are you\nprotecting?" [shape=diamond];
"Data in transit\nvia HTTPS?" [shape=diamond];
"Data at rest\non device?" [shape=diamond];
"Credentials or\ntokens?" [shape=diamond];
"CloudKit or\niCloud?" [shape=diamond];
"Custom crypto\nneeded?" [shape=diamond];
"Use URLSession + TLS\n(system handles crypto)" [shape=box];
"Use Data Protection\n(.completeFileProtection)" [shape=box];
"Use Keychain\n(axiom-keychain skill)" [shape=box];
"Use CloudKit encryption\n(encryptedValues)" [shape=box];
"YES — Use CryptoKit" [shape=box, style=bold];
"What are you\nprotecting?" -> "Data in transit\nvia HTTPS?" [label="network"];
"What are you\nprotecting?" -> "Data at rest\non device?" [label="storage"];
"What are you\nprotecting?" -> "Credentials or\ntokens?" [label="secrets"];
"What are you\nprotecting?" -> "CloudKit or\niCloud?" [label="sync"];
"What are you\nprotecting?" -> "Custom crypto\nneeded?" [label="signatures, key exchange,\nE2E encryption"];
"Data in transit\nvia HTTPS?" -> "Use URLSession + TLS\n(system handles crypto)" [label="standard HTTPS"];
"Data in transit\nvia HTTPS?" -> "Custom crypto\nneeded?" [label="need E2E beyond TLS"];
"Data at rest\non device?" -> "Use Data Protection\n(.completeFileProtection)" [label="file-level"];
"Data at rest\non device?" -> "Custom crypto\nneeded?" [label="field-level encryption"];
"Credentials or\ntokens?" -> "Use Keychain\n(axiom-keychain skill)" [label="yes"];
"CloudKit or\niCloud?" -> "Use CloudKit encryption\n(encryptedValues)" [label="yes"];
"Custom crypto\nneeded?" -> "YES — Use CryptoKit" [label="yes"];
}
摘自 WWDC 2019-709:“我们强烈建议你尽可能依赖更高级别的系统框架。”当系统框架无法满足你的使用场景时,才需要 CryptoKit——例如自定义签名、字段级加密、密钥协商,以及与非 Apple 系统互操作。
安全隔区密钥管理
安全隔区是内置于 Apple 设备中的硬件安全模块。在 SE 中生成的密钥永远不会离开硬件,甚至 Apple 也无法提取这些密钥。
何时使用安全隔区
| 场景 | 使用 SE? | 原因 |
|---|---|---|
| 为 API 请求签名 | 是 | 无法从设备中提取密钥 |
| 受生物识别保护的解密 | 是 | SE 会将密钥与 Face ID/Touch ID 绑定 |
| 端到端加密密钥 | 是(密钥协商) | 私钥绑定在硬件上 |
| 为另一台设备加密数据 | 否 | 密钥必须可导出 |
| 服务端共享的对称密钥 | 否 | SE 仅支持非对称算法(P256、ML-KEM、ML-DSA) |
| 跨设备同步 | 否 | SE 密钥绑定到设备 |
通过生物识别门控生成 SE 密钥
import CryptoKit
import LocalAuthentication
guard SecureEnclave.isAvailable else {
let softwareKey = P256.Signing.PrivateKey()
return
}
let context = LAContext()
context.localizedReason = "Authenticate to sign transaction"
guard let accessControl = SecAccessControlCreateWithFlags(
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet], nil
) else {
throw CryptoKitError.underlyingCoreCryptoError(error: 0)
}
let seKey = try SecureEnclave.P256.Signing.PrivateKey(
compactRepresentable: false,
accessControl: accessControl,
authenticationContext: .init(context)
)
let signature = try seKey.signature(for: data)
let publicKeyDER = seKey.publicKey.derRepresentation
SE 密钥生命周期
seKey.dataRepresentation 是包装后的 blob,不是原始密钥材料。它包含一个加密引用,只有这台特定的 Secure Enclave 硬件才能解包。将其导出到其他设备会得到无法使用的 blob。
- 会保留:应用更新、设备重启
- 不会保留:重新安装应用、恢复设备、抹掉设备、迁移到新设备
- 持久化:将
dataRepresentation存储在 Keychain 中。通过以下方式恢复:
let restoredKey = try SecureEnclave.P256.Signing.PrivateKey(
dataRepresentation: savedKeyData, authenticationContext: .init(context)
)
SE 限制
- 经典密码学:仅支持 P256——不支持 Curve25519、P384/P521。一条曲线,两种用途:
SecureEnclave.P256.Signing和SecureEnclave.P256.KeyAgreement - 后量子(iOS 26+):
SecureEnclave.MLKEM768、SecureEnclave.MLKEM1024、SecureEnclave.MLDSA65、SecureEnclave.MLDSA87 - 仅支持签名和密钥协商——不支持直接加密
- 模拟器不支持 SE——
SecureEnclave.isAvailable返回 false。测试时务必提供软件回退方案。
常见工作流
哈希数据完整性
import CryptoKit
let data = "Hello, world".data(using: .utf8)!
let digest = SHA256.hash(data: data)
guard digest == SHA256.hash(data: data) else { /* tampering detected */ }
可用算法:SHA256、SHA384、SHA512。仅在需要兼容旧协议时使用 Insecure.MD5/Insecure.SHA1,绝不可用于安全用途。
HMAC 消息认证
let key = SymmetricKey(size: .bits256)
let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
let isValid = HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: data, using: key)
内置恒定时间比较,可安全抵御时序攻击。双方共享对称密钥时使用 HMAC。
AES-GCM 加密/解密
let key = SymmetricKey(size: .bits256)
let plaintext = "Secret message".data(using: .utf8)!
let sealedBox = try AES.GCM.seal(plaintext, using: key)
let combined = sealedBox.combined!
let restoredBox = try AES.GCM.SealedBox(combined: combined)
let decrypted = try AES.GCM.open(restoredBox, using: key)
认证加密——篡改会触发 CryptoKitError.authenticationFailure。无需单独执行 HMAC 步骤。Nonce 会自动生成并前置到 combined 中。
ECDSA 签名/验证
let privateKey = P256.Signing.PrivateKey()
let signature = try privateKey.signature(for: data)
let isValid = privateKey.publicKey.isValidSignature(signature, for: data)
曲线:P256(secp256r1,最常见)、P384、P521、Curve25519(Ed25519,速度最快)。与服务器互操作时使用 P256。双方均由你控制时使用 Curve25519。
ECDH 密钥协商
let alicePrivate = P256.KeyAgreement.PrivateKey()
let bobPrivate = P256.KeyAgreement.PrivateKey()
let sharedSecret = try alicePrivate.sharedSecretFromKeyAgreement(
with: bobPrivate.publicKey
)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: "my-app-v1".data(using: .utf8)!,
sharedInfo: Data(),
outputByteCount: 32
)
切勿直接使用原始共享密钥——务必通过 HKDF 派生对称密钥。原始密钥包含有偏的位,会削弱加密安全性。
HPKE 端到端加密
HPKE(混合公钥加密)将密钥协商和对称加密合并为一步。对于新协议,优先使用 HPKE,而非手动组合 ECDH + AES-GCM。经典密码套件(P256、Curve25519)在 iOS 17+ 上可用。量子安全的 XWing 密码套件以及 ML-KEM/ML-DSA 需要 iOS 26+。
let recipientPrivate = P256.KeyAgreement.PrivateKey()
var sender = try HPKE.Sender(
recipientKey: recipientPrivate.publicKey,
ciphersuite: .P256_SHA256_AES_GCM_256,
info: "my-app-message-v1".data(using: .utf8)!
)
let ciphertext = try sender.seal(plaintext)
var recipient = try HPKE.Recipient(
privateKey: recipientPrivate,
ciphersuite: .P256_SHA256_AES_GCM_256,
info: "my-app-message-v1".data(using: .utf8)!,
encapsulatedKey: sender.encapsulatedKey
)
let decrypted = try recipient.open(ciphertext)
量子安全迁移
根据 WWDC 2025-314,“现在收集、日后解密”并非理论上的威胁。民族国家级别的攻击者正在记录今天的加密流量,以便将来使用量子计算机解密。敏感期限超过 10 年的数据现在就需要后量子保护。
迁移优先级
- 量子安全 TLS(自动) — iOS 26 的 URLSession 和 Network.framework 默认使用量子安全 TLS。无需更改代码。iMessage 从 iOS 17.4 起就已使用 PQ3。
- 自定义协议加密 — 如果应用使用手动 ECDH + AES 或自定义密钥交换,这些通道不会自动升级。请替换为后量子 HPKE。
- 升级对称密钥 — 将 AES-128 升级到 AES-256。Grover 算法会将对称密钥强度减半,因此 256 位密钥可提供 128 位后量子安全性。
后量子 HPKE(iOS 26+)
使用相同的 HPKE API,但采用不同的密码套件。将 ML-KEM(抗量子)与 X25519(经典密码学)结合,实现混合安全性——这是 Apple 的建议:
let recipientPrivate = XWingMLKEM768X25519.PrivateKey()
var sender = try HPKE.Sender(
recipientKey: recipientPrivate.publicKey,
ciphersuite: .XWingMLKEM768X25519_SHA256_AES_GCM_256,
info: "my-app-pq-v1".data(using: .utf8)!
)
let ciphertext = try sender.seal(plaintext)
var recipient = try HPKE.Recipient(
privateKey: recipientPrivate,
ciphersuite: .XWingMLKEM768X25519_SHA256_AES_GCM_256,
info: "my-app-pq-v1".data(using: .utf8)!,
encapsulatedKey: sender.encapsulatedKey
)
let decrypted = try recipient.open(ciphertext)
混合构造(经典密码学 + 后量子密码学)可确保即使其中一种算法被破解,安全性仍然得以维持。XWing 构造将 ML-KEM768 与 X25519 配对,因此仅发生经典密码学破解或仅发生量子破解,都无法危及密钥交换。
直接使用 ML-KEM 和 ML-DSA(iOS 26+)
let kemPrivate = try MLKEM768.PrivateKey()
let (sharedSecret, encapsulation) = try kemPrivate.publicKey.encapsulate()
let derivedSecret = try kemPrivate.decapsulate(encapsulation)
let dsaPrivate = try MLDSA65.PrivateKey()
let signature = try dsaPrivate.signature(for: data)
let isValid = dsaPrivate.publicKey.isValidSignature(signature, for: data)
构建自定义协议时,使用 ML-KEM 进行密钥封装。当 P256/Ed25519 无法抵御量子分析时,使用 ML-DSA 进行签名。对于大多数应用,优先使用带混合密码套件的 HPKE,而非直接使用原始 ML-KEM。
跨平台注意事项
Swift Crypto 一致性
在 Linux/服务器上:import Crypto(apple/swift-crypto)提供相同的 API,但不包括 Secure Enclave 和 Keychain。对于共享代码:
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
#endif
签名编码互操作
CryptoKit 的 ECDSA 签名使用原始格式(r || s 拼接,P256 为 64 字节)。大多数非 Apple 平台(OpenSSL、Java、.NET)都需要DER 格式(ASN.1 编码,P256 为可变长度的 70–72 字节)。
let signature = try privateKey.signature(for: data)
let raw = signature.rawRepresentation
let der = signature.derRepresentation
接收来自服务器的签名时:
let fromDER = try P256.Signing.ECDSASignature(derRepresentation: serverDER)
let fromRaw = try P256.Signing.ECDSASignature(rawRepresentation: serverRaw)
用于服务器验证的密钥导出
let publicKey = privateKey.publicKey
publicKey.derRepresentation
publicKey.pemRepresentation
publicKey.x963Representation
大多数平台使用 DER,配置文件和 REST API 使用 PEM,紧凑型 JavaScript 互操作使用 X9.63。
secp256r1 与 secp256k1
CryptoKit 使用 secp256r1(P-256、prime256v1)——这是 NIST 标准曲线,用于 TLS、政府系统和企业软件。
Bitcoin 和 Ethereum 使用 secp256k1(Koblitz 曲线)——这是完全不同的曲线。二者无法互操作。无法使用 secp256k1 验证器验证 P-256 签名。如果区块链互操作需要 secp256k1,请使用专用库(libsecp256k1 封装),而不要使用 CryptoKit。
SE 密钥与跨平台
安全隔区密钥与设备绑定。dataRepresentation 是一个包装后的数据块,只有生成它的硬件才能解包。这意味着 SE 密钥在设计上支持密码销毁——销毁设备或重新安装应用都会使所有由 SE 加密的数据永久无法恢复。请据此规划密钥生命周期。如果数据必须在设备丢失后仍可恢复,请保存恢复途径(由服务器托管的备份密钥、多设备密钥分发)。
反驳借口
| 借口 | 失败原因 | 时间成本 |
|---|---|---|
| “CommonCrypto 用得好好的,没必要迁移” | 缓冲区溢出、缺少认证加密、需要手动管理 IV。缓冲区大小稍有差错,就会导致数据悄无声息地损坏或产生安全漏洞。 | 花费 2–4 小时调试 CryptoKit 从设计上就能避免的隐蔽加密故障 |
| “我以后再为 AES-CBC 添加认证” | 没有认证,密文就可以被篡改。攻击者可以修改数据而不被发现。“以后”意味着漏洞发布之后。 | 在生产环境发现数据被篡改后,花费 4–8 小时进行事件响应 |
| “对于我的用例,nonce 不必随机” | AES-GCM 中重复使用 nonce 会通过密文的异或结果泄露明文。GCM 中不存在固定 nonce 安全的用例。 | 灾难性后果——恢复所有使用重复 nonce 加密的消息的完整明文 |
| “对这个场景来说,安全隔区太大材小用” | 软件密钥可以从越狱设备和备份中提取,SE 密钥则不行。如果密钥用于保护资金、健康数据或身份信息,SE 绝非大材小用。 | 额外开发时间为 0(API 与软件 P256 几乎相同) |
| “量子计算还要几十年才会出现” | 先收集、后解密意味着,今天记录的数据将在量子计算机问世后被解密。Apple 已在 iOS 26 中提供量子安全 TLS。 | 使用 iOS 26 默认设置,耗时为 0;迁移自定义协议需 1–2 小时。 |
| “我直接用自己的加密方案就行” | 专业密码学家会花数年设计协议。时序侧信道、填充预言和 nonce 误用,不经专家审查就无法察觉。 | 安全审计和修复需数周至数月 |
| “DER 和原始格式没区别,反正是同一个签名” | 编码错误会导致服务器验证失败。数学计算正确,但编码错误。 | 调试互操作问题需 2–4 小时,而一次 .derRepresentation 调用就能避免这种问题 |
压力场景
场景 1:“随便用什么加密,我们今天就要发布”
背景:功能截止日期将至。开发者需要在持久化用户数据之前对其进行加密。
压力:“别在密码学上想太多。AES-CBC、CommonCrypto,能在今天下班前搞定就行。”
现实:未进行身份验证的 AES-CBC 易受填充预言攻击。CommonCrypto 需要手动管理缓冲区,任何一个尺寸出错都会造成无声的数据损坏。从 CCCrypt 迁移到 AES.GCM.seal 只需 5 到 10 行代码——比 CommonCrypto 版本的代码还少——却能消除一整类漏洞。
正确做法:将 CCCrypt(kCCEncrypt, kCCAlgorithmAES, ...) 替换为 AES.GCM.seal(plaintext, using: key)。通过 SymmetricKey(data:) 映射现有密钥材料。如果要迁移已有的 CBC 加密数据,请添加读取路径,用于解密旧格式,并在首次访问时重新加密。
回应模板:“通过 CryptoKit 使用 AES-GCM,代码实际上比 CommonCrypto 更少,还能免费提供身份验证加密。CBC 代码存在一类 GCM 可以消除的漏洞。切换只需 10 分钟,而不是几小时。”
场景 2:“我们需要跨平台,跳过 Secure Enclave”
背景:正在构建一项必须在 iOS 和 Android 上运行的端到端加密功能。开发者提议将签名密钥存储在 Keychain(软件)中,以便与 Android 的软件密钥库保持一致。
压力:“Android 没有具备同等保障的 Secure Enclave。我们还是让方案简单一些,并在各平台保持一致吧。”
现实:Android 提供保障类似的硬件支持型 Keystore(StrongBox)。即便 Android 端使用软件密钥,也不意味着 iOS 端也应该如此。iOS 上可免费使用 SE 保护——其 API 与软件版 P256 几乎相同。iOS 使用 SE,Android 使用硬件 Keystore,硬件不可用时则回退到软件方案。
正确做法:使用 SecureEnclave.P256.Signing.PrivateKey,并回退到 P256.Signing.PrivateKey。公钥和签名格式完全相同——服务器不知道也不关心它们是由哪种方式生成的。
回应模板:“SE API 与软件版 P256 相同——没有额外复杂性。无论采用哪种方式,服务器验证的公钥格式都一样。支持该功能的设备可以获得硬件保护,不支持的设备也能平稳回退。两个平台都可以使用各自最好的可用硬件。”
场景 3:“抗量子是过早优化”
背景:正在设计一种新的端到端加密消息协议。开发者提议使用经典 ECDH + AES-GCM。
压力:“量子计算机至少还要十年才会出现。我们这是过度设计。”
现实:“现在收集、日后解密”意味着攻击者今天记录加密流量,等量子计算机问世后再将其解密。对于会话令牌等短期数据,经典密码学足够了。对于长期敏感的消息(健康记录、财务数据、私人通信),现在就有必要采用后量子保护。Apple 已经为 iMessage 推出 PQ3,并在 iOS 26 中提供抗量子的 TLS。
正确做法:使用 HPKE.Ciphersuite.XWingMLKEM768X25519_SHA256_AES_GCM_256 替代 .P256_SHA256_AES_GCM_256。代码只需更改一个密码套件常量——API 相同,复杂性相同,还能提供经典与抗量子相结合的保护。
回应模板:“代码改动真的只有一个密码套件常量。Apple 已经在 iMessage(PQ3)和 iOS 26 TLS 中采用了这种做法。只需改一行代码,就能消除数据在 10 年后仍然敏感时所面临的‘现在收集、日后解密’风险。”
检查清单
在发布任何自定义密码学实现前:
算法选择:
- 使用经过身份验证的加密(AES-GCM,而非 AES-CBC/ECB)
- 出于安全目的,哈希函数至少为 SHA256(而非 MD5/SHA1)
- 选择适合使用场景的曲线(P256 适用于互操作,Curve25519 适用于性能)
- 考虑用于长期敏感数据的后量子密码套件
密钥管理:
- 将私钥存储在 Secure Enclave 中(如果硬件可用,且密钥无需导出)
- 如果 SE 不可用,则回退到 Keychain,并设置适当的访问控制
- 不在 UserDefaults、文件中存储私钥,也不在源代码中硬编码私钥
- 为长期使用的密钥制定轮换策略
Nonce/IV 处理:
- 使用 CryptoKit 的自动 nonce 生成(不自定义生成)
- 不在多次加密操作中重复使用 nonce
- 不存储或硬编码 nonce
互操作(如果与非 Apple 平台通信):
- 签名编码符合服务器预期(DER 或原始格式)
- 双方就公钥格式达成一致(DER、PEM 或 X9.63)
- 曲线与服务器一致(secp256r1,而非 secp256k1)
- 使用实际服务器验证进行测试,而非仅进行本地往返测试
Secure Enclave(如果使用):
- 检查
SecureEnclave.isAvailable,并提供软件回退方案 - 将
dataRepresentation存储在 Keychain 中,以便持久化 - 访问控制标志与用户体验要求一致(每次使用时进行生物识别验证,或基于会话的验证)
- 记录密钥生命周期(重新安装或恢复后密钥不会保留)
资源
WWDC:2019-709、2025-314
文档:/cryptokit、/cryptokit/secureenclave、/security/certificate_key_and_trust_services
技能:axiom-cryptokit-ref、axiom-keychain