How to generate PublicKey for PrivateKey in X25519? - java

I'm working with X25519-keys based encryption at the moment.
My question is, basically, how to derive PublicKey from existing X25519 PrivateKey?
I have found the code in the XDHKeyPairGenerator:
BigInteger publicKey = ops.computePublic(privateKey.clone());
But this package is platform-specific, thus not accessible. And I can't find a method to do it through publicly-accessible interfaces.

So far I've discovered only one way to do it through JDK-provided interfaces (without using any additional libraries like Bouncy Castle or Google Tink):
public class StaticSecureRandom extends SecureRandom {
private final byte[] privateKey;
public StaticSecureRandom(byte[] privateKey) {
this.privateKey = privateKey.clone();
}
#Override
public void nextBytes(byte[] bytes) {
System.arraycopy(privateKey, 0, bytes, 0, privateKey.length);
}
}
public PublicKey generatePublicKeyFromPrivate(PrivateKey privateKey) throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(X25519);
keyPairGenerator.initialize(new NamedParameterSpec(X25519), new StaticSecureRandom(getScalar(privateKey)));
return keyPairGenerator.generateKeyPair().getPublic();
}
It's not a very elegant solution, but it works without any third-party libraries and I couldn't find any other way.

You must scalar multiply the private key (which is just a big number) by the 25519 curve generator point.
Here is some code in python to illustrate:
from tinyec import registry
import secrets
curve = registry.get_curve('curve25519')
def compress_point(point):
return hex(point.x) + hex(point.y % 2)[2:]
privKey = secrets.randbelow(curve.field.n)
pubKey = privKey * curve.g //the key step for you...
print("private key:", hex(privKey))
print("public key:", compress_point(pubKey))
If you let me know the Java lib I will try and help further.

BouncyCastle has Ed25519KeyPairGenerator, X25519KeyPairGenerator, PrivateKeyInfoFactory, and SubjectPublicKeyInfoFactory that can assist with making the keys. Here's an example in C#. Substitute ECKeyPairGenerator with X25519KeyPairGenerator. This example uses a standard keys and a NIST curve since I couldn't get Curve25519 working with the X25519 generated keys because the oid isn't supported in the current implementation.
public static async Task Bouncy()
{
var originalSecret = "X25519 example";
var message = Encoding.UTF8.GetBytes(originalSecret);
// Generate signing keys
var gen = new Ed25519KeyPairGenerator();
gen.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var kp = gen.GenerateKeyPair();
// Sign data with private key
var signer = new Ed25519Signer();
signer.Init(true, kp.Private);
signer.BlockUpdate(message, 0, message.Length);
var sig = signer.GenerateSignature();
// Verify signature with public key
var verifier = new Ed25519Signer();
verifier.Init(false, kp.Public);
verifier.BlockUpdate(message, 0, message.Length);
var sigresult = verifier.VerifySignature(sig);
// Generate encryption keys
var genX = new ECKeyPairGenerator();
genX.Init(new KeyGenerationParameters(new SecureRandom(), 521));
var p1 = genX.GenerateKeyPair();
var p1_private = ECPrivateKeyStructure.GetInstance(PrivateKeyInfoFactory.CreatePrivateKeyInfo(p1.Private));
var p1_x25519_priv = new X25519PrivateKeyParameters(p1_private.GetDerEncoded(), 0);
var p2 = genX.GenerateKeyPair();
var p2_public = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(p2.Public);
var p2_x25519_pub = new X25519PublicKeyParameters(p2_public.GetDerEncoded(), 0);
// Generate secret from keys
var secret = new byte[32];
p1_x25519_priv.GenerateSecret(p2_x25519_pub, secret, 0);
// Setup ECIES (Elliptical Curve Integrated Encryption Scheme)
var gcm = new GcmBlockCipher(new AesEngine());
var ies = new IesEngine(new ECDHBasicAgreement(), new Kdf2BytesGenerator(new Sha512Digest()),
new HMac(new Sha512Digest()), new PaddedBufferedBlockCipher(gcm.GetUnderlyingCipher()));
// 256bit MAC, 256 key
var p = new IesWithCipherParameters(secret, new byte[1], 256, 256);
// Encrypt secret
ies.Init(true, p1.Private, p2.Public, p);
var encrypted = ies.ProcessBlock(message, 0, message.Length);
// Decrypt secret
ies.Init(false, p2.Private, p1.Public, p);
var decrypted = ies.ProcessBlock(encrypted, 0, encrypted.Length);
var decrypted_string = Encoding.UTF8.GetString(decrypted);
}

