Generate EC public key from byte array private key in native java (7+)

偶尔善良 提交于 2019-12-03 21:13:31

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.

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.

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.

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

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.

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

which produces output:

p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663
Gx=(hex)79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Gy=(hex)483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
again p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663

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.

ADDED in response to comments:

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().

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!