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
相关产品推荐
相关产品推荐