Related

ECDSA signature generation KeyPair Java to C# - JcaPEMKeyConverter()

I have been converting over some code from a Java Android app to C# using Xamarin and I have come across a problem when trying to generate a signature using a certain snippet of BouncyCastle code.
Is there a replacement function in C# for the line of code
"pair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) parsed);" ??
This is the Java code:
// Generating the signature
Signature signature = Signature.getInstance("SHA256withECDSA");
Reader rdr = new StringReader("privatekeygoeshere");
Object parsed = new PEMParser(rdr).readObject();
KeyPair pair;
pair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) parsed);
PrivateKey signingKey = pair.getPrivate();
signature.initSign(signingKey);
signature.update(nonceData1);
signature.update(nonceData2);
signature.update(collectorID);
signature.update(publicKeyCompressed);
byte[] signedData = signature.sign();
I have found another way to read the private key and create a KeyPair. However, the private key is stored as a AsymmetricCipherKeyPair which I cannot add into the signature.InitSign() function as this requires an IPrivateKey.
The Different ways that I have tried to create a signature do not allow me to update other byte array data to the signature generation like the Java code, this doesn't work for me so I am really stuck.
I am also open to any ideas of signature generation.
Example of this here:
AsymmetricKeyParameter signingKey;
AsymmetricCipherKeyPair keyPair = null;
using (var textReader = new System.IO.StringReader("privatekeygoeshere"))
{
// Only a private key
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(textReader);
keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
signingKey = keyPair.Private;
}
I managed to come up with a solution for my problem using a string reader and looping through each array using the Update() command. This works well for me however, if any one can find a better way of doing this... Please comment below.
AsymmetricKeyParameter signingKey;
using (var textReader = new System.IO.StringReader(LONG_TERM_PRIVATE_KEY))
{
// Only a private key
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(textReader);
keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
signingKey = keyPair.Private;
}
var signer = SignerUtilities.GetSigner("SHA256withECDSA");
signer.Init(true, signingKey);
foreach (byte b in terminalNonce)
{
signer.Update(b);
}
foreach (byte b in mobileDeviceNonce)
{
signer.Update(b);
}
foreach (byte b in COLLECTOR_ID)
{
signer.Update(b);
}
foreach (byte b in terminalEphemeralPublicKeyCompressed)
{
signer.Update(b);
}
var signed = signer.GenerateSignature();

Migrate Public/Private Keys from one programming language to other for Signing Payload and verifying them

