如何在Swift中计算与NodeJS一致的ECDH共享密钥
解决Swift与NodeJS ECDH共享密钥一致性问题
核心问题原因
NodeJS中ECDH生成共享密钥时,默认输出的是原始的椭圆曲线点的x坐标字节(未经过哈希摘要处理),而SwiftECC的keyAgreement方法默认会对原始共享密钥做指定的哈希摘要,这就是结果不一致的根源。
具体实现方案
方案1:使用苹果原生CryptoKit(iOS 13+)
CryptoKit支持直接获取原始共享密钥,无需依赖第三方库,分曲线处理:
处理secp256k1曲线
CryptoKit原生不支持secp256k1,需结合libsecp256k1的Swift绑定实现:
import CryptoKit // 假设已引入libsecp256k1的Swift绑定库 struct Secp256k1PrivateKey { let rawData: Data init(rawData: Data) throws { guard rawData.count == 32 else { throw NSError(domain: "CryptoError", code: 1, userInfo: [NSLocalizedDescriptionKey: "私钥长度错误"]) } self.rawData = rawData } func generatePublicKey() throws -> Data { var pubKeyBytes = [UInt8](repeating: 0, count: 65) let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY))! defer { secp256k1_context_destroy(context) } let result = secp256k1_ec_pubkey_create(context, &pubKeyBytes, rawData.bytes, 1) guard result == 1 else { throw NSError(domain: "CryptoError", code: 2, userInfo: [NSLocalizedDescriptionKey: "公钥生成失败"]) } return Data(pubKeyBytes) } func computeSharedSecret(with peerPubKeyData: Data) throws -> Data { var sharedSecretBytes = [UInt8](repeating: 0, count: 32) let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY))! defer { secp256k1_context_destroy(context) } let result = secp256k1_ecdh(context, &sharedSecretBytes, peerPubKeyData.bytes, rawData.bytes, nil, nil) guard result == 1 else { throw NSError(domain: "CryptoError", code: 3, userInfo: [NSLocalizedDescriptionKey: "共享密钥生成失败"]) } return Data(sharedSecretBytes) } } // 使用示例 let localPrivateKeyData = Data(hex: "你的NodeJS私钥十六进制字符串") let peerPublicKeyData = Data(hex: "NodeJS传来的对方公钥十六进制字符串") do { let privateKey = try Secp256k1PrivateKey(rawData: localPrivateKeyData) let sharedKey = try privateKey.computeSharedSecret(with: peerPublicKeyData) print(sharedKey.toHexString()) // 与NodeJS结果一致 } catch { print(error.localizedDescription) }
处理secp224r1曲线
CryptoKit原生支持secp224r1(对应P224曲线),直接使用即可:
import CryptoKit // 从NodeJS导出的私钥数据初始化 let privateKey = try P224.KeyAgreement.PrivateKey(rawRepresentation: localPrivateKeyData) // 从NodeJS导出的公钥数据初始化 let peerPublicKey = try P224.KeyAgreement.PublicKey(rawRepresentation: peerPublicKeyData) // 获取原始共享密钥,跳过哈希摘要 let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: peerPublicKey) let rawSharedKey = sharedSecret.rawRepresentation // 与NodeJS结果完全一致
方案2:使用SwiftECC的原始密钥协商方法
如果必须依赖SwiftECC,可调用其rawKeyAgreement方法直接获取原始共享密钥:
import SwiftECC // 以secp256k1为例 let localPrivateKey = try ECPrivateKey(data: localPrivateKeyData, curve: .secp256k1) let peerPublicKey = try ECPublicKey(data: peerPublicKeyData, curve: .secp256k1) // 直接获取原始共享密钥,不做哈希处理 let rawSharedKey = try localPrivateKey.rawKeyAgreement(with: peerPublicKey)
验证方式
将Swift生成的共享密钥转为十六进制字符串,与NodeJS中ecdh.computeSecret(publicKey).toString('hex')的输出对比,完全匹配则说明实现正确。
内容的提问来源于stack exchange,提问作者David Novák
相关产品推荐
相关产品推荐

