Can KeyAgreement be used without AlgorithmParameterSpec? - java

In my Android application working with SpongyCastle, I want to perform ECDH Key Exchange using a specific elliptic curve (prime192v1) and I am using this code to generate byte[] representation of private and public params:
try{
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "SC");
org.spongycastle.jce.spec.ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("prime192v1");
g.initialize(ecSpec, new SecureRandom());
KeyPair kp1 = g.generateKeyPair();
KeyPair kp2 = g.generateKeyPair();
// Alice generated this
byte[] privK1 = kp1.getPrivate().getEncoded();
byte[] pubK1= kp1.getPrivate().getEncoded();
// Bob generated this
byte[] privK2 = kp2.getPrivate().getEncoded();
byte[] pubK2= kp2.getPrivate().getEncoded();
}catch(Exception e)
{
Log.e(LOGTAG, "Exception caught in ECDHInit function");
}
now lets assume Alice and Bob save their private keys locally, exchange pubK1 and pubK2 and want to proceed with setting a shared secret (they have both keys now in byte[] format). I will describe only case of Alice, as for Bob this is identical:
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "SC");
PrivateKey privateKey = KeyFactory.getInstance("ECDH", "SC").generatePrivate(new PKCS8EncodedKeySpec(privK1));
PublicKey publicKey = KeyFactory.getInstance("ECDH", "SC").generatePublic(new X509EncodedKeySpec(pubK2));
Here comes my question -
using the following code, I manage to get same shared secret for both Alice and Bob
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
byte[] sharedSecret = SHA256(keyAgreement.generateSecret());
but I specify nowhere my special curve prime192v1
If I try to supply this in this way:
org.spongycastle.jce.spec.ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("prime192v1");
keyAgreement.init(privateKey, ecSpec);
keyAgreement.doPhase(publicKey, true);
byte[] sharedSecret = SHA256(keyAgreement.generateSecret());
an Exception is thrown saying
java.security.InvalidAlgorithmParameterException: No algorithm parameters supported
Why can't I supply my desired parameter? Does the KeyAgreement somehow deduce this from the private key?
Thanks!

So it turns out I had the answer right there in front of me in the Android Studio debugger. Upon inspecting PrivateKey object I found internal member privateKey.ecSpec which contains all the details of selected curve. So passing arguments is unnecessary and throws an error, use the code without providing additional ecSpec

Related

Unit-Test in Java: Encryption and Decryption given an iOS KeyPair in ASN.1-Format

