I need to implement ECC (Elliptic Curve Cryptography) algorithm using jdk 1.7. I tried using bouncy castle, sunEC, but all of them gave errors and errors. My target is to ge
I wrote a sample program that outputs the following:
FL261:java jvah$ javac -cp bcprov-ext-jdk15on-149.jar ECTest.java
FL261:java jvah$ java -cp bcprov-ext-jdk15on-149.jar:. ECTest
Private key: 7ba78909571fbc336b2b94054dfb745a6b0776ff36a8fa98a598dc32cb83cc8e
Public key: 035b9e4a6148c9f9b08b573871ac66a832e6e9f63cf117545523a45b8017b7c43f
Calculated public key: 035b9e4a6148c9f9b08b573871ac66a832e6e9f63cf117545523a45b8017b7c43f
Congratulations, public keys match!
FL261:java jvah$
The code should be clear enough so that you can understand what is done here. Please note that you really have to know which curve your private key is generated for, otherwise it is impossible to generate the matching public key. The sample code uses secp256r1
curve, which is quite commonly used.
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.math.ec.ECPoint;
class ECTest {
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b: data) {
sb.append(String.format("%02x", b&0xff));
}
return sb.toString();
}
public static void main(String[] argv) {
// Get domain parameters for example curve secp256r1
X9ECParameters ecp = SECNamedCurves.getByName("secp256r1");
ECDomainParameters domainParams = new ECDomainParameters(ecp.getCurve(),
ecp.getG(), ecp.getN(), ecp.getH(),
ecp.getSeed());
// Generate a private key and a public key
AsymmetricCipherKeyPair keyPair;
ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyGenParams);
keyPair = generator.generateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getD().toByteArray();
// First print our generated private key and public key
System.out.println("Private key: " + toHex(privateKeyBytes));
System.out.println("Public key: " + toHex(publicKey.getQ().getEncoded(true)));
// Then calculate the public key only using domainParams.getG() and private key
ECPoint Q = domainParams.getG().multiply(new BigInteger(privateKeyBytes));
System.out.println("Calculated public key: " + toHex(Q.getEncoded(true)));
// The calculated public key and generated public key should always match
if (!toHex(publicKey.getQ().getEncoded(true)).equals(toHex(Q.getEncoded(true)))) {
System.out.println("ERROR: Public keys do not match!");
} else {
System.out.println("Congratulations, public keys match!");
}
}
}
This is an improvement to the answer from @SkateScout. I used the scalar routines in another context, published them here on SO (Derive EC public key from private key string in native Java for curve secp256k1) and got an "error" comment from @President James Moveon Polk:
"That code is incorrect, as is the code you got it from. This line BigInteger k = kin.mod(p); is wrong, wrong, wrong. The scalar multiple should be taken mod the order of the group, which is not the prime over which the field is defined. The only reason you haven't been bitten by that bug is because you never tested it with a kin that was >= p. That's why you don't do crypto by hand. But anyway, the answer to your question is no. If you were exclusively using Java 8 (or maybe 7) you could find an internal sun. class and method that does scalar multiplication, but not beyond that.* "
I changed the source code a little and this is the final code (complete example):
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
public class DerivePublicKeyFromPrivateKeyCurveSecp256k1Final {
// sourcecode available https://github.com/java-crypto/Stackoverflow/tree/master/DervicePublicKeyFromPrivateKeyCurveSecp256k1
// get bouncycastle here: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on/1.65
// tested with version 15 1.65
// java open jdk 11.0.5
final static BigInteger FieldP_2 = BigInteger.TWO; // constant for scalar operations
final static BigInteger FieldP_3 = BigInteger.valueOf(3); // constant for scalar operations
public static void main(String[] args) throws GeneralSecurityException {
System.out.println("Generate ECPublicKey from PrivateKey (String) for curve secp256k1 (final)");
System.out.println("Check keys with https://gobittest.appspot.com/Address");
// https://gobittest.appspot.com/Address
String privateKey = "D12D2FACA9AD92828D89683778CB8DFCCDBD6C9E92F6AB7D6065E8AACC1FF6D6";
String publicKeyExpected = "04661BA57FED0D115222E30FE7E9509325EE30E7E284D3641E6FB5E67368C2DB185ADA8EFC5DC43AF6BF474A41ED6237573DC4ED693D49102C42FFC88510500799";
System.out.println("\nprivatekey given : " + privateKey);
System.out.println("publicKeyExpected: " + publicKeyExpected);
// routine with bouncy castle
System.out.println("\nGenerate PublicKey from PrivateKey with BouncyCastle");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); // this ec curve is used for bitcoin operations
org.bouncycastle.math.ec.ECPoint pointQ = spec.getG().multiply(new BigInteger(1, hexStringToByteArray(privateKey)));
byte[] publickKeyByte = pointQ.getEncoded(false);
String publicKeyBc = byteArrayToHexString(publickKeyByte);
System.out.println("publicKeyExpected: " + publicKeyExpected);
System.out.println("publicKey BC : " + publicKeyBc);
System.out.println("publicKeys match : " + publicKeyBc.contentEquals(publicKeyExpected));
// regeneration of ECPublicKey with java native starts here
System.out.println("\nGenerate PublicKey from PrivateKey with Java native routines");
// the preset "303E.." only works for elliptic curve secp256k1
// see answer by user dave_thompson_085
// https://stackoverflow.com/questions/48832170/generate-ec-public-key-from-byte-array-private-key-in-native-java-7
String privateKeyFull = "303E020100301006072A8648CE3D020106052B8104000A042730250201010420" +
privateKey;
byte[] privateKeyFullByte = hexStringToByteArray(privateKeyFull);
System.out.println("privateKey full : " + privateKeyFull);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKeyNative = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFullByte));
ECPrivateKey ecPrivateKeyNative = (ECPrivateKey) privateKeyNative;
ECPublicKey ecPublicKeyNative = getPublicKey(ecPrivateKeyNative);
byte[] ecPublicKeyNativeByte = ecPublicKeyNative.getEncoded();
String publicKeyNativeFull = byteArrayToHexString(ecPublicKeyNativeByte);
String publicKeyNativeHeader = publicKeyNativeFull.substring(0, 46);
String publicKeyNativeKey = publicKeyNativeFull.substring(46, 176);
System.out.println("ecPublicKeyFull : " + publicKeyNativeFull);
System.out.println("ecPublicKeyHeader: " + publicKeyNativeHeader);
System.out.println("ecPublicKeyKey : " + publicKeyNativeKey);
System.out.println("publicKeyExpected: " + publicKeyExpected);
System.out.println("publicKeys match : " + publicKeyNativeKey.contentEquals(publicKeyExpected));
}
private static String byteArrayToHexString(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for (byte b : a)
sb.append(String.format("%02X", b));
return sb.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
// scalar operations for native java
// see https://stackoverflow.com/a/42797410/8166854
// written by author: SkateScout
private static ECPoint doublePoint(final BigInteger p, final BigInteger a, final ECPoint R) {
if (R.equals(ECPoint.POINT_INFINITY)) return R;
BigInteger slope = (R.getAffineX().pow(2)).multiply(FieldP_3);
slope = slope.add(a);
slope = slope.multiply((R.getAffineY().multiply(FieldP_2)).modInverse(p));
final BigInteger Xout = slope.pow(2).subtract(R.getAffineX().multiply(FieldP_2)).mod(p);
final BigInteger Yout = (R.getAffineY().negate()).add(slope.multiply(R.getAffineX().subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
private static ECPoint addPoint(final BigInteger p, final BigInteger a, final ECPoint r, final ECPoint g) {
if (r.equals(ECPoint.POINT_INFINITY)) return g;
if (g.equals(ECPoint.POINT_INFINITY)) return r;
if (r == g || r.equals(g)) return doublePoint(p, a, r);
final BigInteger gX = g.getAffineX();
final BigInteger sY = g.getAffineY();
final BigInteger rX = r.getAffineX();
final BigInteger rY = r.getAffineY();
final BigInteger slope = (rY.subtract(sY)).multiply(rX.subtract(gX).modInverse(p)).mod(p);
final BigInteger Xout = (slope.modPow(FieldP_2, p).subtract(rX)).subtract(gX).mod(p);
BigInteger Yout = sY.negate().mod(p);
Yout = Yout.add(slope.multiply(gX.subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
public static ECPoint scalmultNew(final ECParameterSpec params, final ECPoint g, final BigInteger kin) {
EllipticCurve curve = params.getCurve();
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) throw new UnsupportedOperationException(field.getClass().getCanonicalName());
final BigInteger p = ((ECFieldFp) field).getP();
final BigInteger a = curve.getA();
ECPoint R = ECPoint.POINT_INFINITY;
// value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf,
// see "Finally the order n of G and the cofactor are: n = "FF.."
BigInteger SECP256K1_Q = params.getOrder();
//BigInteger SECP256K1_Q = new BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16);
BigInteger k = kin.mod(SECP256K1_Q); // uses this !
// BigInteger k = kin.mod(p); // do not use this ! wrong as per comment from President James Moveon Polk
final int length = k.bitLength();
final byte[] binarray = new byte[length];
for (int i = 0; i <= length - 1; i++) {
binarray[i] = k.mod(FieldP_2).byteValue();
k = k.shiftRight(1);
}
for (int i = length - 1; i >= 0; i--) {
R = doublePoint(p, a, R);
if (binarray[i] == 1) R = addPoint(p, a, R, g);
}
return R;
}
public static ECPoint scalmultOrg(final EllipticCurve curve, final ECPoint g, final BigInteger kin) {
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) throw new UnsupportedOperationException(field.getClass().getCanonicalName());
final BigInteger p = ((ECFieldFp) field).getP();
final BigInteger a = curve.getA();
ECPoint R = ECPoint.POINT_INFINITY;
// value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf,
// see "Finally the order n of G and the cofactor are: n = "FF.."
BigInteger SECP256K1_Q = new BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16);
BigInteger k = kin.mod(SECP256K1_Q); // uses this !
// wrong as per comment from President James Moveon Polk
// BigInteger k = kin.mod(p); // do not use this !
System.out.println(" SECP256K1_Q: " + SECP256K1_Q);
System.out.println(" p: " + p);
System.out.println("curve: " + curve.toString());
final int length = k.bitLength();
final byte[] binarray = new byte[length];
for (int i = 0; i <= length - 1; i++) {
binarray[i] = k.mod(FieldP_2).byteValue();
k = k.shiftRight(1);
}
for (int i = length - 1; i >= 0; i--) {
R = doublePoint(p, a, R);
if (binarray[i] == 1) R = addPoint(p, a, R, g);
}
return R;
}
public static ECPublicKey getPublicKey(final ECPrivateKey pk) throws GeneralSecurityException {
final ECParameterSpec params = pk.getParams();
final ECPoint w = scalmultNew(params, pk.getParams().getGenerator(), pk.getS());
//final ECPoint w = scalmult(params.getCurve(), pk.getParams().getGenerator(), pk.getS());
final KeyFactory kg = KeyFactory.getInstance("EC");
return (ECPublicKey) kg.generatePublic(new ECPublicKeySpec(w, params));
}
}
Enjoy!
Some more improvement to the answer of @markw on Generate ECPublicKey from ECPrivateKey, extended with curve name detection by @bas-goossen from here: How to find the matching curve name from an ECPublicKey
public static ECPublicKey publicFromPrivate(final ECPrivateKey privateKey) throws Exception {
ECParameterSpec params = privateKey.getParams();
org.bouncycastle.jce.spec.ECParameterSpec bcSpec = org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util
.convertSpec(params, false);
org.bouncycastle.math.ec.ECPoint q = bcSpec.getG().multiply(privateKey.getS());
org.bouncycastle.math.ec.ECPoint bcW = bcSpec.getCurve().decodePoint(q.getEncoded(false));
ECPoint w = new ECPoint(
bcW.getAffineXCoord().toBigInteger(),
bcW.getAffineYCoord().toBigInteger());
ECPublicKeySpec keySpec = new ECPublicKeySpec(w, tryFindNamedCurveSpec(params));
return (ECPublicKey) KeyFactory
.getInstance("EC", org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME)
.generatePublic(keySpec);
}
@SuppressWarnings("unchecked")
public static ECParameterSpec tryFindNamedCurveSpec(ECParameterSpec params) {
org.bouncycastle.jce.spec.ECParameterSpec bcSpec
= org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertSpec(params, false);
for (Object name : Collections.list(org.bouncycastle.jce.ECNamedCurveTable.getNames())) {
org.bouncycastle.jce.spec.ECNamedCurveParameterSpec bcNamedSpec
= org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec((String) name);
if (bcNamedSpec.getN().equals(bcSpec.getN())
&& bcNamedSpec.getH().equals(bcSpec.getH())
&& bcNamedSpec.getCurve().equals(bcSpec.getCurve())
&& bcNamedSpec.getG().equals(bcSpec.getG())) {
return new org.bouncycastle.jce.spec.ECNamedCurveSpec(
bcNamedSpec.getName(),
bcNamedSpec.getCurve(),
bcNamedSpec.getG(),
bcNamedSpec.getN(),
bcNamedSpec.getH(),
bcNamedSpec.getSeed());
}
}
return params;
}
i use this method to recover ECPublicKey from ECPrivateKey. At the end the main Routine prove that it works. It is plain java without any external library.
public final class MULT {
private static ECPoint doublePoint(final BigInteger p, final BigInteger a, final ECPoint R) {
if (R.equals(ECPoint.POINT_INFINITY)) return R;
BigInteger slope = (R.getAffineX().pow(2)).multiply(FieldP._3);
slope = slope.add(a);
slope = slope.multiply((R.getAffineY().multiply(FieldP._2)).modInverse(p));
final BigInteger Xout = slope.pow(2).subtract(R.getAffineX().multiply(FieldP._2)).mod(p);
final BigInteger Yout = (R.getAffineY().negate()).add(slope.multiply(R.getAffineX().subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
private static ECPoint addPoint (final BigInteger p, final BigInteger a, final ECPoint r, final ECPoint g) {
if (r.equals(ECPoint.POINT_INFINITY)) return g;
if (g.equals(ECPoint.POINT_INFINITY)) return r;
if (r==g || r.equals(g)) return doublePoint(p, a, r);
final BigInteger gX = g.getAffineX();
final BigInteger sY = g.getAffineY();
final BigInteger rX = r.getAffineX();
final BigInteger rY = r.getAffineY();
final BigInteger slope = (rY.subtract(sY)).multiply(rX.subtract(gX).modInverse(p)).mod(p);
final BigInteger Xout = (slope.modPow(FieldP._2, p).subtract(rX)).subtract(gX).mod(p);
BigInteger Yout = sY.negate().mod(p);
Yout = Yout.add(slope.multiply(gX.subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
public static ECPoint scalmult (final EllipticCurve curve, final ECPoint g, final BigInteger kin) {
final ECField field = curve.getField();
if(!(field instanceof ECFieldFp)) throw new UnsupportedOperationException(field.getClass().getCanonicalName());
final BigInteger p = ((ECFieldFp)field).getP();
final BigInteger a = curve.getA();
ECPoint R = ECPoint.POINT_INFINITY;
BigInteger k = kin.mod(p);
final int length = k.bitLength();
final byte[] binarray = new byte[length];
for(int i=0;i<=length-1;i++){
binarray[i] = k.mod(FieldP._2).byteValue();
k = k.shiftRight(1);
}
for(int i = length-1;i >= 0;i--){
R = doublePoint(p, a, R);
if(binarray[i]== 1) R = addPoint(p, a, R, g);
}
return R;
}
public static ECPublicKey getPublicKey(final ECPrivateKey pk) throws GeneralSecurityException {
final ECParameterSpec params = pk.getParams();
final ECPoint w = scalmult(params.getCurve(), pk.getParams().getGenerator(), pk.getS());
final KeyFactory kg = KeyFactory.getInstance("EC");
return (ECPublicKey)kg.generatePublic (new ECPublicKeySpec (w, params));
}
}