Is it possible decrypt an encrypted RSA (or others, shouldn't matter) private keys using JCE and/or BouncyCastle provider (not using openssl bundle)?
I can read unencrypted keys just fine using PrivateKeyFactory.
Googling this gets me through examples of using PEMReader (from BC openssl bundle) that has a password applied to it, but - don't want to use openssl bundle, don't necessarily want to use PEM format, and I can decode PEM using PemReader (from provider bundle). It's what can I do with it afterwards is the question.
I'm looking for some mega-function, or a series thereof that can do it, i.e. I am not looking into parsing the ASN1 of the encrypted key, figuring out the encryption method, passing the input through the cipher, etc.
If you have an encrypted PKCS#8 key in binary format (i.e. not in PEM format) the following code shows how to retrieve the private key:
public PrivateKey decryptKey(byte[] pkcs8Data, char[] password) throws Exception {
PBEKeySpec pbeSpec = new PBEKeySpec(password);
EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(pkcs8Data);
SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
Key secret = skf.generateSecret(pbeSpec);
PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
If you have a PEM format, remove the header (first line), the footer(last line) et convert the remaining content from base64 to regular byte array.
Related
I have this JAVA code & I need to write the same thing in php:
public static String signMsg(String msg, String privateKey)
throws Exception {
byte[] bytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
Signature ps = Signature.getInstance("SHA256withRSA");
ps.initSign(kf.generatePrivate(spec));
ps.update(msg.getBytes("UTF-8"));
byte[] sigBytes = ps.sign();
return Base64.getEncoder().encodeToString(sigBytes);
}
Any idea how to do that?
Thanks in advance :)
Regarding your first approach: A signature is created with the private key. The public key is used to verify the signature. Regarding your second approach: A HMAC is not the same as a signature.
The Java code loads a private key in PKCS8 format, PEM encoded without header and footer. In the PHP code the key can be read in the same format and encoding. Alternatively, the key can be loaded in PKCS#1 format. Regarding the encoding, a PEM or DER encoded key is also accepted.
Additionally, it must be specified which algorithm is used for signing. The Java code applies RSA with PKCS#1 v1.5 padding and SHA-256 as digest. Furthermore, the generated signature is Base64 encoded. In order for the PHP code to provide the same RSA signature, the same parameters must be used.
Note that signing does not necessarily generate the same signature using the same message and the same key. It depends on the algorithm. However, in the case of RSA with PKCS#1 v1.5 padding, always the same signature is generated (deterministic). For PSS, on the other hand, a different signature is generated each time (probabilistic).
The following PHP code uses the PHPSECLIB and generates the same signature as the Java code:
use phpseclib3\Crypt\RSA;
$privateKey= 'MIIEvg...';
$signatureB64 = base64_encode( // Base64 encode signature
RSA::load($privateKey)-> // Choose RSA, load private PKCS8 key
withHash('sha256')-> // Choose SHA-256 as digest
withPadding(RSA::SIGNATURE_PKCS1)-> // Choose PKCS#1 v1.5 padding
sign('The quick brown fox jumps over the lazy dog') // Sign messsage
);
print($signatureB64);
I need to convert an RSA private key located in memory in PEM format into a PrivateKey on Android.
The problem seems to have been solved for public keys but I'm struggling to get it to work for a private key. I'm trying the following code:
String pemkey = "MIICWwIBAAKBgQDIuL0SzG1+wgyaoyDyHvYIaG10ePXBHqaKTnYyZfY5RzaEFLE/vBdFN2Di7AMH3/5iN/YFQqLsVjKqzX3E3LM2dJOZ9qSWeYArSyQPbhy0eM/3amwchvtLvhVLm2UqVFLjiPGlyYX3D75ETD5tmgulAc5ZDRtGqYVoLKPmZ0USPwIDAQABAoGAErfDjf65UUJISZ1fw6Rmfic62csz47P3hNtHQ3Dlsra020FQvChOpTpCUzb+G1xkjQU58Iijx9VL+Uiba2HHZmiJX2LgS3KKqKFZKmbKZnZQTiw+2o+4AXhtcAYfSAJE9TgRPEhwhZmzV2cvfUk5AjnOghSn2gGjdD1g4xtH22ECQQD/ZbfEd2HEGqHf6j/AVMW+N/Q1xtYIB8r0CWxF6cNw5iq/8Ce9ujpnAFi0vgtojyKDlgwBp4XMU2C4is49EkFhAkEAyTH96mS8dExAAmi3Mm2seUIEOtKwuLD6BEECecPyZSIOd24tfNbmA7Ri6MpGjyLZoNoJQ0AJGcnWU1tnc8bXnwJAS+jYyP1OwrHDwUDnt+u6ZoJNBJrXzMU8LnKKivEjFPBkbm4b8cljSHAS7Y266FX6xS+Y2/kFzKhPjCo9iGtfoQJAOv39hYyj9TWmTw6FKLQfri49L0I3ru+1Xynwn+NkX2Ls+vfDPqeEKfHqTneA2NdPGGrV7HIKORWFUkuqubfD4QJAK60RuhDSeH+ZljcYLhbHLoTnja/uTcvDAd0M4ll2HUNId4jPbYl1qw7OQwfg8apKmGwp7HGW50o/EItvvJrR7w==";
byte[] encoded = Base64.decode(pemkey, Base64.DEFAULT);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey sessionkey = kf.generatePrivate(keySpec);
(The key is not the same as for my Bitcoin wallet so don't bother ^^)
The last line gives the error
java.security.spec.InvalidKeySpecException:
Must use RSAPublicKeySpec or PKCS8EncodedKeySpec;
was java.security.spec.X509EncodedKeySpec
I tested the pemkey string to be ok in other languages (e.g. Python RSA.importkey) and it works fine.
Edit:
On a suggestion by a comment (and the answer to the question linked as doublicate), I also tried with X509EncodedKeySpec replaced by PKCS8EncodedKeySpec. Then the new error I get is
java.security.spec.InvalidKeySpecException:
java.lang.RuntimeException:
error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
Thanks to comments by Greggz and James I was able to get it to work. There were two problems:
X509EncodedKeySpec had to be replaced by PKCS8EncodedKeySpec
The provided pemkey was PKCS#1 (to be recognized by 'BEGIN RSA PRIVATE KEY') but needs to be PKCS#8 (to be recognized by 'BEGIN PRIVATE KEY').
Firstly I tried to ask this on security -- I got some upvotes but it seems it has had no answer for a week now. I understand that this is openssl related, however it stems from using a java KeyPairGenerator object so I feel it may be valid for stack overflow. Please see the code below:
I have been using java's KeyPairGenerator in order to generate public/private keys within a program so that I can encrypt and decrypt files(also using java encrypt/decrypt methods). I want to be able to move over to using openssl in order to generate these public private keypairs however I keep getting padding exceptions when I decrypt my files if i used command line generated openssl keys. For example instead of using java's KeyPairGenerator, I try and use openssl to generate keys:
openssl rsa -in keypair.pem -outform DEF -pubout -out public.der
openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der
Where I try to use the DER files to encrypt/decrypt my files. Ultimately every key format I have tried seems to give me problems.
I'm assuming that this means that the format of the keys in my openssl commands do not match up how java's KeyPairGenerator works. Here is a snippet of my key generating code:
public void createPublicPrivateKeys() throws IOException, NoSuchAlgorithmException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
logger.info("Password to encrypt the private key: ");
String password = in.readLine();
logger.info("Generating an RSA keypair.");
// Create an RSA key key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.genKeyPair();
logger.info("Done generating the keypair.\n");
// Write public key out to a file
String publicKeyFilename = "publicKey.der";
logger.info("Public key filename: " + publicKeyFilename);
// Get encoded form of the public key for future usage -- this is X.509 by default.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// Write the encoded public key
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(publicKeyBytes);
fos.close();
String privateKeyFilename = "privateKey.der";
logger.info("Private key filename: " + privateKeyFilename);
// Get the encoded form -- PKCS#8 by default.
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
// Encrypt the password
byte[] encryptedPrivateKeyBytes = new byte[0];
try {
encryptedPrivateKeyBytes = passwordEncrypt(password.toCharArray(), privateKeyBytes);
} catch (Exception e) {
e.printStackTrace();
}
fos = new FileOutputStream(privateKeyFilename);
fos.write(encryptedPrivateKeyBytes);
fos.close();
}
What is the openssl equivalent command line statement of using java's standard KeyPairGenerator? Also note that using outside packages such as bouncy castle is not an option.
OpenSSL commandline rsa or pkcs8 do not generate a keypair; they only convert from one form to another and/or display. For RSA (only), genrsa generates a keypair, in OpenSSL's 'legacy' privatekey format which is not (easily) compatible with Java. Your rsa and pkcs8 commands do convert the legacy format to the "X.509" (SPKI) and PKCS#8 DER formats Java prefers. OpenSSL since 1.0.0 also has genpkey which generates keys, or where applicable parameters, for all supported asymmetric algorithms including RSA, and defaults to PKCS#8 output.
Your Java code has a passwordEncrypt step that is not standard Java and not explained. OpenSSL library supports password-based encryption of PKCS#8 according to that standard if that is what your passwordEncrypt does, but most OpenSSL commandline functions don't.
If you are trying to use the private.der of your example in Java which is trying to do any kind of password-based (or other) decryption on it, that won't work because it isn't encrypted; it isn't even in the PKCS#8 structure used for an encrypted key.
However that error would occur before you even try to decrypt any data, or more likely a working key.
This question already has an answer here:
convert openSSH rsa key to javax.crypto.Cipher compatible format
(1 answer)
Closed 6 years ago.
In one of our applications private keys are stored using BouncyCastle's PEMWriter. At the moment I am investigating if we can get rid of the BouncyCastle dependency since Java 7 seems to have everything we need. The only issue is that I can not read the private keys stored in the database as PEM-encoded strings (the certificates/public keys are fine).
If I save the PEM-encoded string of the private key from the database to a file I can run OpenSSL to convert the key to PKCS#8 format like this:
openssl pkcs8 -topk8 -inform PEM -outform DER \
-in private_key.pem -out private_key.der -nocrypt
The resulting output I can base64 encode and then read using this bit of Java/JCA code:
byte[] privateKeyBytes =
DatatypeConverter.parseBase64Binary(privateKeyDERcontents);
PrivateKey prKey =
KeyFactory.getInstance("RSA").
generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
This private key matches the public key stored as expected, i.e. I can round-trip from plaintext to ciphertext and back.
The question I have is: can I directly read the original PEM encoding somehow?
EDIT
Here is a bit of code that reads the strings in question using BouncyCastle:
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
PEMReader pemReader = new PEMReader(new StringReader(privateKeyPEM));
KeyPair keyPair = (KeyPair) pemReader.readObject();
PrivateKey key = keyPair.getPrivate();
The "privateKeyPEM" is the PEM encoded string in the database, otherwise this example is self-contained. Interestingly it already uses the JCA KeyPair object as output. To rephrase my original question: can I do the equivalent of the code above without depending on PEMReader (and in turn quite a few other BouncyCastle classes)?
Key inside of PEM file is already stored in PKCS#8 format, so if it is not encrypted with password you can just remove headers (-----BEGIN RSA PRIVATE KEY-----), Base64-decode input, and get the needed bytes.
I am trying to read the RSA public and private keys files in Java.
My RSA public and private key is generated using PuttyGen. (SSH-2 RSA, 1024 bits)
The code I am using for reading file is:
//public key
pubkeyBytes = getBytesFromFile(new File(pubKeyfileName));
KeySpec pubSpec = new X509EncodedKeySpec(pubkeyBytes);
RSAPublicKey pubKey =(RSAPublicKey) rsakeyFactory.generatePublic(pubSpec);
//private key
privkeyBytes = getBytesFromFile(new File(privKeyfileName));
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privkeyBytes);
PrivateKey privKey = rsakeyFactory.generatePrivate(privSpec);
It throws:
java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(Unknown Source)
Putty uses its own key format. You need to export the Putty key to the OpenSSH format - see How to convert SSH keypairs generated using PuttyGen(Windows) into key-pairs used by ssh-agent and KeyChain(Linux).
Then, you need to convert the OpenSSH key to pkcs8 format - see How to Load RSA Private Key From File. The Cygwin version of openssh will work just fine for this; there's no need to find a Unix system to run openssh.