I have one Perl Script that sign the payload with private key(id_rsa), generated in linux machine with SSH-Keygen or OpenSSL. After that I am taking the hash or the signed value, decoding it in base 64 and sending to my scala code. Here I send two things, the public key(id_rsa.pub) and the encoded signature.
Now when I verify the signature it always give me false result.
I have tried the opposite, like singing in Scala and verifying with Perl. It returned me false.
I generated the keys in Scala and put them in Linux and done the signing and verifying part from Perl. It worked. It even worked when I imported both keys in scala and try to do the signing and verifying part both from scala.
But whenever I am mixing up these two like signing in Perl and verifying in Scala or signing in Scala and verifying in Perl, it is giving me a false result.
My question is is there any common format that I can use here. The code I am using is -
val fileOpened = Source.fromFile("file.hash") // taking the hashed value
val payload = fileOpened.getLines.mkString //file contents as string
val decodedString = Base64.getDecoder.decode(payload.getBytes) // Base64 Decoding of the hashed value
println("decodedString, the hash value was base64 encoded, so decoded it and took into bytes")
println(decodedString) //the hash value was base64 encoded, so decoded it and took into bytes
val fileOpened1 = Source.fromFile("file")
val ComareElement = fileOpened1.getLines.mkString
val decodedString1 = new String(ComareElement).getBytes
println("decodedString1, this is the main payload to compare, turned into bytes")
println(decodedString1) // this is the main payload to compare, turned into bytes
//val keyPairGen = KeyPairGenerator.getInstance("RSA") //Creating KeyPair generator object
//keyPairGen.initialize(2048, new SecureRandom) //Initializing the key pair generator
//val pair: KeyPair = keyPairGen.generateKeyPair
// save public key as id_rsa.pub
//val x509keySpec = new X509EncodedKeySpec(pair.getPublic.getEncoded)
//val publicKeyStream = new FileOutputStream("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\id_rsa.pub")
//publicKeyStream.write(x509keySpec.getEncoded)
// save private key as id_rsa
//val pkcs8KeySpec = new PKCS8EncodedKeySpec(pair.getPrivate.getEncoded)
//val privateKeyStream = new FileOutputStream("id_rsa")
//privateKeyStream.write(pkcs8KeySpec.getEncoded)
val filePublicKey = new File("id_rsa.pub")
var inputStream = new FileInputStream("id_rsa.pub")
val encodedPublicKey: Array[Byte] = new Array[Byte](filePublicKey.length.toInt)
inputStream.read(encodedPublicKey)
inputStream.close()
val filePrivateKey = new File("id_rsa")
inputStream = new FileInputStream("id_rsa")
val encodedPrivateKey: Array[Byte] = new Array[Byte](filePrivateKey.length.toInt)
println("The key is now " +encodedPrivateKey)
inputStream.read(encodedPrivateKey)
inputStream.close()
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
// public key
val publicKeySpec: X509EncodedKeySpec = new X509EncodedKeySpec(encodedPublicKey)
val publicKey: PublicKey = keyFactory.generatePublic(publicKeySpec)
// private key
val privateKeySpec: PKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey)
val privateKey: PrivateKey = keyFactory.generatePrivate(privateKeySpec)
new KeyPair(publicKey, privateKey)
// val pair = keyPairGen.generateKeyPair //Generate the pair of keys
//val privKey = pair.getPrivate //Getting the privatekey from the key pair
//val pubKey = pair.getPublic //Getting the PublicKey from the key pair
val privKey = privateKey //Getting the privatekey from the key pair
val pubKey = publicKey //Getting the PublicKey from the key pair
println("Getting the privateKey from the key pair " + privateKey)
println("Getting the publicKey from the key pair " + publicKey)
var writer = new PrintWriter(new File("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\Private_Key"))
writer.write(privKey.toString)
writer.close()
writer = new PrintWriter(new File("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\Public_Key"))
writer.write(pubKey.toString)
writer.close()
val sign = Signature.getInstance("SHA256withRSA") //Creating a Signature object
//sign.initSign(privKey)
//val bytes = payload.getBytes //Initializing the signature
val bytes = decodedString
println(bytes)
//sign.update(bytes) //Adding data to the signature
//val signature = sign.sign //Calculating the signature
//val signedPayload = new BASE64Encoder().encode(signature)
//writer = new PrintWriter(new File("file.hash"))
//writer.write(signedPayload)
//writer.close()
println(bytes)
sign.initVerify(pubKey) //Initializing the signature
sign.update(bytes)
//println(signature)
//val bool = sign.verify(new BASE64Decoder().decodeBuffer(signedPayload)) //Verifying the signature
//println(sign.verify(signature))
val bool = sign.verify(bytes)
println(bool)
if (bool) System.out.println("Signature verified")
else System.out.println("Signature failed")
}
I have found a solution. I achieved that by correcting the signature algorithm and its default values in Perl. Also you have to take care of the new line characters (\n). The modified code for verification should look like this in Scala -
object Verify_Test extends App {
var privateKeyString = new String(Files.readAllBytes(Paths.get("private_key.pem")), Charset.defaultCharset)
privateKeyString = privateKeyString.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "")
var publicKeyString = new String(Files.readAllBytes(Paths.get("public_key.pem")), Charset.defaultCharset)
publicKeyString = publicKeyString.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "")
val keyFactory = KeyFactory.getInstance("RSA")
val encodedPrivateKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString))
val privateKey = keyFactory.generatePrivate(encodedPrivateKeySpec)
val encodedPublicKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString))
val publicKey = keyFactory.generatePublic(encodedPublicKeySpec)
var encodedSignatureVale = new String(Files.readAllBytes(Paths.get("hashedSignature")), Charset.defaultCharset)
encodedSignatureVale = encodedSignatureVale.replaceAll("\\n", "")
println(encodedSignatureVale)
var actualValue = new String(Files.readAllBytes(Paths.get("original data")), Charset.defaultCharset)
//actualValue = actualValue.replaceAll("\\n", "")
println(actualValue)
val signature = Signature.getInstance("SHA256withRSA")
//signature.initSign(privateKey)
//signature.update("Hello, World\n".getBytes("UTF-8"))
//val signatureValue = signature.sign
//val encodedSignatureVale = Base64.encodeBase64String(signatureValue)
//println(Base64.encodeBase64String(signatureValue))
signature.initVerify(publicKey)
// signature.update("Hello, World\n".getBytes("UTF-8"))
signature.update(actualValue.getBytes("UTF-8"))
val bool = signature.verify(Base64.decodeBase64(encodedSignatureVale))
println(bool)
if (bool) println("Signature verified")
else println("Signature failed")}
Remember I have taken both private and public key here. But we can use only public for verification.
The Perl code for signing will be -
use File::Slurp qw(read_file);
use File::Slurp qw(write_file);
use MIME::Base64 qw(encode_base64);
require Crypt::PK::RSA;
my $datatosign = read_file( 'payload.txt');
my $privatekey = Crypt::PK::RSA->new('private_key.pem');
my $signature = $privatekey->rsa_sign_message($datatosign, 'SHA256', 'v1.5');
my $hash = encode_base64($signature, '');
write_file('payload.hash', $hash);

