从私钥派生 ECDSA 公钥

Posted

技术标签:

【中文标题】从私钥派生 ECDSA 公钥【英文标题】:Deriving ECDSA Public Key from Private Key 【发布时间】:2018-03-10 02:38:07 【问题描述】:

我试图从私钥生成公共 ECDSA 密钥,但我没有设法在互联网上找到关于如何执行此操作的太多帮助。几乎所有东西都是为了从公钥规范生成公钥,我不知道如何得到它。到目前为止,这是我整理的:

public void setPublic() throws GeneralSecurityException 
    ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
    KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
    ECCurve curve = params.getCurve();
    java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
    java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, this.privateKey.getEncoded());
    java.security.spec.ECParameterSpec params2=EC5Util.convertSpec(ellipticCurve, params);
    java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point,params2);
    this.publicKey = fact.generatePublic(keySpec);

但是,在运行时,我收到以下错误:

Exception in thread "main" java.lang.IllegalArgumentException: Invalid point encoding 0x30
at org.bouncycastle.math.ec.ECCurve.decodePoint(Unknown Source)
at org.bouncycastle.jce.ECPointUtil.decodePoint(Unknown Source)
at Wallet.Wallet.setPublic(Wallet.java:125)

我做错了什么?有没有更好/更简单的方法来做到这一点?

编辑:我已经设法编译了一些代码,但它不能正常工作:

public void setPublic() throws GeneralSecurityException 
    BigInteger privKey = new BigInteger(getHex(privateKey.getEncoded()),16);
    X9ECParameters ecp = SECNamedCurves.getByName("secp256k1");
    ECPoint curvePt = ecp.getG().multiply(privKey);
    BigInteger x = curvePt.getX().toBigInteger();
    BigInteger y = curvePt.getY().toBigInteger();
    byte[] xBytes = removeSignByte(x.toByteArray());
    byte[] yBytes = removeSignByte(y.toByteArray());
    byte[] pubKeyBytes = new byte[65];
    pubKeyBytes[0] = new Byte("04");
    System.arraycopy(xBytes, 0, pubKeyBytes, 1, xBytes.length);
    System.arraycopy(yBytes, 0, pubKeyBytes, 33, xBytes.length);




    ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
    KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
    ECCurve curve = params.getCurve();
    java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
    java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, pubKeyBytes);
    java.security.spec.ECParameterSpec params2 = EC5Util.convertSpec(ellipticCurve, params);
    java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point,params2);
    this.publicKey = fact.generatePublic(keySpec);


private byte[] removeSignByte(byte[] arr)

    if(arr.length==33)
    
        byte[] newArr = new byte[32];
        System.arraycopy(arr, 1, newArr, 0, newArr.length);
        return newArr;
    
    return arr;

当我运行它时,它会生成一个公钥,但它与私钥对应的不是同一个。

【问题讨论】:

我假设你已经阅读了this。 @DevilsHnd 解释了如何从 PublicKeySpec 生成公钥,据我所见,它给出的其余示例是生成一个新的随机密钥。在我的情况下,我需要从输入的私钥中恢复现有的密钥对。 也许将问题的标题改为“Deriving ECDSA Public Key”会更清楚 也许是这样,但在第一个代码段中,您甚至认为私钥由一个点组成,但事实并非如此。私钥的编码由具有许多元素的 SEQUENCE 组成;试图解析这一点当然是无稽之谈。 removeSignByte 也不完整:坐标 x 和 y 也可能小于 32 字节(尝试查找也用于 RSA 的 I2OSP 函数)。也就是说,如果规范化,代码应该可以工作。无论如何,你假设了很多,但我很高兴你现在有了一些工作。 也许您缺少的是... ECDSA 私钥是一个随机的整数。在我使用的库中,它通常表示为x。公钥是G ^ x,其中G 是基。因此,将一个点乘以整数会产生一个点。 y = G ^ x 的结果点是您的公钥。您可能缺少的另一部分是,组中的求幂是乘法(所有组都有两个操作;通常是加法和乘法)。所以寻找exponentiate(...)multiply(...) 方法。分享y,保持x不公开。 【参考方案1】:

所以过了一会儿,我想出了一个解决方案并决定发布它以防其他人遇到与我相同的问题:

KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
    ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");

    ECPoint Q = ecSpec.getG().multiply(((org.bouncycastle.jce.interfaces.ECPrivateKey) this.privateKey).getD());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(Q, ecSpec);
    PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
    this.publicKey = publicKeyGenerated;

编辑:根据@MaartenBodewes 评论删除了解码 ECPoint 的代码。

【讨论】:

