mcpField
C
cryptokit

cryptokit

github.com/dpearson2699安全1.2k3.2k 次安装13 周前更新
代码仓库

关于 cryptokit

面向 Swift 开发者的 Apple CryptoKit 加密指南,介绍常见密码操作的 API 用法、系统版本要求、安全隔区密钥及常见风险。

主要功能

  1. 01哈希与 HMAC
  2. 02带认证的对称加密
  3. 03公钥签名与密钥协商
  4. 04HPKE 与后量子 API
  5. 05安全隔区支持的密钥
  6. 06加密代码检查清单

适用场景

  1. 01计算数据哈希或消息认证码
  2. 02使用 AES-GCM 或 ChaChaPoly 加密
  3. 03签署消息或派生共享密钥
  4. 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)。

选择曲线
曲线签名方案密钥大小典型用途
P256ECDSA256-bit通用;支持 Secure Enclave
P384ECDSA384-bit更高的安全要求
P521ECDSA521-bit最高 NIST 安全级别
Curve25519Ed25519256-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。始终使用以下方法之一派生密钥:

方法标准用途
hkdfDerivedSymmetricKeyHKDF (RFC 5869)推荐的默认选项
x963DerivedSymmetricKeyANSI 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)

参考资料

评论

支持 Markdown