Diffie-Hellman algorithm between C# and Java

I have console applications in c# and Java. Both of them generate public and private keys for Eliptic curved Diffie-Hellman algorithm. Public keys are encrypted in base64 and then are printed in console. Then I paste public key from c# into java program and vice verca. Unforutunately in the end derived keys which have to be the same are different. Seems like the configurations of algorithms are the same and there are no exceptions.
C#:
static void Main(string[] args)
{
ECDiffieHellmanCng eCDiffie = new ECDiffieHellmanCng(256);
eCDiffie.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
eCDiffie.HashAlgorithm = CngAlgorithm.Sha256;
byte[] myPublicKey = eCDiffie.ExportSubjectPublicKeyInfo(); //export in x509 format
String myPublicKeyBase64 = Convert.ToBase64String(myPublicKey);
Console.WriteLine(myPublicKeyBase64);
string otherKey = Console.ReadLine(); // here paste public key in console from Java
byte[] otherKeyFromBase64 = Convert.FromBase64String(otherKey);
ECDiffieHellmanCng eCDiffie2 = new ECDiffieHellmanCng(256);
eCDiffie2.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
eCDiffie2.HashAlgorithm = CngAlgorithm.Sha256;
int some = 0;
eCDiffie2.ImportSubjectPublicKeyInfo(otherKeyFromBase64, out some);
byte[] otherKeyDecoded = eCDiffie2.PublicKey.ToByteArray();
CngKey k = CngKey.Import(otherKeyDecoded, CngKeyBlobFormat.EccPublicBlob);
byte[] derivedKey = eCDiffie.DeriveKeyMaterial(k);
String derivedKeyBase64 = Convert.ToBase64String(derivedKey);
Console.WriteLine("Derived key: ");
Console.WriteLine(derivedKeyBase64);
}
Java:
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
// Generate ephemeral ECDH keypair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded(); //public key in x509 format
// Display our public key
byte[] ourPublicKeyBase64 = Base64.getEncoder().encode(ourPk);
System.out.println(String.format("Public Key: %s", new String(ourPublicKeyBase64)));
// Read other's public key:
Scanner in = new Scanner(System.in);
String oth = in.nextLine(); // here paste in console public key C#
byte[] otherPk = Base64.getDecoder().decode(oth);
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both public keys
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
byte[] derivedKey = hash.digest();
byte[] derivedKeyBase64 = Base64.getEncoder().encode(derivedKey);
System.out.println(String.format("Derived key: %s", new String(derivedKeyBase64)));
}
Your Java code does some extra work to obtain derived key. Just remove following lines and you will get the same key as in ะก# code:
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
Here is the whole Java code just for completeness:
public static void main(String[] args) throws Exception {
// Generate ephemeral ECDH keypair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded(); //public key in x509 format
// Display our public key
byte[] ourPublicKeyBase64 = Base64.getEncoder().encode(ourPk);
System.out.println(String.format("Public Key: %s", new String(ourPublicKeyBase64)));
// Read other's public key:
Scanner in = new Scanner(System.in);
String oth = in.nextLine(); // here paste in console public key C#
byte[] otherPk = Base64.getDecoder().decode(oth);
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
byte[] derivedKey = hash.digest();
byte[] derivedKeyBase64 = Base64.getEncoder().encode(derivedKey);
System.out.println(String.format("Derived key: %s", new String(derivedKeyBase64)));
}