您应该可以直接使用Q 而不是point。如果失败,请在计算末尾添加一个方法调用normalize()【参考方案2】:

在上一个答案的基础上,可以将其扩展到所有一个都是满足 BCECPrivateKey 接口的私钥的情况:

DerivePubKeyFromPrivKey(BCECPrivateKey definingKey, Provider provider) 

    KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);

    BigInteger d = definingKey.getD();
    org.bouncycastle.jce.spec.ECParameterSpec ecSpec = 
    definingKey.getParameters();
    ECPoint Q = definingKey.getParameters().getG().multiply(d);

    org.bouncycastle.jce.spec.ECPublicKeySpec pubSpec = new 
    org.bouncycastle.jce.spec.ECPublicKeySpec(Q, ecSpec);
    PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
    return publicKeyGenerated;

【讨论】:

【参考方案3】:

我在 Kotlin 中遇到了同样的问题,所以,如果它对任何人有帮助,以下是我从 Kotlin 中的 ECDSA PrivateKey 导出 PublicKey 的方法。此代码基于Lev Knoblock's Java answer,然后反复试验,直到它起作用。

就我而言,我知道我的密钥使用了secp256k1 曲线,所以我可以对那部分进行硬编码。我没有费心去学习如何将它推广到其他曲线。

import java.security.KeyFactory
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECPrivateKey
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec
import org.bouncycastle.jce.spec.ECPublicKeySpec
import org.bouncycastle.math.ec.ECPoint
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.openssl.PEMParser

fun getECPrivateKeyFromPEM(privatePem: String): ECPrivateKey 
    val pemParser = PEMParser(privatePem.reader())
    val privateKeyInfo = pemParser.readObject() as PrivateKeyInfo
    return JcaPEMKeyConverter().getPrivateKey(privateKeyInfo) as ECPrivateKey


fun getKeyPairFromECPrivateKey(privateKey: ECPrivateKey): KeyPair 
    val keyFactory: KeyFactory = KeyFactory.getInstance("ECDSA", "BC")
    val spec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
    val Q: ECPoint = spec.getG().multiply(privateKey.getD())
    val publicKey: PublicKey = keyFactory.generatePublic(ECPublicKeySpec(Q, spec))
    return KeyPair(publicKey, privateKey)

这是我的测试工具:

import java.io.StringWriter
import org.bouncycastle.openssl.jcajce.JcaPEMWriter

fun main() 
    val privatePem = """
       |-----BEGIN PRIVATE KEY-----
       |MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg55EMdhNJX+YN/bjN
       |Eof91oKqEqD0QidEsRMhHBwSRjShRANCAARnSFpE0LDugORBWlSJz0Zf9e0mR9s6
       |tlxSeo1Nbd2vv9LDedm+l/CfZpbyYvPm49DAKDhkUHFIVDd2SsiPrRa7
       |-----END PRIVATE KEY-----
    """.trimMargin()
    val privateKey: ECPrivateKey = getECPrivateKeyFromPEM(privatePem)
    val pair: KeyPair = getKeyPairFromECPrivateKey(privateKey)
    val pems: String = StringWriter().use 
        val pemWriter = JcaPEMWriter(it)
        pemWriter.writeObject(pair.getPublic())
        pemWriter.writeObject(pair.getPrivate())
        pemWriter.flush()
        it.toString()
    
    println(pems)

打印出来:

-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcE4HMAHLDvPr6xHKsjhPXJzTdxLlRRR8
BfYnI2TGb0QLTFyyXm13CeYiLnxbkGhSvz9ZRo0zGQygKPVpgiThSw==
-----END PUBLIC KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOeRDHYTSV/mDf24zRKH/daCqhKg9EInRLETIRwcEkY0oAoGCCqGSM49
AwEHoUQDQgAEZ0haRNCw7oDkQVpUic9GX/XtJkfbOrZcUnqNTW3dr7/Sw3nZvpfw
n2aW8mLz5uPQwCg4ZFBxSFQ3dkrIj60Wuw==
-----END EC PRIVATE KEY-----

【讨论】:

嘿!你能帮我加密/解密吗?使用私有/公共 pem 文件?

以上是关于从私钥派生 ECDSA 公钥的主要内容,如果未能解决你的问题,请参考以下文章

从私钥生成的公钥在 2 种情况下不同

PHP:从私钥字符串/文件中获取 RSA 公钥

sh 从私钥生成rsa公钥

sh 从私钥生成rsa公钥

Javascript ECDSA 获取私钥和​​公钥?

如何在 GoLang 中分离编组的 ecdsa 公钥和私钥