Objective
Writing an Encryption and Decryption-Test in a Java17 SpringBoot Service using RSA-keys generated on an iOS mobile device.
Hence:
Target: RSA-4096 is used throughout
This is about an iOS mobile device (Swift) <-> SpringBoot Service (Java17) context
Why is this question important?
The aim of the process:
On the iOS mobile divice a KeyPair is created and the public key (only) is sent to the SpringBoot Service.
Somewhen payload is encrypted in the SpringBoot Service and held for collection.
Later on the iOS mobile device pulls the encrypted data and decrypts it locally with its private key to retreive the payload.
Sidenote
This had to be done with an Android mobile device as well and it works fine.
Android serves PKCS-Formatted Keys.
Luckily regarding Android, both sides may use the standard javax.crypto-Library (only).
Hurdle along the way
Key format: ASN.1
The iOS-device does not export keys along the plain PKCS-Format.
It uses ASN.1-Format from the same 2003 RFC:
https://www.rfc-editor.org/rfc/rfc3447#appendix-A
Nevertheless, the keys can be extracted and exported from the keyChain (not the Secure Enclave).
SampleCode to extract the Keys from the KeyPair (Swift):
// Keypair attributes
let tag = "my_personal_keystore_keypair_alias".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: "4096",
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
var error: Unmanaged<CFError>?
// Generate PrivateKey / Keypair
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
// Extract PrivateKey
guard
let privateKeyOptional = SecKeyCopyExternalRepresentation(privateKey, nil) else {
return "Key generation did not succeed."
}
let privateKeyData = privateKeyOptional as Data
let base64PrivateKey = privateKeyData.base64EncodedString()
print("PrivateKey, base64: ", base64PrivateKey)
// Extract PublicKey
guard let publicKey = SecKeyCopyPublicKey(privateKey),
let publicKeyOptional = SecKeyCopyExternalRepresentation(publicKey, nil) else {
return "PublicKey could not be accessed."
}
let publicKeyData = publicKeyOptional as Data
let base64PublicKey = publicKeyData.base64EncodedString()
print("PublicKey, base64: ", base64PublicKey)
Sample Base64-encoded Keys (Java):
// === Sample KeyPair START ===
String publicKey = "MIICCgKCAgEAu2QLxnFmEMeZTtiX4DRUmeqsocOFtqyP2cKAiWCjbr75D+Ymzem0T3/oJov4G68yPMMAB/scdrxlpu3D7UmOUjeQ4aNUW3u/ZnHoDRdph7Jc/2ed6jAmPV/867BV78XZXL0O9Ofru6W+jCwuFunJN3IWhiVJhwCmYgn3wQDl6TeIYKysZ2XKWauFFlLYY9RSZAoCCyEr5PPS9irJhhiQiP2bv2cqh0eW/8N1DXBDSx80bsgtrhAFaG9UU/P5ApoCr+EmwqM6wEjYbTV/fWi0b74UGxiE2UtvEMHfp+XJ+JkbXH4GT7v+Gwhtkuq0+3wrSSD3bCZ5by7ti1+MK6bugsQ96+teY3Bd3jKHaGVLvYN2mIGwRW7Jmbf93B9s8z5nmvNJibF67Ru+0M86VJBIjmhbmoEJwPda6FDTqJObRmQwqGj6JSIwTeSnEqKA/8PZXn+qID0HaDly4uoVb5q2yDaykvqmA8k5IGV5UOjQvqf6saD4UL8nYmubUcQxLm5m9AC9J4sAmJwWGs5tuYr7auyH+GFIOmn7irtC+YX/lNAypgvnklJEWNmz/M/nBq1nDD4eygpY0E/k/cWMloLTwwvRbjj0ApUbGz1RUB2UP9ZI4Avb17UKhXXylLra9tPz1QtNIhwhIOX3tQzIQMiyK7aMJJUryBJajMzHowYSYQMCAwEAAQ==";
String privateKey = "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";
// === Sample KeyPair END ===
Solving the hurdle
Afaik the javax.crypto-Library does not support ASN.1-Formatted RSA-Keys.
Therefore the bouncycastle-dependency was used to parse the PublicKey (SpringBoot pom.xml):
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
Converting the PublicKey-String into a PublicKey-Class (Java):
public Optional<PublicKey> createIOSPublicKeyFromString(String publicKeyASN1) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyASN1);
ASN1Sequence sequence = ASN1Sequence.getInstance(publicKeyBytes);
ASN1Integer modulus = ASN1Integer.getInstance(sequence.getObjectAt(0));
ASN1Integer exponent = ASN1Integer.getInstance(sequence.getObjectAt(1));
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus.getPositiveValue(),
exponent.getPositiveValue());
KeyFactory factory = KeyFactory.getInstance("RSA");
return Optional.ofNullable(factory.generatePublic(keySpec));
}
The encryption using the PublicKey works without further interruption (Java):
public String encryptPayload(String payload, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher encryptCipher = Cipher.getInstance(encryptionProperties.getAlgorithm());
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] secretMessageBytes = payload.getBytes(StandardCharsets.UTF_8);
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
return Base64.getEncoder().encodeToString(encryptedMessageBytes);
}
The Problem
Please note - this is still in a testing scenario.
The aim is to decrypt within a unit-test of the SpringBoot Service.
The PrivateKey
Although the PublicKey could be parsed the described way, the parsing of the PrivateKey is refused.
java.security.InvalidKeyException: RSA keys must be at least 512 bits long
As the keys had been initialized using a KeySize of 4096, the error message is irritating.
Nevertheless, I cannot find the mistake so far.
Solution space
Multiple approaches may be suitable to solve this issue:
Execute the PrivateKey creation using the bouncycastle dependency.
Conduct the PrivateKey creation using the javax-crypto dependency.
Exporting the PublicKey generated in iOS (Swift) in a PKCS-Format
Solution (3) would erase the need of the bouncycastel-dependency and makes the need of THIS additional test obsolete :)
All approaches are fine ... .. . ;-)
Finally
Thanks for reading & sharing - any help is appreciated :)

