ECC密钥协商详解及实现与应用

有时候,最深的伤,不是别人的背叛,而是自己亲手关闭了心门

Posted by yishuifengxiao on 2025-07-22

ECC密钥协商详解及实现与应用

ECC密钥协商原理

ECC(椭圆曲线密码学)密钥协商基于椭圆曲线离散对数问题,最常用的是ECDH(椭圆曲线Diffie-Hellman)算法。

ECDH密钥协商过程

  1. 参数选择:双方约定使用相同的椭圆曲线参数(如secp256r1)
  2. 密钥对生成:双方各自生成ECC密钥对(私钥d和公钥Q=d×G,其中G是基点)
  3. 公钥交换:双方交换各自的公钥
  4. 共享密钥计算
    • Alice计算:S = d_A × Q_B(d_A是Alice的私钥,Q_B是Bob的公钥)
    • Bob计算:S = d_B × Q_A(d_B是Bob的私钥,Q_A是Alice的公钥)
  5. 密钥派生:对共享密钥S应用密钥派生函数(KDF)生成最终会话密钥

由于椭圆曲线的性质,双方计算出的共享密钥S是相同的:d_A × Q_B = d_A × (d_B × G) = d_B × (d_A × G) = d_B × Q_A

ECC 密钥协商(ECDH)全过程详解

ECDH 是一种密钥协商协议,允许双方在不安全的信道上,通过交换一些公开信息,独立地计算出一个相同的共享秘密(Shared Secret)。这个共享秘密随后可以被用作对称加密的密钥(如 AES 的密钥)。其核心在于利用了椭圆曲线离散对数问题的数学特性。

参与方:

  • Alice:协商的发起方。
  • Bob:协商的响应方。

前提条件:
双方事先约定好使用同一个椭圆曲线(例如,在 Golang 中常用的 P-256, P-384, P-521)。

全过程步骤:

  1. 生成密钥对(Key Pair Generation)

    • Alice:随机生成一个私钥 dA(一个保密的超大整数)。利用椭圆曲线乘法,计算其对应的公钥 QA = dA * G,其中 G 是椭圆曲线上的一个公开的基点(Generator Point)。
    • Bob:同样,随机生成自己的私钥 dB,并计算对应的公钥 QB = dB * G
  2. 交换公钥(Public Key Exchange)

    • Alice 将自己的公钥 QA 发送给 Bob。这个通道可以是不安全的,即使被窃听也无妨。
    • Bob 将自己的公钥 QB 发送给 Alice。同样,这个通道也可以是不安全的。
  3. 计算共享秘密(Shared Secret Calculation)

    • Alice 收到 Bob 的公钥 QB 后,用自己的私钥 dA 进行计算:S = dA * QB
      • 因为 QB = dB * G,所以 S = dA * (dB * G) = (dA * dB) * G
    • Bob 收到 Alice 的公钥 QA 后,用自己的私钥 dB 进行计算:S = dB * QA
      • 因为 QA = dA * G,所以 S = dB * (dA * G) = (dB * dA) * G
    • 根据椭圆曲线乘法的交换律,(dA * dB) * G = (dB * dA) * G。因此,双方计算出的点 S同一个点,即 (x, y) 坐标相同。
  4. 派生对称密钥(Symmetric Key Derivation)

    • 这个共享秘密 S 是一个椭圆曲线上的点(包含 X, Y 坐标)。直接将其字节序列作为密钥可能不够安全或长度不合适。
    • 双方会使用一个密钥派生函数(KDF, Key Derivation Function) 来处理这个共享秘密。通常,只会使用点的 X 坐标(或 X 和 Y 坐标的某种组合)作为 KDF 的输入。
    • KDF(如 HKDF)会将这个输入“拉伸”和“混合”,生成一个或多个长度固定、密码学强度高的密钥字节序列。这个最终的字节序列就是双方用于对称加密(如 AES-256)的共享密钥

安全性:
窃听者即使获得了公开的 G, QA, QB,也无法计算出 dAdB(椭圆曲线离散对数难题),因此也无法计算出共享秘密 S

Java实现ECC密钥协商

以下是使用Java标准库实现ECDH密钥协商的完整示例:

