从本机Java(7+)中的字节数组私钥生成EC公钥 [英] Generate EC public key from byte array private key in native java (7+)

查看:99
本文介绍了从本机Java(7+)中的字节数组私钥生成EC公钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to learn some crypto coding and have generated a 32-byte private key currently held in a byte array (byte[] privatekey). I know the public key is generated using the secp256k1 named elliptic curve parameters, and a formula where publickey = G * privatekey, where G is some point on the elliptic curve (ECPoint?), but I am unable to transfer that named parameter spec and formula into actual coding of a public key. I know that since java 7, there are classes included in the java.security.* and java.security.spec.* packages to do this in short code, but I can't find a good example showing how to do this without using a third party library.

此比特币stackexchange链接具有所有理论上的答案以及出色的python和C#代码,但Java中没有任何内容.

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

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

Edit/Update: I have tried to get what I need with the following code:

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));

应使用正确的编码生成的公钥是:

The publickey that should be generated with correct coding is this:

04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495

04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6 fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495

但是正在生成的公钥是这样的:

but the public key that is being generated is this:

4E6801418BB6EF9F462F69830F82EB51BB9224219B9D89C8C34FB746297F59779D8B986194181BD7AB99DC7E3086914EA13C4B37E05716CADCA0AE391CE81C4B85E0F09E8628F0F81692B5D08D08899

4E6801418BB6EF9F462F69830F82EB51BB9224219B9D89C8C34FB746297F59779D8B986194181BD7AB99DC7E3086914EA13C4B37E05716CADCA0AE391CE81C4B85E0F09E8628F0F81692B5D08D0D8B9E20615A5D23DE0F591D02C650554BB1D8

推荐答案

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

An elliptic curve point is not an integer. Putting the encoded representation of a point (G) in a BigInteger and trying to use it as an integer is nowhere near correct. Elliptic curve point multiplication is not integer multiplication, and is nowhere near as simple as BigInteger.multiply. And it is written with the scalar on the left, e.g. kG not Gk.

对于任何Java程序员来说,将比特币Q上给出的标准(或至少是常规的)算法转换为Java确实是一个相当简单的练习.

Translating the standard (or at least conventional) algorithms given at the bitcoin Q to Java really should be a reasonably simple exercise for any Java programmer.

