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

时间:2018-02-16 17:41:16

标签: java public-key elliptic-curve

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

This bitcoin stackexchange link has all the theoretical answer and great python and C# code, but nothing in Java

编辑/更新:我试图通过以下代码获得我需要的东西:

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

但是生成的公钥是这样的: 4E6801418BB6EF9F462F69830F82EB51BB9224219B9D89C8C34FB746297F59779D8B986194181BD7AB99DC7E3086914EA13C4B37E05716CADCA0AE391CE81C4B85E0F09E8628F0F81692B5D08D0D8B9E20615A5D23DE0F591D02C650554BB1D8

1 个答案:

答案 0 :(得分:1)

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

将比特币Q中给出的标准(或至少是常规)算法转换为Java对任何Java程序员来说都应该是一个相当简单的练习。

Scalar Multiplication of Point over elliptic Curve包含(在答案中)P192又名secp192r1的正确实现;它可以转换为secp256k1,方法是将p和a替换为规范中的值(来自https://www.secg.org的SEC2或X9.62,如果有的话)或任何现有的实现 - 包括Java(见下文) - 并丢弃P192特定的测试数据。实际上你最需要改变p;选择Koblitz曲线得到a = 0。 Elliptic Curve Multiplication Function包含一个非常正确的实现,声明为secp256k1,但实际上并不包含任何曲线的常量。

  

从java 7开始,java.security。*和java.security.spec。*包中包含了用短代码执行此操作的类

不是真的。首先,Java crypto将您在java.securityjavax.crypto中看到的类与实现代码隔离开来,实现代码位于完全不同的类中(大部分(仍在)sun.*com.sun.*下)在一个或多个提供商'这是独立的罐子,技术上是可选的;虽然大多数人都不知道,但可以在不更改代码中的呼叫的情况下删除,添加或更改提供商。 JCA' facade'自从Java 5(称为1.5)以来,EC加密的类已经存在,但是标准构建中没有包含实现EC算法的提供者;要使用它们,您必须添加第三方提供商。从Java 7开始,包括标准的SunEC提供商。但是,JCA(对于所有算法而不仅仅是EC)在生成后将私钥和公钥严格分开,特别是它无法访问EC内部存在的私有 - 公共派生逻辑。

它包括几条标准曲线的参数,包括secp256k1,您可以使用它来避免从规范中复制它们。看起来并不是访问此数据的直接方法,但您可以通过生成随机数密钥并将其丢弃来间接实现。或者,由于您已经拥有私钥,因此可以创建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的坐标符合您的预期。我展示了十进制和十六进制的混合,以显示可能性;这不会影响计算机中的实际数字。

ADDED回应评论:

变量p和p2是ECParameterSpec个对象,它们包含EC曲线的参数(基础字段,曲线系数,基点也称为生成器,顺序和辅助因子;以及内部'名称'尽管API没有公开它)。我打印的值标记为' p'是调用getP从曲线参数返回一个项的结果,即底层素数的模数,因此需要在链接中显示的计算中使用的值发布mod(p)以及modInverse(p)modPow(,p)的位置。由于此p(或P)是曲线的参数,因此该曲线上的所有键都相同;请注意,即使它们来自不同的键,我打印的两个值也是相同的。实际上存在两种用于密码术的椭圆曲线:在素数场上的曲线,表示为Fp,以及在特征二的扩展域上的曲线,表示为F2m。 secp256k1是第一种,这就是在调用ECFieldFp之前强制转换为getP()的原因。

是的,我的固定前缀包含标识私有密钥(PKCS8)编码的标题和字段,用于EC和secp256k1,并且所有EC secp256k1私钥的前缀相同。 p值如上所述,而不是privatekeys或publickeys。是的,如果您有公共点,您可以将其与ECParameterSpec合并为ECPublicKeySpec并转换并使用它 - 或者您可以将点编码附加到类似但不同的固定前缀以获得X509EncodedKeySpec这是Java用于公共密钥的编码,并且 转换 而不需要提前ECParameterSpec - 但是根据我的理解,你的整个问题是你没有&# 39; t还有公共点,并希望得到它,这需要链接帖子中显示的点乘法计算。