在 Swift 中使用 Apple CryptoKit 进行密码学原语操作。适合用于 SHA-2 或 SHA-3 杂凑计算、生成 HMAC、AES-GCM 或 ChaChaPoly 加密、使用 P256/P384/P521/Curve25519 或 ML-DSA 金钥进行签章、执行 ECDH、HPKE、ML-KEM 或 X-Wing 金钥交换、使用 Secure Enclave CryptoKit 金钥,或是将 CommonCrypto 代码迁移至 CryptoKit。
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。
Contents
杂凑
在 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 的摘要(Digest)值。进行安全性敏感的相等性检查时,切勿将摘要转换成字串或阵列。
let expected = SHA256.hash(data: reference)
let actual = SHA256.hash(data: received)
if expected == actual {
// 资料完整性已验证
}
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。两者都会产生一个密封盒(Sealed Box),内含 Nonce、密文与鉴别标籤。
AES-GCM
对称加密的预设首选。在 Apple 晶片上具备硬体加速。
let key = SymmetricKey(size: .bits256)
let plaintext = Data("Secret message".utf8)
// 加密
let sealedBox = try AES.GCM.seal(plaintext, using: key)
let ciphertext = sealedBox.combined! // nonce + 密文 + tag
// 解密
let box = try AES.GCM.SealedBox(combined: ciphertext)
let decrypted = try AES.GCM.open(box, using: key)
ChaChaPoly
在缺乏 AES 硬体加速,或者需要与强制使用 ChaCha20-Poly1305 的协定(例如 TLS、WireGuard)进行互操作时使用 ChaChaPoly。
let sealedBox = try ChaChaPoly.seal(plaintext, using: key)
let combined = sealedBox.combined // ChaChaPoly 永远为非 Optional
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 的 SymmetricKey 尺寸预设请使用 .bits256。若要从既有资料建立金钥:
let key = SymmetricKey(data: existingKeyData)
公开金钥签章
CryptoKit 支援使用 NIST 椭圆曲线的 ECDSA 签章,以及透过 Curve25519 实现的 Ed25519。
NIST 曲线:P256、P384、P521
let signingKey = P256.Signing.PrivateKey()
let publicKey = signingKey.publicKey
// 签署
let signature = try signingKey.signature(for: data)
// 验证
let isValid = publicKey.isValidSignature(signature, for: data)
P384 与 P521 使用相同的 API——只需替换曲线名称即可。
NIST 金钥支援 DER、PEM、X9.63 以及 Raw 表示法。序列化范例请参考 references/cryptokit-patterns.md。
Curve25519 / Ed25519
let signingKey = Curve25519.Signing.PrivateKey()
let publicKey = signingKey.publicKey
// 签署
let signature = try signingKey.signature(for: data)
// 验证
let isValid = publicKey.isValidSignature(signature, for: data)
Curve25519 金钥仅支援 rawRepresentation(不支援 DER/PEM/X9.63)。
选择曲线
| 曲线 | 签章机制 | 金钥大小 | 常见用途 |
|---|---|---|---|
| P256 | ECDSA | 256 位元 | 通用用途;支援 Secure Enclave |
| P384 | ECDSA | 384 位元 | 较高安全性需求 |
| P521 | ECDSA | 521 位元 | NIST 最高安全性等级 |
| Curve25519 | Ed25519 | 256 位元 | 高效;API 简单;不支援 Secure Enclave |
预设请使用 P256。当需要与基于 Ed25519 的协定进行互操作时使用 Curve25519。
金钥协商
金钥协商允许双方透过各自的公私钥对,利用 ECDH 导出共享的对称金钥。
使用 P256 进行 ECDH
// Alice
let aliceKey = P256.KeyAgreement.PrivateKey()
// Bob
let bobKey = P256.KeyAgreement.PrivateKey()
// Alice 计算共享秘密
let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(
with: bobKey.publicKey
)
// 利用 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_SHA256_AES_GCM_256的XWingMLKEM768X25519 - 数位签章:
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 {
// 降级使用软件金钥
return
}
建立 Secure Enclave 签章金钥
let privateKey = try SecureEnclave.P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey // 标准 P256.Signing.PublicKey
let signature = try privateKey.signature(for: data)
let isValid = publicKey.isValidSignature(signature, for: data)
存取控制
当金钥需要生物辨识或密码验证时,请搭配 .privateKeyUsage 使用 SecAccessControl。Keychain 策略的详细决策请统一包含在 swift-security 领域中。
持久化保存 Secure Enclave 金钥
dataRepresentation 是一串加密的 Blob 资料,仅有同一台设备上的 Secure Enclave 才能将其还原。请将其储存至 Keychain 中。
// 导出
let blob = privateKey.dataRepresentation
// 还原
let restored = try SecureEnclave.P256.Signing.PrivateKey(
dataRepresentation: blob
)
Secure Enclave 金钥协商
let seKey = try SecureEnclave.P256.KeyAgreement.PrivateKey()
let peerPublicKey: P256.KeyAgreement.PublicKey = // 来自对方
let sharedSecret = try seKey.sharedSecretFromKeyAgreement(
with: peerPublicKey
)
常见错误
1. 直接将共享秘密当作金钥使用
// 切勿这样写(DON'T)
let badKey = sharedSecret.withUnsafeBytes { bytes in
SymmetricKey(data: Data(bytes))
}
// 请这样写(DO)-- 透过 HKDF 导出
let goodKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: salt,
sharedInfo: info,
outputByteCount: 32
)
2. 重复使用 Nonce
// 切勿这样写(DON'T)-- 使用硬编码的 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)-- 让 CryptoKit 自动生成随机 Nonce(预设行为)
let box = try AES.GCM.seal(data, using: key)
3. 忽略鉴别标籤验证
// 切勿这样写(DON'T)-- 手动剥离 Tag 并解密
// 请这样写(DO)-- 务必使用 AES.GCM.open() 或 ChaChaPoly.open()
// 它们会自动验证 Tag
4. 在安全场景中使用不安全的杂凑演算法
// 切勿这样写(DON'T)-- 将 MD5/SHA1 用于完整性或安全性目的
import CryptoKit
let bad = Insecure.MD5.hash(data: data)
// 请这样写(DO)-- 使用 SHA256 或更高强度的演算法
let good = SHA256.hash(data: data)
Insecure.MD5 与 Insecure.SHA1 的存在仅为了旧系统相容性(校验和验证、旧协定互操作)。切勿将其用于全新的安全性敏感操作。
5. 将对称金钥存放在 UserDefaults 中
// 切勿这样写(DON'T)
UserDefaults.standard.set(rawKeyData, forKey: "en
<!-- truncated for translation batch; full body continues in source -->




