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

C#与Kotlin ECDH共享密钥不匹配问题排查与解决

ECDH secp384r1曲线跨平台密钥不一致问题解决

问题描述

使用secp384r1曲线推导ECDH共享密钥时,C#通过ECDiffieHellmanCng.DeriveKeyMaterial生成的结果,与Kotlin通过KeyAgreement.doPhase+generateSecret生成的结果不一致。

代码复现

C#服务端代码(.NET 7.0.1)

using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;

var listener = new TcpListener(IPAddress.Any, 13000);
listener.Start();
using var client = await listener.AcceptTcpClientAsync();
var sharedKey = await GetSharedKey(client, CancellationToken.None);

async Task<byte[]> GetSharedKey(TcpClient client, CancellationToken token)
{
    //Generate ECDH key pair using secp384r1 curve
    var ecdh = new ECDiffieHellmanCng(ECCurve.CreateFromFriendlyName("secp384r1"));
    var publicKeyBytes = ecdh.ExportSubjectPublicKeyInfo();
    Console.WriteLine($"Server Public Key: {Convert.ToBase64String(publicKeyBytes)}, " +
                      $"Length: {publicKeyBytes.Length}");

    //Send the generated public key encoded in X.509 to client.
    var stream = client.GetStream();
    await stream.WriteAsync(publicKeyBytes, token);
        
    //Receive client's public key bytes (X.509 encoding).
    var otherPublicKeyBytes = new byte[publicKeyBytes.Length];
    await stream.ReadExactlyAsync(otherPublicKeyBytes, 0, otherPublicKeyBytes.Length, token);
        
    //Decode client's public key bytes.
    var otherEcdh = new ECDiffieHellmanCng(ECCurve.CreateFromFriendlyName("secp384r1"));
    otherEcdh.ImportSubjectPublicKeyInfo(otherPublicKeyBytes, out _);
    Console.WriteLine($"Client Public Key: {Convert.ToBase64String(otherEcdh.ExportSubjectPublicKeyInfo())}, " +
                      $"Length: {otherEcdh.ExportSubjectPublicKeyInfo().Length}");

    //Derive shared key.
    var sharedKey = ecdh.DeriveKeyMaterial(otherEcdh.PublicKey);
    Console.WriteLine($"Shared key: {Convert.ToBase64String(sharedKey)}, " +
                      $"Length: {sharedKey.Length}");
    return sharedKey;
}

Kotlin客户端代码(OpenJDK 19.0.1)

import java.net.Socket
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec
import java.util.*
import javax.crypto.KeyAgreement

fun main(args: Array<String>) {
    val socket = Socket("127.0.0.1", 13000)
    val sharedKey = getSharedKey(socket)
}

private fun getSharedKey(socket: Socket): ByteArray {
    //Generate ECDH key pair using secp384r1 curve
    val keyGen = KeyPairGenerator.getInstance("EC")
    keyGen.initialize(ECGenParameterSpec("secp384r1"))
    val keyPair = keyGen.generateKeyPair()
    println("Client Public Key: ${Base64.getEncoder().encodeToString(keyPair.public.encoded)}, Length: ${keyPair.public.encoded.size}")

    //Receive server's public key bytes (encoded in X.509)
    val input = socket.getInputStream()
    val publicKeyBytes = input.readNBytes(keyPair.public.encoded.size)

    //Send the generated public key encoded in X.509 to server
    val output = socket.getOutputStream()
    output.write(keyPair.public.encoded)

    // Decode the server's public key
    val keySpec = X509EncodedKeySpec(publicKeyBytes)
    val keyFactory = KeyFactory.getInstance("EC")
    val otherPublicKey = keyFactory.generatePublic(keySpec)
    println("Server Public Key: ${Base64.getEncoder().encodeToString(otherPublicKey.encoded)}, Length: ${otherPublicKey.encoded.size}")

    // Use KeyAgreement to generate the shared key
    val keyAgreement = KeyAgreement.getInstance("ECDH")
    keyAgreement.init(keyPair.private)
    keyAgreement.doPhase(otherPublicKey, true)
    val sharedKey = keyAgreement.generateSecret()
    println("Shared key: ${Base64.getEncoder().encodeToString(sharedKey)}, Length: ${sharedKey.size}")
    return sharedKey
}

问题分析

运行后双方公钥导入导出正常,但存在以下差异:

  • C#生成的共享密钥长度为32字节(理论secp384r1原始密钥应为48字节,即384/8)
  • Kotlin生成的是48字节的原始共享密钥

排查后发现,C#的ECDiffieHellmanCng.DeriveKeyMaterial默认返回共享密钥的SHA256哈希值,而Kotlin的generateSecret返回的是原始共享密钥,这是导致结果不一致的核心原因。

解决方案

修改C#端哈希算法为SHA384

调整ECDH实例的哈希算法配置,使其生成SHA384哈希后的密钥:

//Generate ECDH key pair using secp384r1 curve and change default key's hashing algorithm SHA256 to SHA384
var ecdh = new ECDiffieHellmanCng(ECCurve.CreateFromFriendlyName("secp384r1"))
{
    HashAlgorithm = CngAlgorithm.Sha384
};

修改Kotlin端对原始密钥计算SHA384哈希

对Kotlin生成的原始共享密钥计算SHA384哈希,与C#端保持一致:

val sharedKey = keyAgreement.generateSecret()
val sharedKeyHash = MessageDigest.getInstance("SHA384").digest(sharedKey)
println("Shared key SHA384 hash: ${Base64.getEncoder().encodeToString(sharedKeyHash)}, Length: ${sharedKeyHash.size}")
return sharedKeyHash

额外建议

将C#中的GetSharedKey方法重命名为GetSharedKeysSHA384Hash,使方法名与实际功能匹配,提升代码可读性。


内容的提问来源于stack exchange,提问作者Sorhox

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:25:15