Derive Shared Secret From ECDH with existing foreign public key

Im converting a method from nodeSJ to Java, but im having trouble getting it to work. Im stuck trying to calculate a derived shared secret.
Hoping someone can catch what im doing wrong porting over the nodeJS to Java.
NodeJS code:
//the public_key param here is from a different device.
sign: function(public_key)
{
//dummy values
var PRE_SALT_VALUE = 'f0f0f0f0f0';
var POST_SALT_VALUE = '0101010101';
const crypto = require('crypto');
var sha512 = crypto.createHash("sha512");
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair(); //key1 is gen before pub key
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key gen from saved cert
var derived_secret = key1.derive(key2.getPublic());
var derived_secret = Buffer.from(derived_secret.toString(16), 'hex')
var public_key_client = key1.getPublic('hex')
var pre_salt = Buffer.from(PRE_SALT_VALUE, 'hex')
var post_salt = Buffer.from(POST_SALT_VALUE, 'hex')
derived_secret = Buffer.from(pre_salt.toString('hex')+derived_secret.toString('hex')+post_salt.toString('hex'), 'hex') // finalyze shared secret
// Hash shared secret
var sha = sha512.update(derived_secret);
derived_secret = sha.digest();
return {
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
secret: derived_secret.toString('hex')
}
}
In progress Java Code:
//load cert from pem string (passed in from file), foreign cert
ByteArrayInputStream input = new ByteArrayInputStream(pem);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(input);
X509Certificate x509Cert = (X509Certificate) cert;
// get pub key from cert
PublicKey publicKeyForSignature = x509Cert.getPublicKey();
// Generate ephemeral ECDH keypair KEY1
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp1 = kpg.generateKeyPair();
byte[] ourPk = kp1.getPublic().getEncoded(); //use this later
//load KEY2 from others public key
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(publicKeyForSignature.getEncoded());
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp1.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both salt keys
MessageDigest hash = MessageDigest.getInstance("SHA-512");
hash.update(Util.PRE_SALT_VALUE);
hash.update(sharedSecret);
hash.update(Util.POST_SALT_VALUE);
byte[] derivedKey = hash.digest();
... etc, derivedKey = secret returned in JS method, ourPk = public_key returned in JS method.
One thing i notice is that the public/private keys generated from nodejs vs java are different sizes? 65 bytes in node an 91 bytes in java. No idea why that would happen.
What stands out as wrong here?
Thanx
Edit:
So basically, I just need to know how to do this in Java
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair();
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key from foreign device
var derived_secret = key1.derive(key2.getPublic());
Like already mentioned in the comments, to be able to use the shared secret between Java and Node you need to convert the keys accordingly.
For the key conversion code from this two fine stackoverflow answers can be used:
https://stackoverflow.com/a/57209308/2331445
https://stackoverflow.com/a/36033552
Test
To get a complete test case, one can write a Java program that generates a DER public key, converts it into an uncompressed EC key (65 bytes) and outputs it to the console. It then reads the other PK from the console, converts it to a public key, and prints out the shared secret.
The Node code uses the PK from the Java program, determines the shared secret and a public key. This public key can then be passed to the Java program via copy/paste, which is still waiting for the input.
The Java program finally determines the shared secret and prints it out.
If both shared secrets have the same value, we know it works.
Java
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import javax.crypto.KeyAgreement;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
public class Main {
public static byte[] ecKeyBytesFromDERKey(byte[] ourPk) {
ASN1Sequence sequence = DERSequence.getInstance(ourPk);
DERBitString subjectPublicKey = (DERBitString) sequence.getObjectAt(1);
return subjectPublicKey.getBytes();
}
private static PublicKey publicKeyFromEC(byte[] ecKeyByteArray) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance("EC", "BC");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint publicPoint = ECPointUtil.decodePoint(params.getCurve(), ecKeyByteArray);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params);
return kf.generatePublic(pubKeySpec);
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded();
byte[] ecPublicKey = ecKeyBytesFromDERKey(ourPk);
System.out.println("our ec public key (65 bytes): " + Hex.encodeHexString(ecPublicKey));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("other public key (65 bytes): ");
String input = br.readLine();
br.close();
byte[] otherPk = Hex.decodeHex(input);
PublicKey otherPublicKey = publicKeyFromEC(otherPk);
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
byte[] sharedSecret = ka.generateSecret();
System.out.println("Shared secret: " + Hex.encodeHexString(sharedSecret));
}
}
Node
One change is required in your node program:
In the line
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
the .slice(2) needs to be removed:
public_key: public_key_client.toString('hex'),
because it removes the first byte (which is hex 04) needed to indicate that it is an uncompressed key.
So just using the public key from the Java program (which will be different for each run) the Node part could look like this:
var publickey = Buffer.from("<public key from java>", 'hex');
var derived = sign(publickey);
console.log(derived);
Test
In the upper area you see the Java program and in the lower area the output of the Node program. The shared secret is the same.

