从本机java中的字节数组私钥生成EC公钥(7+;)

从本机java中的字节数组私钥生成EC公钥(7+;),java,public-key,elliptic-curve,Java,Public Key,Elliptic Curve,我试图学习一些加密编码,并生成了一个32字节的私钥,当前保存在字节数组中(byte[]privatekey)。我知道公钥是使用secp256k1命名的椭圆曲线参数和公式生成的,其中publickey=G*privatekey,其中G是椭圆曲线上的某个点(ECPoint?),但我无法将该命名参数规范和公式转换为公钥的实际编码。我知道自java 7以来,java.security.*和java.security.spec.*包中包含了一些类,可以用简短的代码来实现这一点,但我找不到一个好的示例来说明

我试图学习一些加密编码,并生成了一个32字节的私钥,当前保存在字节数组中(byte[]privatekey)。我知道公钥是使用secp256k1命名的椭圆曲线参数和公式生成的,其中
publickey=G*privatekey
,其中G是椭圆曲线上的某个点(ECPoint?),但我无法将该命名参数规范和公式转换为公钥的实际编码。我知道自java 7以来,
java.security.*
java.security.spec.*
包中包含了一些类,可以用简短的代码来实现这一点,但我找不到一个好的示例来说明如何在不使用第三方库的情况下实现这一点

编辑/更新:我已尝试使用以下代码获取所需内容:

String secp256k1_G_uncompressed_string = "0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8";
byte[] secp256k1_G_uncompressed_bytes = DatatypeConverter.parseHexBinary(secp256k1_G_uncompressed_string);
String privatekeystring = "1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD";
byte[] privatekeybytes = DatatypeConverter.parseHexBinary(privatekeystring);
BigInteger secp256k1_G_num = new BigInteger(1, secp256k1_G_uncompressed_bytes);
BigInteger privatekey_num = new BigInteger(1, privatekeybytes);
BigInteger curvepoint = secp256k1_G_num.multiply(privatekey_num);
byte[] publickeybytes = curvepoint.toByteArray();
System.out.println(DatatypeConverter.printHexBinary(privatekeybytes));
System.out.println(DatatypeConverter.printHexBinary(publickeybytes));
使用正确编码生成的公钥如下:

04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6 fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495

但正在生成的公钥是:

4E6801418BB6EF9F462F69830F82EB51BB9224219B9D89C8C34FB746297F59779D8B986194181BD7AB99DC7E3086914EA13C4B37E05716CADC0AE391CE81C4B85E0F09E8628F0F81692B5D08D0D8B9E20615A5D23DE0F591D02C65054BB1D8


椭圆曲线点不是整数。将点(G)的编码表示形式放在
biginger
中并尝试将其用作整数是不正确的。椭圆曲线点乘法不是整数乘法,它远没有biginger.multiply那么简单。它是用左边的标量写的,例如kG而不是Gk

对于任何Java程序员来说,将比特币Q上给出的标准(或至少是传统)算法翻译成Java应该是一个相当简单的练习

包含(在答案中)P192又名secp192r1的正确实现;通过使用规范(SEC2 from或X9.62,如果有的话)或任何现有实现(包括Java(见下文))中的值替换p和a,并丢弃P192特定的测试数据,可以将其转换为secp256k1。实际上你主要需要改变p;选择Koblitz曲线的a=0。包含一个不太正确的实现,该实现声明用于secp256k1,但实际上不包括任何曲线的常数

自Java7以来,java.security.*和java.security.spec.*包中包含了一些类,可以用简短的代码来实现这一点

不是真的。首先,Java crypto将您在
Java.security
javax.crypto
中看到的类与实现代码隔离开来,实现代码位于一个或多个“提供者”中,这些“提供者”是独立的JAR,在技术上是可选的,属于完全不同的类(大部分(仍然)在
sun.
com.sun.
);可以在不更改代码中的调用的情况下删除、添加或更改提供程序,尽管大多数人不这样做。自Java5(称为1.5)以来,就出现了用于EC加密的JCA“facade”类,但标准构建中不包括实现EC算法的提供者;要使用它们,您必须添加第三方提供商。从Java7开始,包含一个标准的SunEC提供程序。然而,JCA(对于所有算法,而不仅仅是EC)在生成后将私钥和公钥严格分开,特别是它无法访问EC内部存在的从私钥到公钥的派生逻辑

它确实包括几个标准曲线的参数,包括secp256k1,您可以使用这些参数来避免从规范中复制这些参数。似乎没有直接的方法来访问这些数据,但您可以通过生成一个nonce键并丢弃它来间接地访问这些数据。或者,由于您已经拥有一个私钥,因此可以创建Java使用的编码(PKCS8)并读入,从而生成相同的曲线参数和可用密钥。一般来说,构造像PKCS8这样的ASN.1 DER编码相当复杂,但对于EC,它被简化了,因为(1)每个人都使用“命名”形式,将曲线编码为单个OID,(2)标准规定了私有值的编码,该值在给定曲线的长度上是固定的;因此,给定EC曲线的PKCS8编码由固定前缀和私钥值组成。示例代码片段:

    KeyPairGenerator kg = KeyPairGenerator.getInstance ("EC");
    kg.initialize (new ECGenParameterSpec ("secp256k1"));
    ECParameterSpec p = ((ECPublicKey) kg.generateKeyPair().getPublic()).getParams();
    System.out.println ("p=(dec)" + ((ECFieldFp) p.getCurve().getField()).getP() );
    ECPoint G = p.getGenerator(); 
    System.out.format ("Gx=(hex)%032x%n", G.getAffineX());
    System.out.format ("Gy=(hex)%032x%n", G.getAffineY());
    //
    byte[] privatekey_enc = DatatypeConverter.parseHexBinary(
            "303E020100301006072A8648CE3D020106052B8104000A042730250201010420"+
            "1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD");
    // note fixed prefix for PKCS8-EC-secp256k1 plus your private value
    KeyFactory kf = KeyFactory.getInstance("EC");
    PrivateKey k1 = kf.generatePrivate(new PKCS8EncodedKeySpec(privatekey_enc));
    ECParameterSpec p2 = ((ECPrivateKey) k1).getParams();
    System.out.println ("again p=(dec)" + ((ECFieldFp) p2.getCurve().getField()).getP() );
它产生输出:

p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663
Gx=(hex)79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Gy=(hex)483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
again p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663
请注意,基点G的坐标符合您的期望。我显示了十进制和十六进制的混合,只是为了显示可能性;这不会影响计算机中的实际数字

在回应评论时增加:

变量p和p2是
ECParameterSpec
对象,其中包含EC曲线的参数(基础字段、曲线系数、基点aka生成器、顺序和辅因子;以及内部“名称”,尽管API未公开它)。我打印的标记为“p”的值是调用
getP
的结果,该函数从曲线参数返回一项,即基本素数字段的模,因此,在链接的帖子中显示的计算中,需要使用的值是
mod(p)
modInverse(p)
modPow(,p)
。由于p(或p)是曲线的一个参数,因此该曲线上的所有关键点都是相同的;请注意,我打印的两个值是相同的,即使它们来自不同的键。实际上,有两种椭圆曲线是为密码学而标准化的:素域上的曲线,表示为Fp,和特征为2的扩展域上的曲线,表示为F2m。secp256k1是第一种类型,这就是为什么在调用
getP()
之前强制转换为
ECFieldFp

Yes my fixed prefix包含识别私钥(PKCS8)编码为EC和secp256k1的头和字段,该前缀对于所有EC secp256k1私钥都是相同的。p值如上所述,