You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 02:35:08