最近在項(xiàng)目中網(wǎng)絡(luò)請(qǐng)求項(xiàng)目需要用到ECDH算法(橢圓曲線選擇P-384),計(jì)算出協(xié)商秘鑰后導(dǎo)出密碼使用HKDF進(jìn)行密鑰擴(kuò)展,這里將算法遇到的幾個(gè)問題記錄下來
1、生成公鑰和私鑰
iOS 10.0之后有個(gè)類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的互相轉(zhuǎn)換
使用ECDH加密,必然涉及到服務(wù)端傳給APP公鑰,APP傳給服務(wù)端公鑰,就這需要密鑰字符串和SecKey的互相轉(zhuǎn)換。在這里就不能不說到一個(gè)
SecKey的一個(gè)坑:在將密鑰字符串轉(zhuǎn)化為SecKey時(shí),會(huì)自動(dòng)的去掉密鑰的ASN.1,所以在轉(zhuǎn)化時(shí)得先加上
// 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
}
// 根據(jù)公鑰字符串生成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
]
}
}
這里我偷了懶,沒有繼續(xù)翻蘋果的文檔看有沒有關(guān)于asn.1的類,我是直接用的ASN.1 JavaScript decoder 這個(gè)網(wǎng)址去查看了我這所需要的P-384曲線的ASN.1
3、之后就是計(jì)算出協(xié)商秘鑰了,這里代碼很簡(jiǎn)單
let exchangeData = exchangeKey(privateKey: privateKey, pubkey: pubkey)!
后面用HKDF算法對(duì)協(xié)商密鑰擴(kuò)展我這用的第三方庫(kù)CryptoSwift,代碼
let key = try! HKDF(password: [UInt8](exchangeData), variant: .sha256).calculate()