In my App I generate a public/private key pair and store them for later usage on disk. Loading and re-initialising the private key works fine but for the private key I get a Unknown KeySpec type: java.security.spec.PKCS8EncodedKeySpec - and I have no idea why.
That's how I create and save the keys (code a bit simplified to be easier to read):
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(4096);
KeyPair keyPair = kpg.generateKeyPair();
privKey =keyPair.getPrivate();
pubKey =keyPair.getPublic();
DataOutputStream out=new DataOutputStream(ctx.openFileOutput(PRIVKEY_FILE,Context.MODE_PRIVATE));
byte[] data=privKey.getEncoded();
out.write(data);
out.close();
DataOutputStream out=new DataOutputStream(ctx.openFileOutput(PUBKEY_FILE,Context.MODE_PRIVATE));
byte[] data=pubKey.getEncoded();
out.write(data);
out.close();
Next loading of the private key works fine:
DataInputStream in=new DataInputStream(ctx.openFileInput(PRIVKEY_FILE));
byte[] data=new byte[in.available()];
in.readFully(data);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
KeyFactory kf = KeyFactory.getInstance("RSA");
privKey = kf.generatePrivate(keySpec);
decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, privKey);
Similar code for the public key fails miserably:
DataInputStream in=new DataInputStream(ctx.openFileInput(PUBKEY_FILE));
byte[] data=new byte[in.available()];
in.readFully(data);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
KeyFactory kf = KeyFactory.getInstance("RSA");
pubKey = kf.generatePublic(keySpec); --> here the exception is thrown
encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, pubKey);
So what am I doing wrong? What is the proper way of loading a public keys data from disk?
Thanks!
Public and Private keys are encoded differently. Whilst private keys are encoded in PKCS #8, public keys are not. They are instead encoded in X.509 according to the ASN.1 specifications.
Description from the Key.getFormat() method:
Returns the name of the primary encoding format of this key, or null if this key does not support encoding. The primary encoding format is named in terms of the appropriate ASN.1 data format, if an ASN.1 specification for this key exists. For example, the name of the ASN.1 data format for public keys is SubjectPublicKeyInfo, as defined by the X.509 standard; in this case, the returned format is "X.509". Similarly, the name of the ASN.1 data format for private keys is PrivateKeyInfo, as defined by the PKCS #8 standard; in this case, the returned format is "PKCS#8".
According to this, instead of reading public keys as PKCS #8, you should read it as X.509.
Consider changing your public key reading code from:
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
to:
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(data);
Related
I had generate public key using Java Spring Security, but I can not use that public key to encrypt the data using Nodejs crypto library. I think it is because of its format(X509).
My Nodejs code
module.exports.encryptRsa = (toEncrypt, pemPath) => {
let absolutePath = path.resolve(pemPath);
let publicKey = fs.readFileSync(absolutePath, "utf8");
let buffer = Buffer.from(toEncrypt);
let encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
My Java code
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlgorithm);
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
String formatPrivate = privateKey.getFormat(); // PKCS#8
String formatPublic = publicKey.getFormat(); // X.509
FileWriter fos = new FileWriter("publicKey.pem");
fos.write("-----BEGIN RSA PUBLIC KEY-----\n");
fos.write(enc.encodeToString(publicKeyBytes));
fos.write("\n-----END RSA PUBLIC KEY-----\n");
fos.close();
Java's getEncoded() method returns the public key in format called 'spki' by Node crypto. Java's name for that format is "X.509", an unfortunate choice because it causes confusion with certificates of that name.
The proper PEM header for spki keys is simply -----BEGIN PUBLIC KEY-----. Just get rid of RSA in the header and footer.
I have an unencrypted PKCS8 encoded file that represents a Private Key. It can be any of these private key types - RSA, DSA or EC. I viewed these files in an ASN1 decoder (https://lapo.it/asn1js/) and I could see the type (RSA, DSA or EC) in the data.
Is there a way to read the PKC8 private key data into the correct Private Key Java object without specifying the key type in code like this -
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8key);
KeyFactory factory = KeyFactory.getInstance("RSA"); // Avoid "RSA" here?
PrivateKey privateKey = factory.generatePrivate(spec);
Is there a way to avoid specifying the algorithm in KeyFactory.getInstance("RSA")? Shouldn't this be determined from the PKCS8EncodedKeySpec since it is available in the PKCS8 data?
Sample unencrypted PKCS8 data and their ASN1 decodings which show the key type -
DSA - link
EC - link
RSA - link
This can be achieved with the help of BouncyCastle APIs -
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
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've been strugling with reading a publickey file which I want to get the key sting in the file and use it to encrypt another file. I'm using RSA PKCS1 v1.5 in encrypting and signing the file with SH1 hashing algorythim but thats not the problem, the problem is that I've been supplied with the publickey file to use when encrypting and I cant seem to win with reading the file and generating a publicKey object.
Here's the code:
void setPublicKey(String file)
{
try
{
FileInputStream keyfis = new FileInputStream(file);
byte[] encKey = new byte[keyfis.available()]; keyfis.read(encKey);
keyfis.close();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// I get an exception on the below line
publicKey = keyFactory.generatePublic(pubKeySpec);
} catch (Exception e)
{
e.printStackTrace();
}
}
Can someone please help!!
AFAIK X509 encoded keys are binary files encoded using ASN.1. Therefore the question on new-lines at the end does not make any sense.
If you have a text file you have a PEM encoded file and I am currently not sure which KeySpec you have to use in this case.
You may convert the PEM encoded key to a DER encoded key (e.g. using OpenSSL) or you can use BouncyCastle which as support for loading PEM encoded keys.
BTW: Using keyfis.read(encKey); is dangerous as the read method only reads up encKey bytes but don't have to. Better create a DataInputStream from the InputStream and use readFully(encKey):
new DataInputStream(keyfis).readFully(encKey);
Found the solution but not sure yet if its the right solution coz I still have to get the PrivateKey and decrypt the file but for now I was able to encrypt it using the supplied PublicKey as the modulus but I don’t have the exponent and I just used some common number “65537” as the exponent
Which I read that it is not a critical part of the encryption.
I had to change the logic to use the RSAPublicKeySpec (which uses BigInteger and Base64Decoder)instead of X509EncodedKeySpec to set the KeySpec
And continue to use the KeyFactory object to generate the public key.
Now this logic NEEDS the modulus and exponent.
byte[] buffer = new byte[(int) new File(file).length()];
BufferedInputStream f = new BufferedInputStream(new FileInputStream(file));
f.read(buffer);
String modulusBase64 = new String(buffer);
BASE64Decoder b64dec = new BASE64Decoder();
String exponentBase64 = "65537";
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger (1, b64dec.decodeBuffer(modulusBase64)), new BigInteger(1, b64dec.decodeBuffer(exponentBase64)));
KeyFactory publicKeyFactory = KeyFactory.getInstance("RSA");
publicKey = publicKeyFactory.generatePublic(publicKeySpec);
//This is the PublicKey in the file. "J45t4SWGbFzeNuunHliNDZcLVeFU7lOpyNkX1xX+sVNaVJK8Cr0rSjUkDC8h9n+Zg7m0MVYk0byafPycmzWNDynpvj2go9mXwmUpmcQprX1vexxT5j1XmAaBZFYaJRcPWSVU92pdNh1Sd3USdFjgH0LQ5B3s8F95xdyc/5I5LDKhRobx6c1gUs/rnJfJjAgynrE4AsNsNem+STaZWjeb4J+f5Egy9xTSEl6UWxCClgCwhXopy10cBlH8CucpP0cyckOCIOloJ7mEMRCIpp6HPpYexVmXXSikTXh7aQ7tSlTMwUziIERc/zRpyj1Nk96Y7V8AorLFrn1R4Of66mpAdQ=="
I want to generate a privatekey PKCS8 format encrypted with password, and I try with this code:
String password = "123456";
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048);
KeyPair key = gen.generateKeyPair();
PrivateKey privateKey = key.getPrivate();
PublicKey publicKey = key.getPublic();
FileOutputStream pvt = new FileOutputStream("d:\\pvt123456.der");
try {
pvt.write(privateKey.getEncoded());
pvt.flush();
} finally {
pvt.close();
}
FileOutputStream pub = new FileOutputStream("d:\\pub123456.der");
try {
pub.write(publicKey.getEncoded());
pub.flush();
} finally {
pub.close();
}
But I don´t know how to encrypt a password with 3des to be compatible with openssl format.
I know it's a little bit late but I also have been looking for a way to do this and while i was searching I found your question, now that I have found a way to do this I decided to come back and share this:
// generate key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// extract the encoded private key, this is an unencrypted PKCS#8 private key
byte[] encodedprivkey = keyPair.getPrivate().getEncoded();
// We must use a PasswordBasedEncryption algorithm in order to encrypt the private key, you may use any common algorithm supported by openssl, you can check them in the openssl documentation http://www.openssl.org/docs/apps/pkcs8.html
String MYPBEALG = "PBEWithSHA1AndDESede";
String password = "pleaseChangeit!";
int count = 20;// hash iteration count
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(MYPBEALG);
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(MYPBEALG);
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] ciphertext = pbeCipher.doFinal(encodedprivkey);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(MYPBEALG);
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, ciphertext);
// and here we have it! a DER encoded PKCS#8 encrypted key!
byte[] encryptedPkcs8 = encinfo.getEncoded();
This example code is based on the folowing code I found: http://www.jensign.com/JavaScience/PEM/EncPrivKeyInfo/EncPrivKeyInfo.java
but the folowing resource also helped me to understand a little bit better: http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html