Is it possible to generate a 64-byte (256-bit) key and store/retrieve it with AndroidKeyStore?

In my Android app, I need a way to encrypt the data I store in a local DB.
I chose Realm DB because the offer a seamless integration with encryption. I just need to pass a key when initializing the Realm instance. This key must be of 64 byte size.
For security reason, I found out that the best way to store this key is in AndroidKeyStore. I'm struggling to find a way to generate a key (using any algorithm) with that size, and getting it into a 64-byte array. I'm trying to keep a minSdk of API 19, but I believe I can bump it up to 23 if needed (many changes to AndroidKeyStore between these two versions).
Does anyone have an idea? Here is my code:
Class Encryption.java
private static KeyStore ks = null;
private static String ALIAS = "com.oi.pap";
public static byte[] loadkey(Context context) {
byte[] content = new byte[64];
try {
if (ks == null) {
createNewKeys(context);
}
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
content= ks.getCertificate(ALIAS).getEncoded(); //<----- HERE, I GET SIZE GREATER THAN 64
Log.e(TAG, "original key :" + Arrays.toString(content));
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = Arrays.copyOfRange(content, 0, 64); //<---- I would like to remove this part.
return content;
}
private static void createNewKeys(Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
try {
// Create new key if needed
if (!ks.containsAlias(ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(ALIAS)
.setSubject(new X500Principal("CN=PapRealmKey, O=oipap"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setKeySize(256)
.setKeyType(KeyProperties.KEY_ALGORITHM_EC)
.build();
KeyPairGenerator generator = KeyPairGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
Log.e(TAG, "generated key :" + Arrays.toString(keyPair.getPrivate().getEncoded()));
}
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
The point of AndroidKeyStore is to move sensitive key material out of your app, out of the operating system and into secure hardware where it can never leak or be compromised. So, by design, if you create a key in AndroidKeyStore, you can never get the key material out.
In this case, Realm DB wants the secret key material, so you can't give it an AndroidKeyStore key. Also, what Realm wants is two AES keys, not an EC key, as you were trying to generate.
The right way to generate the key material you need is:
byte[] dbKey = new byte[64];
Random random = new SecureRandom();
random.nextBytes(dbKey);
// Pass dbKey to Realm DB...
Arrays.fill(dbKey, 0); // Wipe key after use.
Just 64 random bytes. However, you're going to need to store those bytes somewhere. You could create an AES key with AndroidKeyStore and use it to encrypt dbKey. Something like:
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
new KeyGenParameterSpec.Builder("dbKeyWrappingKey",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey key = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedDbKey = cipher.doFinal(dbKey);
You'll need to save both iv and encryptedDbKey somewhere (not in the database!) so that you can recover dbKey. Then you can decrypt it with:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
key = (SecretKey) keyStore.getKey("dbKeyWrappingKey", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] dbKey = cipher.doFinal(encryptedDbKey);
// Pass dbKey to Realm DB and then wipe it.
However, with all of that said... I don't think you should do any of it. I don't think this actually gives you any security that Android doesn't give you by default anyway. If an attacker tries to dump the device storage, which contains your database, he'll get nothing because Android encrypts all of the storage anyway. If an attacker can root the device, he can run code as your app and use it to decrypt dbKey the same way your app does.
Where AndroidKeyStore may really add value is if you add some additional protections on dbKeyWrappingKey. For example, if you set it to require user authentication within, say five minutes, it will only be possible to use dbWrappingKey to decrypt dbKey when the user is around to enter their PIN/pattern/password or touch the fingerprint scanner. Note that this only works if the user has a PIN/pattern/password, but if they don't, well, your database is wide open to anyone who picks up the phone anyway.
See KeyGenParameterSpec for all of the things you can do to restrict the ways dbKeyWrappingKey can be used.
As far as I know, the usual way to solve this problem is, that you generate your own random key of the size you need (master-key), and this master-key can be encrypted with the help of the key store.
Generate your own random master-key of the size you need.
Encrypt data with this master-key (e.g. symmetric encryption).
Encrypt the master-key with the help of the key-store.
Store the encrypted master-key somewhere.
To decrypt your data:
Read the encrypted master-key.
Decrypt the master-key with the help of the key-store.
Decrypt data with the master-key.
In other words, it is not the master-key which is stored inside the key-store, but the key-store can be used to protect/encrypt your master-key.

Android keypair generation

OpenSsl generates a private key in DER format with 118 bytes length. (openssl ecparam -genkey -name secp256k1 and so on).
In android KeyPairGenerator initialized like:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "SC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(spec, new SecureRandom());
return keyPairGenerator.generateKeyPair();
returns a private key with a 144 length. But i need 118. Where is a difference? Is smh added in android implementation? How could i get 118 key length? Cannot find implementation to figure out.
Found a way to do this.
Generate key pair:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "SC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(spec, new SecureRandom());
KeyPair kp = keyPairGenerator.generateKeyPair();
Get private key bytes array:
byte[] privateK = kp.getPrivate();
Then convert private key to PKCS1:
byte[] privBytes = privateKey.getEncoded();
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privBytes);
ASN1Encodable encodable = pkInfo.parsePrivateKey();
ASN1Primitive primitive = encodable.toASN1Primitive();
byte[] privBytesEncoded = primitive.getEncoded();
It's a little bit of good news/bad news. The good news is that the bytes you want are a subsequence of the bytes returned by PrivateKey.getEncoded(). The bad news is that there's no good way that I'm aware of to get at them. Well, there is one easy way: the bytes you want are always at the end of PrivateKey.getEncoded(), so if you know the length of the byte sequence is n (e.g. 118 in your example) then just take the last n bytes of PrivateKey.getEncoded().
A slightly harder way is to parse through the encoding using the Spongycastle/Bouncycastle ASN1 routines, as in the following snippet:
private static byte[] encodePrivateKey(PrivateKey privateKey) throws Exception{
ASN1InputStream asn1InputStream = new ASN1InputStream(privateKey.getEncoded());
ASN1Primitive asn1Primitive = asn1InputStream.readObject();
DLSequence seq = (DLSequence) asn1Primitive;
ASN1OctetString octetString = (ASN1OctetString) seq.getObjectAt(2);
return octetString.getOctets();
}
I offer this as an example but I have to warn you that this is brittle: I haven't made any real effort to follow the PKCS#8 specification, I just eyeballed the ASN.1 structure to grab the bytes I knew were needed.

"NoSuchAlgorithmException" for Simple KeyPairGenerator [duplicate]

I have an issue with my java code. I'm trying to encrypt a file. However, when I run my java code I get "java.security.InvalidKeyException: Invalid AES key length: 162 bytes".
Here is the code:
byte[] rawFile;
File f = new File("./src/wonkybox.stl");
FileInputStream fileReader = new FileInputStream(f);
rawFile = new byte[(int)f.length()];
fileReader.read(rawFile);
/***** Encrypt the file (CAN DO THIS ONCE!) ***********/
//Generate the public/private keys
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG","SUN");
keyGen.initialize(1024, random);
KeyPair key = keyGen.generateKeyPair();
PrivateKey privKey = key.getPrivate();
PublicKey pubKey = key.getPublic();
//Store the keys
byte[] pkey = pubKey.getEncoded();
FileOutputStream keyfos = new FileOutputStream("./CloudStore/keys/pubkey");
keyfos.write(pkey);
keyfos.close();
pkey = privKey.getEncoded();
keyfos = new FileOutputStream("./CloudStore/keys/privkey");
keyfos.write(pkey);
keyfos.close();
//Read public/private keys
KeyFactory keyFactory = KeyFactory.getInstance("AES");
FileInputStream keyfis = new FileInputStream("./CloudStore/keys/pubkey");
byte[] encKey = new byte[keyfis.available()];
keyfis.read(encKey);
keyfis.close();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
PublicKey pub1Key = keyFactory.generatePublic(pubKeySpec);
keyfis = new FileInputStream("./CloudStore/keys/privkey");
encKey = new byte[keyfis.available()];
keyfis.read(encKey);
keyfis.close();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encKey);
PrivateKey priv1key = keyFactory.generatePrivate(privKeySpec);
//Encrypt file using public key
Cipher cipher = Cipher.getInstance("AES");
System.out.println("provider= " + cipher.getProvider());
cipher.init(Cipher.ENCRYPT_MODE, pub1Key);
byte[] encryptedFile;
encryptedFile = cipher.doFinal(rawFile);
//Write encrypted file to 'CloudStore' folder
FileOutputStream fileEncryptOutput = new FileOutputStream(new File("./CloudStore/encrypted.txt"));
fileEncryptOutput.write(encryptedFile);
fileEncryptOutput.close();
The error occurs at the line "KeyPairGenerator keyGen = KeyPairGenerator.getInstance("AES");".
AES is a symmetric algorithm, hence they use of KeyPairGenerator is not supported. To generate a key with AES you call KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); //set keysize, can be 128, 192, and 256
By looking at the rest of your code, it looks like you are trying to achive asymmetric encryption (since you call getPublic() and getPrivate() etc), so I advice you to switch to using RSA or any other asymmetric algorithm that java supports. You will most likley only need to replace AES with RSA in your getInstance(); calls, and pherhaps some fine-tuning. Good luck
As far as I know, AES is symmetric encryption algorithm i.e. it needs only one key for encryption/decryption.
From the JavaDoc of java.security.KeyPairGenerator:
The KeyPairGenerator class is used to generate pairs of public and private keys.
Meaning that it should be used for asymmetric encryption algorithms. For symmetric encryption algorithms one should use javax.crypto.KeyGenerator.
However, I advise simply mimicking some tutorial on how to encrypt / decrypt byte array in Java using AES like this one.
It uses sun.misc.Base64Encoder / Base64Decoder classes to encode / decode byte array to / from String, however you may skip this step.
Hope this helps
How can you use a keypair generator for AES? AES is a symmetric key algorithm. Refer this link. That means if you encrypt data using a key "k", then you will have to decrypt it also using the same key "k". But when you generate key pair, as the name suggests, two keys are generated and if you encrypt using one of the keys, you can decrypt only using the other key. This is the base for PKI.
If you want to use keypair generator use an algorithm like "rsa" or "dsa" in the getInstance() method like this :
KeyPairGenerator keygen=KeyPairGenerator.getInstance("rsa");
I think your code should now work fine after making the above change.

Converting byte array to publickey ECDSA

I need to use ECDSA algorithm to sign a message and send to receiver in java. Then, receiver should verify sender's signature.
So, for this, receiver has sender's public key but in byte array format after converting java.security.PublicKey to byte array by the command bellow:
byte[] byteArrayPublicKey = publickey.getEncoded();
The format of public key in ECDSA algorithm (before converting it to byte array) is as follow:
Public Key:
X: 8a83c389e7bb817c17bf2db4ed71055f18342b630221b2a3a1ca752502dc2e21
Y: 3eaf48c9ab1700fe0966a0cde196b85af66bb8f0bacef711c9dca2368f9d8470
But, the problem is to convert this byte array to usable format to verify the signature that is java.security.PublicKey by receiver.
In general, is there any solution to verify the signature without converting it to byte array? In the other word, the problem is to verify the signature by sender's public key, using any method.
But, the problem is to convert this byte array to usable format to verify the signature that is java.security.PublicKey by receiver.
You can solve the problem like this way:
public static ECPublicKey genEcPubKey() throws Exception {
KeyFactory factory = KeyFactory.getInstance("ECDSA", "BC");
java.security.PublicKey ecPublicKey = (ECPublicKey) factory
.generatePublic(new X509EncodedKeySpec(Helper
.toByte(ecRemotePubKey))); // Helper.toByte(ecRemotePubKey)) is java.security.PublicKey#getEncoded()
return (ECPublicKey) ecPublicKey;
}
Note that, you need BouncyCastle provider to do that.
But question remains, how you generate the private key?
public KeyPair ecKeyPairGenerator(String curveName) throws Exception {
KeyPair keyPair;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
"ECDSA", "BC");
ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(
curveName);
keyPairGenerator.initialize(ecGenParameterSpec, new SecureRandom());
keyPair = keyPairGenerator.generateKeyPair();
java.security.PublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
System.out.println("JAVA EC PublicKey: "
+ Helper.toHex(ecPublicKey.getEncoded()));
// write private key into a file. Just for testing purpose
FileOutputStream fileOutputStream = new FileOutputStream(
"ECPrivateKey.key");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
fileOutputStream);
objectOutputStream.writeObject(keyPair.getPrivate());
objectOutputStream.close();
return keyPair;
}
I have the full running code for EC sign/verify in github. You can take a look for better understanding.

Categories

Resources