cryptokit

cryptokit

热门

在 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 的场景。

967Star
49Fork
更新于 2026/7/31
SKILL.md
只读
名称
cryptokit
描述

在 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。

目录

哈希计算

在 iOS 13+ 上推荐使用 SHA256/SHA384/SHA512;SHA3_256/SHA3_384/SHA3_512 则需要 iOS 26+。所有哈希算法均遵循 HashFunction 协议。

单次哈希(One-shot hashing)

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 可用性检查

除非项目的最低部署目标(Deployment Target)设为 iOS 26+,否则使用 SHA-3 前必须进行可用性检查:

if #available(iOS 26.0, *) {
    let digest = SHA3_256.hash(data: data)
}

增量哈希(Incremental hashing)

处理大文件或流式输入时,推荐使用增量哈希:

var hasher = SHA256()
hasher.update(data: chunk1)
hasher.update(data: chunk2)
let digest = hasher.finalize()

摘要比对(Digest comparison)

对 CryptoKit 摘要值进行相等性比较时,请直接使用 ==。在安全敏感的场景下,切勿将摘要转化为字符串或数组后再做比对。

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 提供了两种带认证的加密算法(AEAD):AES-GCM 与 ChaChaPoly。两者加密后均会输出包含随机数(Nonce)、密文和认证标签(Authentication Tag)的密封盒(Sealed Box)。

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 + ciphertext + tag

// 解密
let box = try AES.GCM.SealedBox(combined: ciphertext)
let decrypted = try AES.GCM.open(box, using: key)

ChaChaPoly

在缺乏 AES 硬件加速的设备上,或需要与特定协议(如 TLS、WireGuard)互操作且要求 ChaCha20-Poly1305 时使用 ChaChaPoly。

let sealedBox = try ChaChaPoly.seal(plaintext, using: key)
let combined = sealedBox.combined  // 对于 ChaChaPoly 而言始终非可选值

let box = try ChaChaPoly.SealedBox(combined: combined)
let decrypted = try ChaChaPoly.open(box, using: key)

附加认证数据(Authenticated data)

两种加密算法均支持附加认证数据(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

// 签名
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-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 计算共享密钥
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
)

密钥衍生函数(KDF)

SharedSecret 不能直接当作 SymmetricKey 使用,必须始终通过以下方法之一衍生密钥:

方法 标准 用途
hkdfDerivedSymmetricKey HKDF (RFC 5869) 推荐的默认方法
x963DerivedSymmetricKey ANSI X9.63 与 ANSI X9.63 系统互操作

请始终传入非空的 sharedInfo 字符串,以将衍生出的密钥绑定到具体的协议上下文中。

HPKE

iOS 17+ 提供了基于公钥加密工作流的 HPKE 功能。向接收方公钥加密数据时,相比手动拼装 ECDH + HKDF + AEAD 协议,更推荐使用 HPKE。

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.SenderHPKE.Recipient 是有状态的;请声明为 var 变量,将 encapsulatedKey 连同密文一起发送,并按加密时的相同顺序解密消息。算法套件选择和后量子 HPKE 详情参见 references/cryptokit-patterns.md

后量子 CryptoKit

iOS 26+ 新增了抗量子密码学 API:

  • 密钥封装:MLKEM768MLKEM1024
  • 混合 HPKE:配合 .XWingMLKEM768X25519_SHA256_AES_GCM_256 使用 XWingMLKEM768X25519
  • 数字签名:MLDSA65MLDSA87
  • Secure Enclave 变体:SecureEnclave.MLKEM768SecureEnclave.MLKEM1024SecureEnclave.MLDSA65SecureEnclave.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. 直接将共享密钥作为加密密钥使用

// ❌ 错误写法
let badKey = sharedSecret.withUnsafeBytes { bytes in
    SymmetricKey(data: Data(bytes))
}

// ✅ 正确写法 -- 使用 HKDF 衍生密钥
let goodKey = sharedSecret.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: salt,
    sharedInfo: info,
    outputByteCount: 32
)

2. 重用随机数(Nonce)

// ❌ 错误写法 -- 硬编码 Nonce
let nonce = try AES.GCM.Nonce(data: Data(repeating: 0, count: 12))
let box = try AES.GCM.seal(data, using: key, nonce: nonce)

// ✅ 正确写法 -- 让 CryptoKit 自动生成随机 Nonce(默认行为)
let box = try AES.GCM.seal(data, using: key)

3. 忽略认证标签校验

// ❌ 错误写法 -- 手动剥离认证标签后解密
// ✅ 正确写法 -- 始终使用 AES.GCM.open() 或 ChaChaPoly.open(),它们会自动校验认证标签

4. 为了安全性使用不安全的哈希算法

// ❌ 错误写法 -- 为了完整性或安全性使用 MD5/SHA1
import CryptoKit
let bad = Insecure.MD5.hash(data: data)

// ✅ 正确写法 -- 使用 SHA256 或更强的哈希算法
let good = SHA256.hash(data: data)

Insecure.MD5Insecure.SHA1 的存在仅用于兼容旧系统(如校验和比对、旧协议互操作)。切勿在全新的安全敏感业务中使用它们。

5. 将对称密钥直接保存在 UserDefaults 中

// ❌ 错误写法
UserDefaults.standard.set(rawKeyData, forKey: "en...