关于 cryptokit
面向 Swift 开发者的 Apple CryptoKit 加密指南,介绍常见密码操作的 API 用法、系统版本要求、安全隔区密钥及常见风险。
主要功能
- 01哈希与 HMAC
- 02带认证的对称加密
- 03公钥签名与密钥协商
- 04HPKE 与后量子 API
- 05安全隔区支持的密钥
- 06加密代码检查清单
适用场景
- 01计算数据哈希或消息认证码
- 02使用 AES-GCM 或 ChaChaPoly 加密
- 03签署消息或派生共享密钥
- 04检查 CryptoKit 代码中的常见风险
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill cryptokit安装后在 Claude Code 中输入/cryptokit确认已加载。
附带文件evals/evals.jsonreferences/cryptokit-patterns.md
详细介绍
CryptoKit
Apple CryptoKit 提供了原生 Swift API,用于执行加密操作: 哈希、消息认证、对称加密、公钥签名、 密钥协商、HPKE、抗量子密钥封装/签名,以及由 Secure Enclave 支持的密钥。大多数核心原语在 iOS 13+ 上可用;请检查 HPKE(iOS 17+)以及 SHA-3/后量子 API(iOS 26+)的可用性。 面向 Swift 6.3+ 的新加密原语代码,请优先使用 CryptoKit,而非 CommonCrypto 或原始 Security 框架 API。
目录
哈希
在 iOS 13+ 上使用 SHA256/SHA384/SHA512;SHA3_256/SHA3_384/SHA3_512 需要 iOS 26+。它们都遵循 HashFunction。
一次性哈希
import CryptoKit
let data = Data("Hello, world!".utf8)
let digest = SHA256.hash(data: data)
let hex = digest.compactMap { String(format: "%02x", $0) }.joined()
SHA-3 可用性
除非最低部署目标为 iOS 26+,否则只能在检查可用性后使用 SHA-3:
if #available(iOS 26.0, *) {
let digest = SHA3_256.hash(data: data)
}
增量哈希
对于大型数据或流式输入,请使用增量方式进行哈希:
var hasher = SHA256()
hasher.update(data: chunk1)
hasher.update(data: chunk2)
let digest = hasher.finalize()
摘要比较
直接比较 CryptoKit 摘要值。安全敏感的相等性检查中,不要将摘要转换为字符串或数组。
let expected = SHA256.hash(data: reference)
let actual = SHA256.hash(data: received)
if expected == actual {
// Data integrity verified
}
HMAC
当协议要求使用密钥进行消息认证时,请使用 HMAC;验证时使用 isValidAuthenticationCode,而不要自行比较序列化后的值。
计算认证码
let key = SymmetricKey(size: .bits256)
let data = Data("message".utf8)
let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
验证认证码
let isValid = HMAC<SHA256>.isValidAuthenticationCode(
mac, authenticating: data, using: key
)
增量 HMAC
var hmac = HMAC<SHA256>(key: key)
hmac.update(data: chunk1)
hmac.update(data: chunk2)
let mac = hmac.finalize()
对称加密
CryptoKit 提供两种认证加密算法:AES-GCM 和 ChaChaPoly。两者都会生成一个密封盒,其中包含随机数、密文 和认证标签。
AES-GCM
对称加密的默认选择。在 Apple 芯片上可由硬件加速。
let key = SymmetricKey(size: .bits256)
let plaintext = Data("Secret message".utf8)
// Encrypt
let sealedBox = try AES.GCM.seal(plaintext, using: key)
let ciphertext = sealedBox.combined! // nonce + ciphertext + tag
// Decrypt
let box = try AES.GCM.SealedBox(combined: ciphertext)
let decrypted = try AES.GCM.open(box, using: key)
ChaChaPoly
当 AES 硬件加速不可用,或需要与要求 ChaCha20-Poly1305 的协议互操作时,请使用 ChaChaPoly(例如 TLS、 WireGuard)。
let sealedBox = try ChaChaPoly.seal(plaintext, using: key)
let combined = sealedBox.combined // Always non-optional for ChaChaPoly
let box = try ChaChaPoly.SealedBox(combined: combined)
let decrypted = try ChaChaPoly.open(box, using: key)
认证数据
这两种算法都支持附加认证数据(AAD)。AAD 会经过认证,但不会加密——适用于必须保持明文但需要防篡改的元数据。
let header = Data("v1".utf8)
let sealedBox = try AES.GCM.seal(
plaintext, using: key, authenticating: header
)
let decrypted = try AES.GCM.open(
sealedBox, using: key, authenticating: header
)
对于 AES-256-GCM 或 ChaChaPoly,默认使用 .bits256 作为 SymmetricKey 的大小。若要从现有数据创建密钥:
let key = SymmetricKey(data: existingKeyData)
公钥签名
CryptoKit 支持使用 NIST 曲线进行 ECDSA 签名,以及通过 Curve25519 实现的 Ed25519。
NIST 曲线:P256、P384、P521
let signingKey = P256.Signing.PrivateKey()
let publicKey = signingKey.publicKey
// Sign
let signature = try signingKey.signature(for: data)
// Verify
let isValid = publicKey.isValidSignature(signature, for: data)
P384 和 P521 使用相同的 API——只需替换曲线名称。
NIST 密钥支持 DER、PEM、X9.63 和原始格式。序列化示例请参阅 references/cryptokit-patterns.md。
Curve25519/Ed25519
let signingKey = Curve25519.Signing.PrivateKey()
let publicKey = signingKey.publicKey
// Sign
let signature = try signingKey.signature(for: data)
// Verify
let isValid = publicKey.isValidSignature(signature, for: data)
Curve25519 密钥仅使用 rawRepresentation(不支持 DER/PEM/X9.63)。
选择曲线
| 曲线 | 签名方案 | 密钥大小 | 典型用途 |
|---|---|---|---|
| P256 | ECDSA | 256-bit | 通用;支持 Secure Enclave |
| P384 | ECDSA | 384-bit | 更高的安全要求 |
| P521 | ECDSA | 521-bit | 最高 NIST 安全级别 |
| Curve25519 | Ed25519 | 256-bit | 速度快;API 简单;不支持 Secure Enclave |
默认使用 P256。与基于 Ed25519 的协议互操作时使用 Curve25519。
密钥协商
密钥协商允许双方使用各自的公钥/私钥对通过 ECDH 派生共享对称密钥。
使用 P256 的 ECDH
// Alice
let aliceKey = P256.KeyAgreement.PrivateKey()
// Bob
let bobKey = P256.KeyAgreement.PrivateKey()
// Alice computes shared secret
let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(
with: bobKey.publicKey
)
// Derive a symmetric key using HKDF
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: Data("salt".utf8),
sharedInfo: Data("my-app-v1".utf8),
outputByteCount: 32
)
Bob 使用自己的私钥和 Alice 的公钥计算出相同的 sharedSecret。双方会派生出相同的 symmetricKey。
使用 Curve25519 的 ECDH
let aliceKey = Curve25519.KeyAgreement.PrivateKey()
let bobKey = Curve25519.KeyAgreement.PrivateKey()
let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(
with: bobKey.publicKey
)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: Data(),
sharedInfo: Data("context".utf8),
outputByteCount: 32
)
密钥派生函数
SharedSecret 不能直接用作 SymmetricKey。始终使用以下方法之一派生密钥:
| 方法 | 标准 | 用途 |
|---|---|---|
hkdfDerivedSymmetricKey | HKDF (RFC 5869) | 推荐的默认选项 |
x963DerivedSymmetricKey | ANSI X9.63 | 与采用 X9.63 的系统互操作 |
始终提供非空的 sharedInfo 字符串,将派生密钥绑定到特定的协议上下文。
HPKE
iOS 17+ 支持用于公钥加密工作流的 HPKE。加密给定接收方公钥的内容时,优先使用 HPKE,而不是手动拼装 ECDH + HKDF + AEAD 协议。
let info = Data("my-protocol-v1".utf8)
let recipientKey = Curve25519.KeyAgreement.PrivateKey()
var sender = try HPKE.Sender(
recipientKey: recipientKey.publicKey,
ciphersuite: .Curve25519_SHA256_ChachaPoly,
info: info
)
let encapsulatedKey = sender.encapsulatedKey
let ciphertext = try sender.seal(
plaintext,
authenticating: Data("metadata".utf8)
)
var recipient = try HPKE.Recipient(
privateKey: recipientKey,
ciphersuite: .Curve25519_SHA256_ChachaPoly,
info: info,
encapsulatedKey: encapsulatedKey
)
HPKE.Sender 和 HPKE.Recipient 都是有状态的;请将它们设为 var,将 encapsulatedKey 与密文一同发送,并按密封消息的相同顺序打开消息。有关密码套件选择和抗后量子 HPKE,请参阅 references/cryptokit-patterns.md。
后量子 CryptoKit
iOS 26+ 增加了量子安全 API:
- 密钥封装:
MLKEM768、MLKEM1024 - 混合 HPKE:
XWingMLKEM768X25519,搭配.XWingMLKEM768X25519_SHA256_AES_GCM_256 - 数字签名:
MLDSA65、MLDSA87 - Secure Enclave 变体:
SecureEnclave.MLKEM768、SecureEnclave.MLKEM1024、SecureEnclave.MLDSA65、SecureEnclave.MLDSA87
当需要兼顾经典安全性和抗量子安全性时,请使用混合机制进行迁移。请注意,与 P256 或 Curve25519 相比,公钥、密文和签名都会大得多。
Secure Enclave
Secure Enclave 提供硬件支持的密钥存储。私钥永远不会离开硬件。对于经典椭圆曲线 CryptoKit,Secure Enclave 支持 P256 签名和密钥协商。在 iOS 26+ 的受支持硬件上,CryptoKit 还提供 Secure Enclave ML-KEM 密钥封装类型和 ML-DSA 签名类型。
检查可用性
guard SecureEnclave.isAvailable else {
// Fall back to software keys
return
}
创建 Secure Enclave 签名密钥
let privateKey = try SecureEnclave.P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey // Standard P256.Signing.PublicKey
let signature = try privateKey.signature(for: data)
let isValid = publicKey.isValidSignature(signature, for: data)
访问控制
如果密钥需要通过生物识别或密码保护使用,请将 SecAccessControl 与 .privateKeyUsage 配合使用。详细的 Keychain 策略决策请留在 swift-security 领域内处理。
持久化 Secure Enclave 密钥
dataRepresentation 是一个加密数据块,只有同一设备的 Secure Enclave 才能恢复。请将其存储在 Keychain 中。
// Export
let blob = privateKey.dataRepresentation
// Restore
let restored = try SecureEnclave.P256.Signing.PrivateKey(
dataRepresentation: blob
)
Secure Enclave 密钥协商
let seKey = try SecureEnclave.P256.KeyAgreement.PrivateKey()
let peerPublicKey: P256.KeyAgreement.PublicKey = // from peer
let sharedSecret = try seKey.sharedSecretFromKeyAgreement(
with: peerPublicKey
)
常见错误
1. 直接将共享密钥用作密钥
// DON'T
let badKey = sharedSecret.withUnsafeBytes { bytes in
SymmetricKey(data: Data(bytes))
}
// DO -- derive with HKDF
let goodKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: salt,
sharedInfo: info,
outputByteCount: 32
)
2. 重用随机数
// DON'T -- hardcoded nonce
let nonce = try AES.GCM.Nonce(data: Data(repeating: 0, count: 12))
let box = try AES.GCM.seal(data, using: key, nonce: nonce)
// DO -- let CryptoKit generate a random nonce (default behavior)
let box = try AES.GCM.seal(data, using: key)
3. 忽略认证标签验证
// DON'T -- manually strip tag and decrypt
// DO -- always use AES.GCM.open() or ChaChaPoly.open()
// which verifies the tag automatically
4. 将不安全的哈希用于安全用途
// DON'T -- MD5/SHA1 for integrity or security
import CryptoKit
let bad = Insecure.MD5.hash(data: data)
// DO -- use SHA256 or stronger
let good = SHA256.hash(data: data)
Insecure.MD5 和 Insecure.SHA1 仅用于兼容旧版场景(校验和验证、协议互操作)。切勿将它们用于新的安全敏感操作。
5. 将对称密钥存储在 UserDefaults 中
// DON'T
UserDefaults.standard.set(rawKeyData, forKey: "encryptionKey")
// DO -- store in Keychain
// See references/cryptokit-patterns.md for Keychain storage patterns
6. 不检查 Secure Enclave 的可用性
// DON'T -- crash on simulator or unsupported hardware
let key = try SecureEnclave.P256.Signing.PrivateKey()
// DO
guard SecureEnclave.isAvailable else { /* fallback */ }
let key = try SecureEnclave.P256.Signing.PrivateKey()
审查清单
- 使用 CryptoKit,而非 CommonCrypto 或原始 Security 框架
- 哈希使用 SHA256 或更高版本;不将 MD5/SHA1 用于安全目的
- HMAC 验证使用
isValidAuthenticationCode(恒定时间) - 对称加密使用 AES-GCM 或 ChaChaPoly;密钥为 256 位
- 随机数为随机生成(默认方式)——不硬编码或重用
- 元数据需要完整性保护时使用经过认证的数据(AAD)
- 使用 HKDF 派生 SharedSecret,不直接使用它
-
sharedInfo参数非空,且与上下文相关 - 在 iOS 17+ 上,为接收方公钥加密使用 HPKE,而非自定义 ECDH+HKDF+AEAD
- SHA-3 和后量子 API 通过 iOS 26+ 可用性检查进行保护
- 使用前检查 Secure Enclave 的可用性
- 将 Secure Enclave 密钥的
dataRepresentation存储在 Keychain 中 - 不记录、打印或不必要地序列化私钥
- 将对称密钥存储在 Keychain 中,而非 UserDefaults 或文件中
- 考虑加密出口合规性(
ITSAppUsesNonExemptEncryption)
参考资料
- 扩展模式(密钥序列化、Insecure 模块、Keychain 集成、AES 密钥封装、HPKE):references/cryptokit-patterns.md
- Apple 文档:CryptoKit
- Apple 文档:HPKE
- Apple 文档:量子安全工作流
- Apple 示例:执行常见加密操作
- Apple 示例:在 Keychain 中存储 CryptoKit 密钥