import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class ECCKeyExchange {

// 生成ECC密钥对
public static KeyPair generateECCKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); // 使用标准曲线
keyGen.initialize(ecSpec, new SecureRandom());
return keyGen.generateKeyPair();
}

// 执行ECDH密钥协商
public static byte[] performKeyAgreement(PrivateKey privateKey, PublicKey publicKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret();
}

// 从共享密钥派生AES密钥
public static SecretKey deriveAESKey(byte[] sharedSecret, int keySize) throws Exception {
// 简单的KDF:使用SHA-256哈希并取前keySize位
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashedSecret = digest.digest(sharedSecret);

// 根据所需密钥长度截取
byte[] derivedKey = new byte[keySize / 8];
System.arraycopy(hashedSecret, 0, derivedKey, 0, derivedKey.length);

return new SecretKeySpec(derivedKey, "AES");
}

// 公钥序列化为字符串
public static String publicKeyToString(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}

// 从字符串恢复公钥
public static PublicKey publicKeyFromString(String keyString) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyString);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(keySpec);
}

public static void main(String[] args) throws Exception {
System.out.println("=== ECC密钥协商演示 ===");

// Alice生成密钥对
KeyPair aliceKeyPair = generateECCKeyPair();
PublicKey alicePublicKey = aliceKeyPair.getPublic();
PrivateKey alicePrivateKey = aliceKeyPair.getPrivate();

// Bob生成密钥对
KeyPair bobKeyPair = generateECCKeyPair();
PublicKey bobPublicKey = bobKeyPair.getPublic();
PrivateKey bobPrivateKey = bobKeyPair.getPrivate();

System.out.println("Alice和Bob已生成各自的ECC密钥对");

// 模拟公钥交换
String alicePublicKeyStr = publicKeyToString(alicePublicKey);
String bobPublicKeyStr = publicKeyToString(bobPublicKey);

System.out.println("公钥交换中...");

// Alice使用Bob的公钥计算共享密钥
PublicKey receivedBobPublicKey = publicKeyFromString(bobPublicKeyStr);
byte[] aliceSharedSecret = performKeyAgreement(alicePrivateKey, receivedBobPublicKey);
SecretKey aliceAESKey = deriveAESKey(aliceSharedSecret, 256); // 生成256位AES密钥

// Bob使用Alice的公钥计算共享密钥
PublicKey receivedAlicePublicKey = publicKeyFromString(alicePublicKeyStr);
byte[] bobSharedSecret = performKeyAgreement(bobPrivateKey, receivedAlicePublicKey);
SecretKey bobAESKey = deriveAESKey(bobSharedSecret, 256); // 生成256位AES密钥

// 验证双方生成的密钥是否相同
if (java.util.Arrays.equals(aliceAESKey.getEncoded(), bobAESKey.getEncoded())) {
System.out.println("✓ 密钥协商成功!双方拥有相同的AES密钥");

// 显示密钥信息(实际应用中不应记录或显示密钥)
System.out.println("共享密钥哈希: " + Base64.getEncoder().encodeToString(
MessageDigest.getInstance("SHA-256").digest(aliceSharedSecret)));
System.out.println("AES密钥: " + Base64.getEncoder().encodeToString(aliceAESKey.getEncoded()));

// 演示使用协商的密钥进行加密通信
demonstrateKeyUsage(aliceAESKey, bobAESKey);
} else {
System.out.println("✗ 密钥协商失败");
}
}

// 演示如何使用协商的密钥
public static void demonstrateKeyUsage(SecretKey aliceKey, SecretKey bobKey) throws Exception {
System.out.println("\n=== 使用协商的密钥进行加密通信 ===");

String originalMessage = "这是一条需要加密的秘密消息";
System.out.println("原始消息: " + originalMessage);

// Alice加密消息
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12]; // GCM推荐12字节IV
SecureRandom random = new SecureRandom();
random.nextBytes(iv);

cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aliceKey, new javax.crypto.spec.GCMParameterSpec(128, iv));
byte[] encryptedMessage = cipher.doFinal(originalMessage.getBytes("UTF-8"));