椭圆曲线上点的标量乘法包含(在答案)对于P192又称为secp192r1的正确实现;可以通过将p和a替换为规范中的值将其转换为secp256k1(SEC2来自 https://www.secg.org 或X9.62(如果有的话)或任何现有的实现(包括Java(请参见下文)),并丢弃特定于P192的测试数据.实际上,您最需要更改p;Koblitz曲线选择为a = 0.椭圆曲线乘法功能包含一种不太正确的实现,据说该实现适用于secp256k1,但实际上不包含任何曲线的常数.

Scalar Multiplication of Point over elliptic Curve contains (in the answer) a correct implementation for P192 aka secp192r1; it can be converted to secp256k1 by replacing p and a with the values from the spec (SEC2 from https://www.secg.org or X9.62 if you have it) or any existing implementation -- including Java (see below) -- and discarding the P192-specific test data. Actually you mostly need to change p; the Koblitz curves were chosen to have a=0. Elliptic Curve Multiplication Function contains a not-quite-correct implementation that is stated to be for secp256k1 but doesn't actually include the constants for any curve.

从Java 7开始,java.security.*和java.security.spec.*程序包中包含一些类,可以用短代码来做到这一点

since java 7, there are classes included in the java.security.* and java.security.spec.* packages to do this in short code

并非如此.首先,Java crypto将实现代码中的 java.security javax.crypto 中看到的类隔离开来,实现代码是完全不同的类(大多数情况下(仍然))> sun.* 和 com.sun.* )放在一个或多个提供者"中,这些提供者"是单独的jar,在技术上是可选的;尽管大多数人不会,但是可以在不更改代码调用的情况下删除,添加或更改提供程序.自从Java 5(称为1.5)以来,就出现了EC加密的JCA'facade'类,但是标准构建中没有包含实现EC算法的提供程序;要使用它们,您必须添加第三方提供程序.从Java 7开始,包括一个标准的SunEC提供程序.但是,JCA(不仅针对EC,对于所有算法)在生成后严格将私钥和公钥分开,尤其是它无法提供访问EC内部存在的私对公派生逻辑的方法.

Not really. First, Java crypto isolates the classes you see in java.security and javax.crypto from the implementation code, which is in entirely different classes (mostly (still) under sun.* and com.sun.*) in one or more 'providers' which are separate jars and technically optional; it is possible to remove, add, or change providers without changing the calls in your code, although most people don't. The JCA 'facade' classes for EC crypto were present since java 5 (called 1.5), but no provider implementing EC algorithms was included in the standard build; to use them you had to add a third-party provider. Starting in java 7 a standard SunEC provider IS included. However, JCA (for all algorithms not just EC) keeps private and public keys strictly separate after generation, and in particular it provides no way to access the private-to-public derivation logic that exists internally for EC.

它确实包含一些标准曲线的参数,包括secp256k1,您可以使用它们来避免从规格中复制它们的工作.似乎没有直接的方法可以访问此数据,但是您可以通过生成一个随机数密钥并将其丢弃来间接地进行访问.另外,由于您已经有一个私钥,因此可以创建Java使用的编码(PKCS8)并将其读入,从而产生相同的曲线参数和一个可用的密钥.通常,构造ASN.1 DER编码(例如PKCS8)相当复杂,但是对于EC 却被简化了,因为(1)每个人都使用命名"形式将曲线编码为单个OID,(2)该标准规定了私有值的编码,该私有值在给定曲线上的长度是固定的;结果,给定EC曲线的PKCS8编码由固定前缀和私钥值组成.示例片段:

It does include the parameters for several standard curves, including secp256k1, which you could use to avoid the effort of copying them from the spec. There doesn't appear to be a direct way to access this data, but you can do so indirectly by generating a nonce key and discarding it. Alternatively, since you already have a private key you can create the encoding Java uses (PKCS8) and read that in, producing the same curve parameters and also a usable key. In general constructing ASN.1 DER encodings like PKCS8 is fairly complicated, but for EC it is simplified because (1) everybody uses the 'named' form which encodes the curve as a single OID and (2) the standard specifies an encoding of the private value which is fixed in length for a given curve; as a result the PKCS8 encoding for a given EC curve consists of a fixed prefix followed by the private key value. Example snippets:

    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的那些坐标符合您的期望.我显示了十进制和十六进制的混合形式,只是为了显示可能性.这不会影响计算机中的实际数字.

Note those coordinates for the basepoint G match your expectation. I displayed a mix of decimal and hex just to show the possibilities; this does not affect the actual numbers in the computer.

根据评论添加:

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

The variables p and p2 are ECParameterSpec objects which contain the parameters of an EC curve (underlying field, curve coefficients, basepoint aka generator, order and cofactor; and internally 'name' although the API doesn't expose it). The values I print labelled 'p' are the result of calling getP which returns one item from the curve parameters, namely the modulus of the underlying prime field, and thus the value you need to use in the calculations shown in the linked post where it does mod(p) and modInverse(p) and modPow(,p). Since this p (or P) is a parameter of the curve it is the same for all keys on that curve; note the two values I print are the same even though they are from different keys. There are actually two kinds of elliptic curves standardized for cryptography: curves over a prime field, denoted Fp, and curves over an extension field of characteristic two, denoted F2m. secp256k1 is the first kind, that's why the cast to ECFieldFp before calling getP().

是的,我的固定前缀包含标头和字段,这些标头和字段标识用于EC和secp256k1的私钥(PKCS8)编码,并且该前缀对于所有EC secp256k1私钥都是相同的.p值如上所述,而不是私钥或公钥.是的,如果您拥有公共点,则可以将其与 ECParameterSpec 组合为 ECPublicKeySpec 并转换并使用它,也可以将点编码附加到类似但不同的固定前缀来获取 X509EncodedKeySpec ,这是Java用于公钥的编码,并且无需预先 ECParameterSpec 即可转换 ,但是整个过程据我了解,问题是您还没有公共点,并且想要导出它,这需要链接的帖子中显示的点乘法计算.

Yes my fixed prefix contains the headers and fields identifying a privatekey (PKCS8) encoding as being for EC and secp256k1 and that prefix is the same for all EC secp256k1 privatekeys. The p values are as described above, and not privatekeys or publickeys. Yes if you had the public point you could combine it with the ECParameterSpec into an ECPublicKeySpec and convert it and use it -- or you could append the point encoding to a similar but different fixed prefix to get an X509EncodedKeySpec which is the encoding Java uses for publickeys and convert that without needing the ECParameterSpec in advance -- but your whole problem, as I understand it, is that you don't have the public point yet and want to derive it, which requires the point multiplication calculation shown in the linked posts.

这篇关于从本机Java(7+)中的字节数组私钥生成EC公钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