How to DER encode an ECDH Public Key in BouncyCastle Java

So I know how to encode/decode a public key in the BouncyCastle C# library into a byte array:
Encode:
PublicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(p1.Public).GetDerEncoded();
Decode:
ECPublicKeyParameters pubKey = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(OtherPublicKey);
I can't seem to figure out how to do this in the java version of the BouncyCastle Library since there doesn't seem to be a SubjectPublicKeyInfoFactory object in the Java version of the library. There does, however, seem to be a PublicKeyFactory class in Java so it looks like I can just use the same code but I don't know how to DER encode the public key in the java library. Can anyone help?? Thanks!
-----EDIT---------------------------------------------------------
Ok, so here is what I have so far in C#:
Create the ECDH instance:
public static ECDHBasicAgreement CreateECDHInstance(out byte[] PublicKey)
{
IAsymmetricCipherKeyPairGenerator g = GeneratorUtilities.GetKeyPairGenerator("ECDH");
FpCurve curve = new FpCurve(
new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
ECDomainParameters ecSpec = new ECDomainParameters(
curve,
curve.DecodePoint(Hex.Decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307"), // n
BigInteger.One); // h
g.Init(new ECKeyGenerationParameters(ecSpec, new SecureRandom()));
AsymmetricCipherKeyPair aKeyPair = g.GenerateKeyPair();
ECDHBasicAgreement aKeyAgreeBasic = new ECDHBasicAgreement();
aKeyAgreeBasic.Init(aKeyPair.Private);
PublicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(aKeyPair.Public).GetDerEncoded();
return aKeyAgreeBasic;
}
This creates and returns a ECDHBasicAgreement object perfectly and outputs a public key in der encoded byte array form. Here is what I have in java:
public void testECDH() throws Exception
{
AsymmetricCipherKeyPairGenerator g = new ECKeyPairGenerator();
Fp curve = new Fp(
new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
ECDomainParameters ecSpec = new ECDomainParameters(
curve,
curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307"), // n
BigInteger.ONE); // h
g.init(new ECKeyGenerationParameters(ecSpec, new SecureRandom()));
AsymmetricCipherKeyPair aKeyPair = g.generateKeyPair();
ECDHBasicAgreement aKeyAgreeBasic = new ECDHBasicAgreement();
aKeyAgreeBasic.init(aKeyPair.getPrivate());
// The part that doesn't work
//byte[] publickey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(aKeyPair.getPublic()).GetDerEncoded();
}
In java, there doesn't seem to be a SubjectPublicKeyInfoFactory class or the equivalent that can take the aKeyPair.getPublic() and be able to generate a DER encoded byte array. Can anyone please help!??!!? I'm about at my wits end! Thanks!!!!
----------EDIT 2 -------------------------------------------------------------------------
Ok, so here's where I'm at now:
public void test2() throws Exception
{
ECKeyPairGenerator g = new ECKeyPairGenerator();
Fp curve = new Fp(
new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
ECDomainParameters ecP = new ECDomainParameters(
curve,
curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307"), // n
BigInteger.ONE); // h
g.init(new ECKeyGenerationParameters(ecP, new SecureRandom()));
// Generate key pair
AsymmetricCipherKeyPair aKeys = g.generateKeyPair();
JCEECPublicKey jpub = new JCEECPublicKey("EC", (ECPublicKeyParameters)aKeys.getPublic());
JCEECPrivateKey jpriv = new JCEECPrivateKey("EC", (ECPrivateKeyParameters)aKeys.getPrivate());
KeyPair aKeyPair = new KeyPair(jpub, jpriv);
ECDHBasicAgreement aKeyAgree = new ECDHBasicAgreement();
aKeyAgree.init(aKeys.getPrivate());
byte[] encoded = aKeyPair.getPublic().getEncoded();
// The part that breaks now (Exception DERNull)
ECPublicKeyParameters decoded = decodeECPublicKeyParameters(encoded);
}
public static ECPublicKeyParameters decodeECPublicKeyParameters(byte[] pkByte) throws IOException {
return (ECPublicKeyParameters) PublicKeyFactory.createKey(pkByte);
}
So I've been able to get the public/private keys into JCEEC Key objects and have been able to encode them. When I attempt to decode them I get a DERNull exception. I ran some other tests and generated keys using the regular native java KeyPairGenerator and was able to encode/decode the keys so I know that this method does work. I think there is just something missing when I convert the AsymmetricCipherKeys into the JCEEC Keys. I did notice there is another parameter in the JCEECPublicKey construct, the third parameter of ECKeySpec. Only trouble is, I'm not exactly sure how to get an ECKeySpec out of the code I have so far (or if thats even the problem to begin with). Any other suggestions? Thanks!!!
Have you tried using the Bouncycastle SubjectPublicKeyInfo class? Something like:
byte [] derEncoded;
//...
SubjectPublicKeyInfo pkInfo = new SubjectPublicKeyInfo((ASN1Sequence)ASN1Object.fromByteArray(derEncoded))
EDIT:
There is an easy if somewhat unsatisfying way. You can use the JCEPublicKey class and it has a getEncoded() method that produces (I think) the correct answer.
EDIT 2:
I'm learning as I go :) It turns out you must identify the elliptic curve in the algorithm parameters, which makes sense. Here is a slight change that does that.
g.init(new ECKeyGenerationParameters(ecP, new SecureRandom()));
// Generate key pair
AsymmetricCipherKeyPair aKeys = g.generateKeyPair();
ECParameterSpec ecSpec = new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN());
JCEECPublicKey jpub = new JCEECPublicKey("EC",
(ECPublicKeyParameters) aKeys.getPublic(), ecSpec);
JCEECPrivateKey jpriv = new JCEECPrivateKey("EC",
(ECPrivateKeyParameters) aKeys.getPrivate(), jpub, ecSpec);

Categories

Resources