// 将IV和加密消息组合在一起传输
byte[] messageToSend = new byte[iv.length + encryptedMessage.length];
System.arraycopy(iv, 0, messageToSend, 0, iv.length);
System.arraycopy(encryptedMessage, 0, messageToSend, iv.length, encryptedMessage.length);

System.out.println("加密后消息: " + Base64.getEncoder().encodeToString(messageToSend));

// Bob解密消息
byte[] receivedIv = new byte[12];
byte[] receivedEncryptedMessage = new byte[messageToSend.length - 12];
System.arraycopy(messageToSend, 0, receivedIv, 0, 12);
System.arraycopy(messageToSend, 12, receivedEncryptedMessage, 0, messageToSend.length - 12);

cipher.init(javax.crypto.Cipher.DECRYPT_MODE, bobKey, new javax.crypto.spec.GCMParameterSpec(128, receivedIv));
byte[] decryptedMessage = cipher.doFinal(receivedEncryptedMessage);

System.out.println("解密后消息: " + new String(decryptedMessage, "UTF-8"));
}
}

协商密钥的使用方式

协商出的共享密钥通常用于对称加密算法(如AES),具体应用方式包括:

1. 直接作为会话密钥

将协商出的密钥直接用于加密通信,如示例中演示的AES加密。

2. 密钥派生

使用密钥派生函数(KDF)从共享密钥派生出多个子密钥:

  • 加密密钥:用于加密数据
  • 认证密钥:用于消息认证码(MAC)
  • IV生成密钥:用于生成初始化向量

3. 安全协议中的应用

在TLS、IPSec等安全协议中,ECDH协商的密钥用于:

  • 生成预主密钥(pre-master secret)
  • 与客户端和服务器随机数结合生成主密钥(master secret)
  • 从主密钥派生出实际用于加密和认证的密钥

4. 前向安全性保障

ECDH的一个重要优势是提供前向安全性:即使长期私钥在未来被泄露,过去的通信会话也不会被解密,因为每次会话都使用临时密钥对。


Golang 实现ECC密钥协商

Golang 的标准库 crypto/ecdsacrypto/elliptic 提供了 ECC 相关的操作,但 crypto/ecdsa 主要用于数字签名。对于密钥协商,我们使用更通用的 crypto/ecdh 包(Go 1.20+ 引入,更现代且专门用于 ECDH)。

package main

import (
"crypto/ecdh"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
)

func main() {
// 1. 选择一条椭圆曲线 (这里使用 NIST P-256)
curve := ecdh.P256()

// 模拟 Alice 和 Bob

// 2. 生成密钥对 (Alice)
alicePrivateKey, err := curve.GenerateKey(rand.Reader)
if err != nil {
log.Fatal("Failed to generate Alice's private key:", err)
}
alicePublicKey := alicePrivateKey.PublicKey()

// 2. 生成密钥对 (Bob)
bobPrivateKey, err := curve.GenerateKey(rand.Reader)
if err != nil {
log.Fatal("Failed to generate Bob's private key:", err)
}
bobPublicKey := bobPrivateKey.PublicKey()

fmt.Printf("Alice's Public Key: %s...\n", hex.EncodeToString(alicePublicKey.Bytes())[:64])
fmt.Printf("Bob's Public Key: %s...\n", hex.EncodeToString(bobPublicKey.Bytes())[:64])
fmt.Println("(Note: Public keys are being exchanged over an insecure channel)")

// 3. 交换公钥并计算共享秘密 (Alice 端)
// Alice 使用自己的私钥和 Bob 的公钥计算共享秘密
aliceSharedSecret, err := alicePrivateKey.ECDH(bobPublicKey)
if err != nil {
log.Fatal("Alice failed to compute shared secret:", err)
}

// 3. 交换公钥并计算共享秘密 (Bob 端)
// Bob 使用自己的私钥和 Alice 的公钥计算共享秘密
bobSharedSecret, err := bobPrivateKey.ECDH(alicePublicKey)
if err != nil {
log.Fatal("Bob failed to compute shared secret:", err)
}

// 4. 验证共享秘密是否相同
// 在真实应用中,共享秘密不会直接打印或比较,而是直接用于派生密钥。
// 这里为了演示,我们比较一下。
fmt.Printf("\nAlice's Shared Secret: %s\n", hex.EncodeToString(aliceSharedSecret))
fmt.Printf("Bob's Shared Secret: %s\n", hex.EncodeToString(bobSharedSecret))

if hex.EncodeToString(aliceSharedSecret) == hex.EncodeToString(bobSharedSecret) {
fmt.Println("\n✅ Success! Shared secrets match.")
} else {
fmt.Println("\n❌ Error! Shared secrets do not match.")
}

// 5. 演示如何使用共享密钥(派生为 AES 密钥)
// 重要:在真实应用中,必须使用 KDF(如HKDF)从共享秘密派生密钥!
// 这里为了简单,我们直接取共享秘密的前 32 字节作为 AES-256 密钥。
// 警告:这不是标准做法,仅用于演示。请始终使用 HKDF。

aesKeyForAlice := deriveAESKey(aliceSharedSecret)
aesKeyForBob := deriveAESKey(bobSharedSecret)

fmt.Printf("\nDerived AES Key (Alice): %s\n", hex.EncodeToString(aesKeyForAlice))
fmt.Printf("Derived AES Key (Bob): %s\n", hex.EncodeToString(aesKeyForBob))

// 现在,Alice 和 Bob 都有了相同的 AES 密钥 (aesKeyForAlice == aesKeyForBob)
// 他们可以用这个密钥进行对称加密和解密。
// example: ciphertext := aesGCMEncrypt(aesKeyForAlice, plaintext)
// plaintext := aesGCMDecrypt(aesKeyForBob, ciphertext)
}

