最近项目的需求用到RSA的加密解密,并且需要把公钥信息保存到Keychain里面,网上很多文章都是用Keychain保存账号密码什么的,自己在实现保存的公钥过程中也踩了不少坑。现在来整理一下实现的方法。
首先肯定是先要导入 Security
import Security
然后定义一个常量 Identifier
// 密钥唯一标示
fileprivate let publicKeyIdentifier = "com.hhh.publicKey"
fileprivate let privateKeyIdentifier = "com.hhh.privateKey"
fileprivate let publicKeyTag = publicKeyIdentifier.data(using: .utf8)!
fileprivate let privateKeyTag = privateKeyIdentifier.data(using: .utf8)!
实现一个把SecKey转换成Data的方法
private static func getKeyDataFrom(secKey: SecKey, tag: Data) -> Data {
var data: Data?
var query = [String: Any]()
query[kSecClass as String] = kSecClassKey
query[kSecAttrApplicationTag as String] = tag
query[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA
var attributes = query
attributes[kSecValueRef as String] = secKey
attributes[kSecReturnData as String] = true
var result: CFTypeRef?
let status = SecItemAdd(attributes as CFDictionary, &result)
if status == errSecSuccess {
data = result as? Data
SecItemDelete(query as CFDictionary)
}
return data!
}
这个方法的原理其实就是先把SecKey存到Keychain里面然后再读取出Data类型的数据
接下来就是实现存到Keychain的方法
// keySize 就是RSA密钥的长度
// isPrivate 判断存储的是否为私钥(true为私钥、false为公钥)
static func saveRSAKeyToKeychain(key: SecKey, keySize: size_t, isPrivate: Bool) {
var saveDictionary = [String: Any]()
let keyClass = isPrivate ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic
// 设置keychain字典
saveDictionary[kSecClass as String] = kSecClassKey
saveDictionary[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA
saveDictionary[kSecAttrApplicationTag as String] = isPrivate ? privateKeyTag : publicKeyTag
saveDictionary[kSecAttrKeyClass as String] = keyClass
saveDictionary[kSecValueData as String] = getKeyDataFrom(secKey: key, tag: isPrivate ? privateKeyTag : publicKeyTag)
saveDictionary[kSecAttrKeySizeInBits as String] = keySize
saveDictionary[kSecAttrEffectiveKeySize as String] = keySize
saveDictionary[kSecAttrCanDerive as String] = kCFBooleanFalse
saveDictionary[kSecAttrCanEncrypt as String] = kCFBooleanTrue
saveDictionary[kSecAttrCanDecrypt as String] = kCFBooleanTrue
saveDictionary[kSecAttrCanVerify as String] = kCFBooleanTrue
saveDictionary[kSecAttrCanSign as String] = kCFBooleanFalse
saveDictionary[kSecAttrCanWrap as String] = kCFBooleanTrue
saveDictionary[kSecAttrCanUnwrap as String] = kCFBooleanFalse
saveDictionary[kSecAttrApplicationLabel as String] = isPrivate ? privateKeyIdentifier : publicKeyIdentifier
// 删除旧数据
SecItemDelete(saveDictionary as CFDictionary)
let status = SecItemAdd(saveDictionary as CFDictionary, nil)
assert(status == errSecSuccess, "keychain存储密钥失败")
}
最后就是实现从Keychain取出密钥信息的方法
// isPrivate 判断存储的是否为私钥(true为私钥、false为公钥)
static func getRSAKeyFromKeychain(isPrivate: Bool) -> SecKey! {
var queryDictionary = [String: Any]()
let keyClass = isPrivate ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic
queryDictionary[kSecClass as String] = kSecClassKey
queryDictionary[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA
queryDictionary[kSecAttrApplicationTag as String] = isPrivate ? privateKeyTag : publicKeyTag
queryDictionary[kSecAttrKeyClass as String] = keyClass
queryDictionary[kSecReturnRef as String] = kCFBooleanTrue
queryDictionary[kSecAttrApplicationLabel as String] = isPrivate ? privateKeyIdentifier : publicKeyIdentifier
var key: CFTypeRef?
let status = SecItemCopyMatching(queryDictionary as CFDictionary, &key)
if status == errSecSuccess {
// 强转SecKey类型
return key as! SecKey
}
assert(false, "keychain读取密钥失败")
}
因为在开头我们已经定义了公钥私钥的 Identifier 所有我们只需要判断存/取的是私钥还是公钥就可以将密钥信息进行读写操作。