最近在项目中网络请求项目需要用到ECDH算法(椭圆曲线选择P-384),计算出协商秘钥后导出密码使用HKDF进行密钥扩展,这里将算法遇到的几个问题记录下来
1、生成公钥和私钥
iOS 10.0之后有个类SecKey,专门用于生成公钥和私钥。生成代码如下图
var error: Unmanaged<CFError>?
let attributes: [String: Any] = [kSecAttrKeySizeInBits as String: 384,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false]]
self.privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
if let privateKey = self.privateKey {
publicKey = SecKeyCopyPublicKey(privateKey)
}
其中384表示的是 P-384椭圆曲线。另外还有P-224, P-256, P-521这些椭圆曲线
2、密钥字符串和SecKey的互相转换
使用ECDH加密,必然涉及到服务端传给APP公钥,APP传给服务端公钥,就这需要密钥字符串和SecKey的互相转换。在这里就不能不说到一个
SecKey的一个坑:在将密钥字符串转化为SecKey时,会自动的去掉密钥的ASN.1,所以在转化时得先加上
// MARK: -
public extension SecKey {
func publicSeckeyToString() -> String? {
var error:Unmanaged<CFError>?
if let cfdata = SecKeyCopyExternalRepresentation(self, &error) {
// 添加secp384r1的asn.1
let pemPrefixBuffer :[UInt8] = SecKey.getPKCS1SHA384ASN1()
var finalPemData = Data(bytes: pemPrefixBuffer as [UInt8], count: pemPrefixBuffer.count)
finalPemData.append(cfdata as Data)
let finalPemString = finalPemData.base64EncodedString(options: .lineLength64Characters)
return finalPemString
}
return nil
}
// 根据公钥字符串生成seckey
class func initPubkeyString(pubkeyString: String) -> SecKey {
let pubKeyByte:Array<UInt8> = Data.init(base64Encoded: pubkeyString)!.bytes
let asn1Byte:Array<UInt8> = SecKey.getPKCS1SHA384ASN1()
let array = pubKeyByte[asn1Byte.count...]
var error: Unmanaged<CFError>?
let attributes = [kSecAttrKeySizeInBits as String: 384,
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPublic] as NSDictionary
let pubkey = SecKeyCreateWithData(Data(array) as NSData, attributes, &error)
return pubkey!
}
class func getPKCS1SHA384ASN1() -> Array<UInt8> {
return [
0x30, 0x76,
0x30, 0x10,
0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05,
0x2b, 0x81, 0x04,
0x00, 0x22, 0x03, 0x62, 0x00
]
}
}
这里我偷了懒,没有继续翻苹果的文档看有没有关于asn.1的类,我是直接用的ASN.1 JavaScript decoder 这个网址去查看了我这所需要的P-384曲线的ASN.1
3、之后就是计算出协商秘钥了,这里代码很简单
let exchangeData = exchangeKey(privateKey: privateKey, pubkey: pubkey)!
后面用HKDF算法对协商密钥扩展我这用的第三方库CryptoSwift,代码
let key = try! HKDF(password: [UInt8](exchangeData), variant: .sha256).calculate()