// deriveAESKey 从一个共享秘密派生一个固定长度的密钥。
// 警告:这是一个极度简化的示例。在生产环境中,你必须使用
// 一个标准的 KDF(如 crypto/hkdf)并加入盐(salt)和上下文信息(info)。
func deriveAESKey(sharedSecret []byte) []byte {
// 我们只需要 AES-256 的 32 字节密钥。
// 简单起见,取共享秘密的前 32 字节。
// !!!安全警告:请勿在生产环境中这样操作 !!!
key := make([]byte, 32)
copy(key, sharedSecret[:32])
return key
}

运行结果示例:

Alice's Public Key: 04b89f25d75e79f27d26d6e2a63f4e2e7c6e6e8e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7e6e6e7...
Bob's Public Key: 04a76f45c23e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5...
(Note: Public keys are being exchanged over an insecure channel)

Alice's Shared Secret: 5a1e7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b
Bob's Shared Secret: 5a1e7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b

✅ Success! Shared secrets match.

Derived AES Key (Alice): 5a1e7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b
Derived AES Key (Bob): 5a1e7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b


Golang中协商出来的密钥是如何使用的

协商出的最终密钥(如上面代码中的 aesKeyForAliceaesKeyForBob)是一个字节序列([]byte),它作为对称密钥使用。最常见的用途是:

1. 用于对称加密/解密(例如 AES):

一旦 Alice 和 Bob 有了相同的 AES 密钥,他们就可以使用任何 AES 操作模式(如 GCM、CBC)来加密通信。

  • Alice 想给 Bob 发送一条消息:
    1. Alice 使用 aesKeyForAlice 和 AES-GCM 算法加密明文 plainText,得到密文 ciphertext 和一个认证标签 authTag
    2. Alice 将 (ciphertext, authTag) 发送给 Bob。
  • Bob 收到消息后:
    1. Bob 使用 aesKeyForBob(与 aesKeyForAlice 相同)、收到的 ciphertextauthTag,使用 AES-GCM 算法进行解密和认证。
    2. 如果认证成功(说明消息未被篡改),则得到原始的 plainText

Golang 代码片段示例(使用 crypto/aescrypto/cipher):

// 注意:以下代码需要导入相应的包
// import (
// "crypto/aes"
// "crypto/cipher"
// )

func aesGCMEncrypt(key, plaintext []byte) ([]byte, []byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, err
}
// 生成一个随机 Nonce
nonce := make([]byte, aesgcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, nil, err
}
// 加密并认证
ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)
// 返回密文(包含认证标签)和 nonce
// 在实际协议中,需要将 nonce 随密文一起发送
return ciphertext, nonce, nil
}

func aesGCMDecrypt(key, ciphertext, nonce []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// 解密并认证
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}

