在macOS平台上做安全相关的开发,绕不开Security Framework。它是苹果生态里最底层的安全基础设施之一,从钥匙串访问到证书校验,再到非对称加密,都由这套框架提供支撑。其中SecKey是操作RSA、EC等非对称密钥的核心对象,无论是生成密钥对、执行加解密,还是做数字签名验证,都离不开它。本文将用一个完整的流程把这些能力串起来,配合可直接编译运行的Swift代码,帮助你把SecKey真正用起来。

SecKey生成RSA密钥对:从参数选择到代码实现
生成密钥对是一切操作的前提。在Security Framework中,旧式的SecKeyGeneratePair已经在Swift中显得不够安全友好,推荐使用的是基于属性的SecKeyCreateRandomKey函数。它接收一个CFDictionary形式的参数字典,通过kSecAttrKeyType指定算法类型,用kSecAttrKeySizeInBits指定密钥长度。
关于密钥长度需要多说几句。RSA 1024位早已被认为不够安全,目前主流选择是2048位,如果业务对安全等级要求更高,可以用3072或4096位,但要注意密钥越长,加解密和签名运算的开销也越大,需要根据实际场景权衡。下面的代码演示了生成一对2048位RSA密钥的完整过程:
import Security
func generateRSAKeyPair() throws -> (SecKey, SecKey) {
// 定义密钥生成的属性参数
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(
attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
// 公钥可以直接从私钥中派生出来
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw NSError(domain: "KeyGen", code: -1,
userInfo: [NSLocalizedDescriptionKey: "无法获取公钥"])
}
return (publicKey, privateKey)
}
这段代码有两个细节值得注意。第一,SecKeyCreateRandomKey返回的直接就是私钥对象,公钥通过SecKeyCopyPublicKey派生,不需要单独生成两次。第二,错误处理使用的是Unmanaged<CFError>这种Core Foundation风格的模式,必须调用takeRetainedValue()来转移内存所有权,否则会造成内存泄漏。
如果你希望密钥持久化保存,可以在属性字典里追加kSecAttrIsPermanent设为true,并把kSecAttrApplicationTag设置为一个唯一标签,这样密钥会自动写入钥匙串。后续通过SecItemCopyMatching就能按标签把密钥重新读出来,应用重启后依然可用。
使用SecKey执行RSA加密与解密
非对称加密的典型场景是用公钥加密、私钥解密。比如客户端要发送一段敏感数据给服务器,先用服务器下发的公钥加密,只有持有私钥的服务器才能还原内容。Security Framework提供了SecKeyCreateEncryptedData和SecKeyCreateDecryptedData两个函数来完成这件事。
这里有一个新手最容易踩的坑:RSA本身只能加密长度不超过密钥长度减去填充开销的数据。对2048位密钥来说,使用OAEP填充时明文最多只能约190字节左右。如果你直接拿它去加密一个大文件,会得到errSecParam错误。正确的做法是用RSA加密一个随机的对称密钥(比如AES密钥),再用AES加密实际数据,这也是TLS等协议的基本思路。
func rsaEncrypt(plainText: Data, publicKey: SecKey) throws -> Data {
var error: Unmanaged<CFError>?
guard let cipherData = SecKeyCreateEncryptedData(
publicKey,
.rsaEncryptionOAEPSHA256, // 推荐使用OAEP填充算法
plainText as CFData,
&error) else {
throw error!.takeRetainedValue() as Error
}
return cipherData as Data
}
func rsaDecrypt(cipherText: Data, privateKey: SecKey) throws -> Data {
var error: Unmanaged<CFError>?
guard let plainData = SecKeyCreateDecryptedData(
privateKey,
.rsaEncryptionOAEPSHA256,
cipherText as CFData,
&error) else {
throw error!.takeRetainedValue() as Error
}
return plainData as Data
}
关于填充算法的选择,强烈建议使用.rsaEncryptionOAEPSHA256而不是老旧的PKCS1填充。OAEP在安全性上明显更强,能抵御多种针对填充机制的攻击。还要注意加密和解密必须使用相同的填充算法,否则解密会直接失败并返回错误码。
数字签名的生成与验证
签名和加密看起来都是用密钥做运算,但目的完全不同。加密是为了保密,签名是为了证明身份和完整性。RSA签名的流程是:发送方用私钥对数据的摘要做签名运算,接收方用公钥验证签名是否匹配。任何人都能验证,但只有私钥持有者能生成,这正是数字签名的价值所在。
Security Framework中对应的是SecKeyCreateSignature和SecKeyVerifySignature这对函数。签名算法通过SecKeyAlgorithm指定,常用的是.rsaSignatureMessagePKCS1v15SHA256,它会自动对输入数据计算SHA256摘要再签名,不需要你手动做哈希:
func sign(data: Data, privateKey: SecKey) throws -> Data {
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(
privateKey,
.rsaSignatureMessagePKCS1v15SHA256,
data as CFData,
&error) else {
throw error!.takeRetainedValue() as Error
}
return signature as Data
}
func verify(data: Data, signature: Data, publicKey: SecKey) -> Bool {
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
publicKey,
.rsaSignatureMessagePKCS1v15SHA256,
data as CFData,
signature as CFData,
&error)
if !result, let err = error {
print("验证失败: \(err.takeRetainedValue())")
}
return result
}
验证失败时不要急着下结论说数据被篡改了,先排查几个常见原因:签名和验证用了不同的摘要算法、数据在传输中编码方式不一致(比如一边是UTF-8一边经过了Base64解码)、或者公钥和私钥根本不是同一对。实践中建议把待签名的数据先做一次规范化处理,例如统一JSON字段顺序,再计算签名,能避免大量莫名其妙的验证失败。
密钥存储方案对比与工程建议
密钥怎么存直接决定整体安全水位。把私钥留在内存里用完即弃最简单,但应用退出密钥就没了,适合一次性会话密钥。把密钥写入钥匙串(Keychain)是macOS上更推荐的方式,钥匙串底层由安全芯片和系统服务保护,还能配合访问控制策略,比如要求验证用户密码后才能使用私钥签名。
如果私钥需要和服务器交互,通常会涉及格式转换问题。SecKey对象不能直接序列化,需要用SecKeyCopyExternalRepresentation导出,公钥导出的是原始PKCS1格式数据,服务器端往往需要包装成PEM格式,也就是加上Base64编码和头尾标记。反过来导入外部公钥时,用SecKeyCreateWithData并指定kSecAttrKeyTypeRSA即可。
最后给几条工程实践建议:一是永远不要在代码里硬编码私钥内容,也不要把私钥导出后写到普通文件;二是给密钥设置合理的kSecAttrAccessible属性,控制其在设备锁定时是否可用;三是为不同业务使用不同的密钥对,通过Application Tag区分,避免一把密钥既做加密又做签名,职责混用会扩大单点泄露的影响面。把这些细节处理好,SecKey就能在你的应用里稳定可靠地承担起安全职责。
macOS Security FrameworkSecKeyRSA数字签名修改时间:2026-09-05 02:24:37