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.
Related
I have the following public key, that is stored in the DB (PostgresSQL) as text. It's a String, in java:
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI
1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW
s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ
KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9
QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg
d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy
esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi
KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD
9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn
Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla
2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==
-----END RSA PUBLIC KEY-----
I don't know how this key has been generated, I'm sorry. I have been told to take this key and verify the signature of another string that I'll call "object". I have been told that the algorithm that I have to use to verify "object" is SHA256withRSA.
So, I have written the following java method to read the key
private PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = publicKey.replaceAll("\\n", "");
publicKey = publicKey.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKey = publicKey.replace("-----END RSA PUBLIC KEY-----", "");
publicKey = publicKey.trim();
byte[] keyDecoded = Base64.getDecoder().decode(publicKey.getBytes());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyDecoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicSpec);
return pubKey;
}
The point is that I get the following exception:
java.security.InvalidKeyException: IOException: algid parse error, not
a sequence
I have read plenty of qustions as mine in stackoverflow. The code written by other users is pretty similar (sometimes identical) to mine. So I definitely don't get why it doesn't work for me. Other developers (workmates) are doing the same in php and it works great, so I would discard the hypothesis of wrong public key. Maybe didn't I understood the process clearly? Do you have any clue, please?
I have also tried to cope with the problem using BouncyCastle library, as suggested here, but I get the same exception. The following is the code I have written:
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new BouncyCastleProvider());
PemReader pp = new PemReader(new StringReader(publicKey));
PemObject pem = pp.readPemObject();
byte[] content = pem.getContent();
pp.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
You can't load that key using an X509EncodedKeySpec. According to it's JavaDoc documentation it expects the following format:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Instead your key looks different. I used the data from your post, converted it to hex data and posted it into the online ASN.1 decoder.
The output is this:
SEQUENCE (2 elem)
INTEGER (4096 bit) 873481340827968071893572683200799871431146795599597693981565010037737…
INTEGER 65537
As you may recognize your key does not contain an AlgorithmIdentifier therefore it can not be loaded using X509EncodedKeySpec.
My suggestion would be to use the BouncyCastle library and it's PEMParser class for loading this key:
File pemFile = new File("test.pem");
try (PEMParser pp = new PEMParser(new InputStreamReader(new FileInputStream(pemFile)))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = kf.generatePublic(rsaSpec);
System.out.println(publicKey);
}
Or you manually convert the key to PKCS#8 format via openssl.
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.
I need to save my RSA private and public key. Here I create it.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
Now I want to use both keys the next time. So they should be stored on the device.
byte[] publicKeyBytes = publicKey.getEncoded();
byte[] privateKeyBytes = privateKey.getEncoded();
So I can get the Byte Arrays and then I save them as a text file.
Then the next time i read it and convert it back to the byte Array.
Now my question: How can i convert a byte array back to a key?
This does not really work:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
publicKey = keyFactory.generatePublic(spec);
spec = new X509EncodedKeySpec(privateKeyBytes);
privateKey = keyFactory.generatePrivate(spec);
On LogCat, this is going to be display:
Must use RSAPublicKeySpec or PKCS8EncodedKeySpec; was
java.security.spec.X509EncodedKeySpec
Any ideas whats wrong with this code?
Thanks for your help.
Try using PKCS8EncodedKeySpec for the private key.
RSAPublicKeySpec for the public key, instead of X509EncodedKeySpec for both.
Please also note that saving keys to a text file might not be the most secure idea. Android provides a nice KeyStore Api, which is easy to use.
You try encode the keys in Base64 and stored like a String, when you retrieved only need decoded the keys from Base64 to Byte arrays.
I am generating a keypair and converting one of the same into string which later is inserted into the database using the following code:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair generatedKeyPair = keyGen.genKeyPair();
PublicKey pubkey = generatedKeyPair.getPublic();
PrivateKey prvkey = generatedKeyPair.getPrivate();
System.out.println("My Public Key>>>>>>>>>>>"+pubkey);
System.out.println("My Private Key>>>>>>>>>>>"+prvkey);
String keyAsString = new BigInteger(prvkey.getEncoded()).toString(64);
I then retrieve the string from the database and convert it back to the original key using the following code (where rst is my ResultSet):
String keyAsString = rst.getString("privateKey").toString();
byte[] bytes = new BigInteger(keyAsString, 64).toByteArray();
//byte k[] = "HignDlPs".getBytes();
PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(bytes);
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
PrivateKey privKey = rsaKeyFac.generatePrivate(encodedKeySpec);
On using the privKey for RSA decryption, I get the following exception
java.lang.NumberFormatException: Radix out of range
at java.math.BigInteger.<init>(BigInteger.java:294)
at com.util.SimpleFTPClient.downloadFile(SimpleFTPClient.java:176)
at com.Action.FileDownload.processRequest(FileDownload.java:64)
at com.Action.FileDownload.doGet(FileDownload.java:94)
Please guide.
You're getting an exception because the radix you're providing is greater than Character.MAX_RADIX (which is 36). In other words, it's entirely predictable.
Don't use BigInteger as an encoding class. That's not what it's there for. There are plenty of decent ways of performing base64 encoding. Personally I like this public domain library.
String keyAsString = Base64.encode(prvkey.getEncoded());
Then later:
byte[] bytes = Base64.decode(keyAsString);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
Key publicKey = kp.getPublic();
Key privateKey = kp.getPrivate();
I want to create just the public key from a byte[].
I have tried this as an experiment:
publicKey = new SecretKeySpec(publicKey.getEncoded(), publicKey.getAlgorithm());
But decryption using that key then fails.
I have also tried serializing the key with ObjectOutputStream, but serialization fails.
java.io.NotSerializableException: org.apache.harmony.xnet.provider.jsse.OpenSSLKey
I read here that I can't use SecretKeySpec with RSA.
so as long as you are talking of a SecretKey and not an RSA or DSA key then you don't have to go through any contortions involving KeyGenerator or the like.
Anyone know how to perform these contortions or a way of doing this.
Asymmetric keys like those from RSA are usually stored in X509 format. Therefor you can use X509EncodedKeySpecinstead.
A simple example is already in the Java 7 JavaDoc (just replace DSA with RSA):
http://docs.oracle.com/javase/7/docs/api/java/security/KeyFactory.html
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);
If you need to deserialize the private from byte[], I've found that you must use PKCS8EncodedKeySpec.
Depends on what do you want to do with the serialized representation. If the consumer is no one but your own program, feel free to roll your own implementation. A public RSA key consists of two integers - exponent and modulus. Modulus is large - around 1024 bits, exponent is typically on the order of 17 bits. Both are available as BigInteger objects if you cast your public key object to RSAPublicKey.
So, to recap:
RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic();
return publicKey.getModulus().toString() + "|" +
publicKey.getPublicExponent().toString();
That's sufficient to restore the key. To deserialize:
String []Parts = MyKeyString.split("\\|");
RSAPublicKeySpec Spec = new RSAPublicKeySpec(
new BigInteger(Parts[0]),
new BigInteger(Parts[1]));
return KeyFactory.getInstance("RSA").generatePublic(Spec);
If the key needs to be passed to third party software, you better serialize to a standard format - PEM or DER.
I use the following code to convert a PubliKey to PEM Base64 format
String publicKeyString = javax.xml.bind.DatatypeConverter.printBase64Binary(publicKey.getEncoded());
I hope it helps.