// 使用方式:
// ciphertext, nonce, err := aesGCMEncrypt(aesKeyForAlice, []byte("Hello Bob!"))
// plaintext, err := aesGCMDecrypt(aesKeyForBob, ciphertext, nonce)

2. 用于其他对称加密算法:
这个密钥同样可以用于 ChaCha20-Poly1305 等其他对称加密算法,使用方式与 AES-GCM 类似。

关键点总结:

  1. 用途:ECDH 协商出的密钥直接用于对称加密,保护后续通信的机密性和完整性。
  2. 密钥派生(KDF)是必须的:生产环境一定要使用 HKDF 等标准函数从共享秘密派生密钥,而不是简单地截取字节。这可以防止某些攻击并确保密钥质量。
    import "golang.org/x/crypto/hkdf"
    import "crypto/sha256"
    ...
    // 使用 HKDF 的示例
    hkdf := hkdf.New(sha256.New, sharedSecret, nil, nil) // 可加入 salt 和 info
    derivedKey := make([]byte, 32) // AES-256 key
    if _, err := io.ReadFull(hkdf, derivedKey); err != nil {
    log.Fatal(err)
    }
  3. 前向保密(Forward Secrecy):如果每次会话都生成临时的 ECC 密钥对(Ephemeral Key),那么一次会话的密钥泄露不会导致其他历史会话的加密通信被解密。这是现代安全通信(如 TLS)的标配。上面的示例代码使用的就是临时密钥对。

Java 中 ECC 算法参数与 P、A、B、G 的相互转换

package com.dp.gateway;

import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;

public class ECCParamsConverterWithBC {

static {
// 添加 Bouncy Castle 作为安全提供者
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}

/**
* 从已知参数创建 BC 的 ECParameterSpec
*/
public static org.bouncycastle.jce.spec.ECParameterSpec createBCParamsFromValues(
BigInteger p, BigInteger a, BigInteger b,
BigInteger gx, BigInteger gy, BigInteger n, int h) {

// 创建有限域曲线
ECCurve curve = new ECCurve.Fp(p, a, b);

// 创建基点
ECPoint g = curve.createPoint(gx, gy);

// 创建 BC 的参数规范
return new org.bouncycastle.jce.spec.ECParameterSpec(
curve, g, n, BigInteger.valueOf(h));
}

/**
* 从标准 JCE 参数转换为 BC 参数
*/
public static org.bouncycastle.jce.spec.ECParameterSpec convertToBCParams(
java.security.spec.ECParameterSpec jceParams) {

// 获取曲线参数
EllipticCurve jceCurve = jceParams.getCurve();
ECFieldFp field = (ECFieldFp) jceCurve.getField();
BigInteger p = field.getP();
BigInteger a = jceCurve.getA();
BigInteger b = jceCurve.getB();

// 创建 BC 曲线
ECCurve bcCurve = new ECCurve.Fp(p, a, b);

// 转换基点
java.security.spec.ECPoint jceG = jceParams.getGenerator();
ECPoint bcG = bcCurve.createPoint(jceG.getAffineX(), jceG.getAffineY());

// 创建 BC 参数
return new org.bouncycastle.jce.spec.ECParameterSpec(
bcCurve, bcG, jceParams.getOrder(),
BigInteger.valueOf(jceParams.getCofactor()));
}

/**
* 从 BC 参数转换为标准 JCE 参数
*/
public static java.security.spec.ECParameterSpec convertToJCEParams(
org.bouncycastle.jce.spec.ECParameterSpec bcParams) {

// 获取曲线参数
ECCurve bcCurve = bcParams.getCurve();
BigInteger p = ((SecP256R1Curve) bcCurve).getQ(); // 对于素数域曲线,Q 就是 P
BigInteger a = bcCurve.getA().toBigInteger();
BigInteger b = bcCurve.getB().toBigInteger();

// 创建 JCE 曲线
ECFieldFp field = new ECFieldFp(p);
EllipticCurve jceCurve = new EllipticCurve(field, a, b);

// 转换基点
ECPoint bcG = bcParams.getG();
java.security.spec.ECPoint jceG = new java.security.spec.ECPoint(
bcG.getAffineXCoord().toBigInteger(),
bcG.getAffineYCoord().toBigInteger());

// 创建 JCE 参数
return new java.security.spec.ECParameterSpec(
jceCurve, jceG, bcParams.getN(), bcParams.getH().intValue());
}

/**
* 从命名的曲线获取参数
*/
public static org.bouncycastle.jce.spec.ECParameterSpec getNamedCurveParams(String curveName) {
return org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec("secp256r1");
}

/**
* 提取 BC 参数中的各个组件
*/
public static void extractBCParams(org.bouncycastle.jce.spec.ECParameterSpec bcParams) {
ECCurve curve = bcParams.getCurve();

// 提取域参数
if (curve instanceof ECCurve.Fp) {
ECCurve.Fp fpCurve = (ECCurve.Fp) curve;
BigInteger p = fpCurve.getQ();
System.out.println("素数 P: " + p.toString(16));
}

// 提取曲线系数
BigInteger a = curve.getA().toBigInteger();
BigInteger b = curve.getB().toBigInteger();
System.out.println("系数 A: " + a.toString(16));
System.out.println("系数 B: " + b.toString(16));

// 提取基点
ECPoint g = bcParams.getG();
BigInteger gx = g.getAffineXCoord().toBigInteger();
BigInteger gy = g.getAffineYCoord().toBigInteger();
System.out.println("基点 Gx: " + gx.toString(16));
System.out.println("基点 Gy: " + gy.toString(16));

// 提取阶和辅因子
BigInteger n = bcParams.getN();
BigInteger h = bcParams.getH();
System.out.println("阶 N: " + n.toString(16));
System.out.println("辅因子 H: " + h.toString());
}

/**
* 创建密钥对从参数
*/
public static void createKeyPairFromParams(org.bouncycastle.jce.spec.ECParameterSpec bcParams)
throws Exception {

// 生成私钥
BigInteger privateValue = new BigInteger("1234567890ABCDEF", 16); // 示例私钥
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateValue, bcParams);

// 计算公钥
ECPoint publicPoint = bcParams.getG().multiply(privateValue);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(publicPoint, bcParams);

// 创建密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");

// 生成密钥
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

System.out.println("私钥值: " + privateValue.toString(16));
System.out.println("私钥: " + privateKey.toString());
System.out.println("公钥: " + publicKey.toString());
// System.out.println("公钥 X: " + publicPoint.getAffineXCoord().toBigInteger().toString(16));
// System.out.println("公钥 Y: " + publicPoint.getAffineYCoord().toBigInteger().toString(16));
}

