完整的流程如下所示:
- 从服务器端使用公钥接收 91 个字节
- 使用 SecKeyCreateWithData 创建一个 SecKey
- 使用 SecKeyCreateRandomKey 在 iOS 上创建密钥对
- 将自己的公钥发送到服务器端
- 服务器端可以使用该信息计算共享密钥
- 客户端使用 SecKeyCopyKeyExchangeResult 计算共享密钥
- 如果一切正确,它应该在 iOS 和 Java 端提供相同的共享密钥
因此,要获得完整的测试用例,可以编写一个生成密钥对的 Java 程序。为简单起见,可以在 Java 和 iOS 应用程序之间复制/粘贴公钥以进行测试,而不是使用网络连接。Java 程序将公钥写入控制台。该密钥被复制到 Swift 源代码中。Swift 程序也被编译并生成一个密钥对。公钥被复制/粘贴到 Java 程序中,该程序在控制台上读取它。然后,两个程序都输出计算出的共享密钥,出于显而易见的原因,这应该是相同的,因为它用于进一步的对称加密。
这个很好的答案https://stackoverflow.com/a/26502285/2331445提供了将十六进制字符串转换为数据并返回的实用方法。
iOS Swift 代码
以下代码假定使用 secp256r1 曲线,密钥大小为 256 位。
所描述的流程可以实现如下:
let otherKey = "3059301306072a8648ce3d020106082a8648ce3d03010703420004df96b3c0c651707c93418781b91782319f6e798550d954c46ac7318c7eac130f96380991a93049059e03e4190dd147b64d6ebc57320938f026844bda3de22352".hexadecimal!
guard let otherPublicKey = otherPublicKey(data: otherKey) else { return }
guard let ownPrivateKey = createOwnKey() else { return }
guard let ownPublicKey = SecKeyCopyPublicKey(ownPrivateKey) else { return }
send(ownPublicKey: ownPublicKey)
if let sharedSecret = computeSharedSecret(ownPrivateKey: ownPrivateKey, otherPublicKey: otherPublicKey) {
print("shared secret: \(sharedSecret.hexadecimal)")
} else {
print("shared secret computation failed")
}
用到的功能:
private func otherPublicKey(data: Data) -> SecKey? {
var error: Unmanaged<CFError>? = nil
let cfData = data.dropFirst(26) as CFData
let attributes = [
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
] as CFDictionary
if let publicKey = SecKeyCreateWithData(cfData, attributes, &error) {
return publicKey
}
print("other EC public: \(String(describing: error))")
return nil
}
private func createOwnKey() -> SecKey? {
var error: Unmanaged<CFError>? = nil
let keyPairAttr: [String : Any] = [kSecAttrKeySizeInBits as String: 256,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]
]
guard let key = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
print("key creation: \(String(describing: error))")
return nil
}
return key
}
此函数send
仅在调试控制台上以十六进制输出密钥。对于测试,可以通过复制/粘贴将其传输到 Java 程序。在实际程序中,它将通过网络连接传输到服务器。
private func send(ownPublicKey: SecKey) {
guard let data = SecKeyCopyExternalRepresentation(ownPublicKey, nil) as Data? else {
print("SecKeyCopyExternalRepresentation failed")
return
}
let secp256r1Header = "3059301306072a8648ce3d020106082a8648ce3d030107034200"
let pkWithHeader = secp256r1Header + data.hexadecimal
print("ownPublicKeyHexWithHeader \(pkWithHeader.count / 2) bytes: " + pkWithHeader)
}
使用自己的私钥和服务器的公钥,可以计算共享秘密。
private func computeSharedSecret(ownPrivateKey: SecKey, otherPublicKey: SecKey) -> Data? {
let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandard
let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32, SecKeyKeyExchangeParameter.sharedInfo.rawValue: Data()] as [String: Any]
var error: Unmanaged<CFError>? = nil
if let sharedSecret: Data = SecKeyCopyKeyExchangeResult(ownPrivateKey, algorithm, otherPublicKey, params as CFDictionary, &error) as Data? {
return sharedSecret
} else {
print("key exchange: \(String(describing: error))")
}
return nil
}
测试
在上方区域您可以看到 Xcode 控制台,在下方区域中可以看到 Java 程序的输出。共同的秘密是相同的。所以测试成功了。