/**
* 示例:使用 secp256r1 曲线
*/
public static void demoWithSecp256r1() throws Exception {
System.out.println("=== secp256r1 曲线参数 ===");

// 获取 secp256r1 曲线参数
org.bouncycastle.jce.spec.ECParameterSpec bcParams = getNamedCurveParams("secp256r1");

// 提取并显示参数
extractBCParams(bcParams);

// 转换为 JCE 参数
java.security.spec.ECParameterSpec jceParams = convertToJCEParams(bcParams);
System.out.println("\n转换为 JCE 参数成功");

// 从参数创建密钥对
System.out.println("\n从参数创建密钥对:");
createKeyPairFromParams(bcParams);
}

/**
* 示例:自定义曲线参数
*/
public static void demoWithCustomCurve() throws Exception {
System.out.println("\n=== 自定义曲线参数 ===");

// 定义自定义曲线参数 (示例参数,实际应用中应使用标准曲线)
BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF", 16);
BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC", 16);
BigInteger b = new BigInteger("1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45", 16);
BigInteger gx = new BigInteger("4A96B5688EF573284664698968C38BB913CBFC82", 16);
BigInteger gy = new BigInteger("23A628553168947D59DCC912042351377AC5FB32", 16);
BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5FB1A724", 16);
int h = 1;

// 创建 BC 参数
org.bouncycastle.jce.spec.ECParameterSpec bcParams =
createBCParamsFromValues(p, a, b, gx, gy, n, h);

// 提取并显示参数
extractBCParams(bcParams);

// 从参数创建密钥对
System.out.println("\n从自定义参数创建密钥对:");
createKeyPairFromParams(bcParams);
}

/**
* 示例:使用 X9ECParameters
*/
public static void demoWithX9Parameters() {
System.out.println("\n=== 使用 X9ECParameters ===");

// 从命名曲线获取 X9 参数
X9ECParameters x9Params = ECNamedCurveTable.getByName("secp256k1");

// 提取参数
ECCurve curve = x9Params.getCurve();
BigInteger p = ((ECCurve.Fp) curve).getQ();
BigInteger a = curve.getA().toBigInteger();
BigInteger b = curve.getB().toBigInteger();

ECPoint g = x9Params.getG();
BigInteger gx = g.getAffineXCoord().toBigInteger();
BigInteger gy = g.getAffineYCoord().toBigInteger();

BigInteger n = x9Params.getN();
BigInteger h = x9Params.getH();

System.out.println("曲线: secp256k1 (比特币使用的曲线)");
System.out.println("素数 P: " + p.toString(16));
System.out.println("系数 A: " + a.toString(16));
System.out.println("系数 B: " + b.toString(16));
System.out.println("基点 Gx: " + gx.toString(16));
System.out.println("基点 Gy: " + gy.toString(16));
System.out.println("阶 N: " + n.toString(16));
System.out.println("辅因子 H: " + h.toString());
}

public static void main(String[] args) throws Exception {
// 演示使用标准曲线
demoWithSecp256r1();

// 演示使用自定义曲线
demoWithCustomCurve();

// 演示使用 X9ECParameters
demoWithX9Parameters();
}
}

运行结果为

=== secp256r1 曲线参数 ===
系数 A: ffffffff00000001000000000000000000000000fffffffffffffffffffffffc
系数 B: 5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
基点 Gx: 6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
基点 Gy: 4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
阶 N: ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
辅因子 H: 1

转换为 JCE 参数成功

从参数创建密钥对:
私钥值: 1234567890abcdef
私钥: EC Private Key [61:21:b0:43:d9:01:76:59:8b:9e:dd:10:f2:f0:32:4d:89:9f:d2:21]
X: 9fad84aeae08bbef7f010014d82cef6a09de2b0cf871b5ce0c4f1d13a59a5934
Y: 7cb45769f1070e2c2470fe5b1bfe63133c0b0cdc64ea4bf3791a8ec2a07fd4f

公钥: EC Public Key [61:21:b0:43:d9:01:76:59:8b:9e:dd:10:f2:f0:32:4d:89:9f:d2:21]
X: 9fad84aeae08bbef7f010014d82cef6a09de2b0cf871b5ce0c4f1d13a59a5934
Y: 7cb45769f1070e2c2470fe5b1bfe63133c0b0cdc64ea4bf3791a8ec2a07fd4f


=== 自定义曲线参数 ===
素数 P: ffffffffffffffffffffffffffffffff7fffffff
系数 A: ffffffffffffffffffffffffffffffff7ffffffc
系数 B: 1c97befc54bd7a8b65acf89f81d4d4adc565fa45
基点 Gx: 4a96b5688ef573284664698968c38bb913cbfc82
基点 Gy: 23a628553168947d59dcc912042351377ac5fb32
阶 N: fffffffffffffffffffffffffffffffe5fb1a724
辅因子 H: 1

从自定义参数创建密钥对:
私钥值: 1234567890abcdef
私钥: EC Private Key [c6:d2:a8:54:5e:cd:7c:27:18:b8:28:d9:71:bb:d1:b8:d2:d2:0e:63]
X: 6b364242b655e56cea164fc1455b5a5285e209ea
Y: 26a63389f6bfd4e1f0c567290b5ccc246969da7

公钥: EC Public Key [c6:d2:a8:54:5e:cd:7c:27:18:b8:28:d9:71:bb:d1:b8:d2:d2:0e:63]
X: 6b364242b655e56cea164fc1455b5a5285e209ea
Y: 26a63389f6bfd4e1f0c567290b5ccc246969da7


=== 使用 X9ECParameters ===
曲线: secp256k1 (比特币使用的曲线)
素数 P: fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
系数 A: 0
系数 B: 7
基点 Gx: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
基点 Gy: 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
阶 N: fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
辅因子 H: 1