14

I have my Private and Public keys in a String in base64 which where encoded using ANS1 DER. I tried creating the instance of a java PrivateKey and PublicKey:

byte [] llave2 = DatatypeConverter.parseBase64Binary(key);
PKCS8Key pkcs8 = new PKCS8Key( llave2, password.toCharArray()); //line 2
llave2 = pkcs8.getDecryptedBytes();                             //line 3
certificado = DatatypeConverter.parseBase64Binary(cer);

KeyFactory kf = KeyFactory.getInstance("RSA");  
PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(llave2);
PrivateKey privateKey = kf.generatePrivate(ks);
X509EncodedKeySpec x = new X509EncodedKeySpec(certificado);
PublicKey publicKey = kf.generatePublic(x);

I get the following error in PublicKey publicKey = kf.generatePublic(x).

    java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException:     IOException: ObjectIdentifier() -- data isn't an object ID (tag = -96)
    at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(Unknown Source)
    at java.security.KeyFactory.generatePublic(Unknown Source)
    at vital.cancelaciones.GeneraXMLCancelacion.main(GeneraXMLCancelacion.java:118)
Caused by: java.security.InvalidKeyException: IOException: ObjectIdentifier() -- data isn't an object ID (tag = -96)
    at sun.security.x509.X509Key.decode(Unknown Source)
    at sun.security.x509.X509Key.decode(Unknown Source)
    at sun.security.rsa.RSAPublicKeyImpl.<init>(Unknown Source)
    at sun.security.rsa.RSAKeyFactory.generatePublic(Unknown Source)
    ... 3 more

I guess I should do something similar with the public key as done with the private key in lines 2 and 3. Because the certificate is also encrypted. Any suggestions?

user1084509
  • 1,832
  • 10
  • 33
  • 48

1 Answers1

37

To test your scenario, I've created an RSA private key with openssl.

openssl genrsa -out private.pem 1024

Then I've converted this key to PKCS#8 DER format.

openssl pkcs8 -topk8 -inform PEM -in private.pem -outform DER -out private.der -nocrypt

The manual of openssl refers to PKCS#8 and DER both as formats, so as far as I'm concerned the following happens:

  • pkcs8 tells openssl that I want to work with private keys in PKCS#8 format.
  • -topk8 tells it that the private key I'm going to specify with -in is not in PKCS#8 (otherwise it'll assume it is).
  • -inform and -in specify that I want to convert the (PEM) private key to PKCS#8 (without -topk8 it'll try to convert a key already in PKCS#8 format to a standard key format).
  • -outform and -out tells it I want a DER formatted key as output.
  • -nocrypt tells it that I don't want to encrypt the key.

Then, with my RSA key (in standard format) I've created a certificate.

openssl req -new -x509 -keyform PEM -key private.pem -outform DER -out public.der

The certificate contains the public key corresponding to my private key.

After all of these, I've encoded both the private key and the certificate with Base64.

base64 private.der > private.der.b64
base64 public.der > public.der.b64

The following files were generated.

private.pem      # standard
private.der      # pkcs8/DER
private.der.b64 
public.der       # x509/DER
public.der.b64   
public static void main(String[] args) throws IOException, GeneralSecurityException {
  // get a handle on the base64 encoded key and certificate
  File privateKeyFile = new File("private.der.b64");
  File publicKeyFile = new File("public.der.b64");

  // pull them into arrays
  byte[] privateKeyBytes = toByteArray(privateKeyFile);
  byte[] publicKeyBytes = toByteArray(publicKeyFile);

  // decode them
  privateKeyBytes = toDecodedBase64ByteArray(privateKeyBytes);
  publicKeyBytes = toDecodedBase64ByteArray(publicKeyBytes);

  // get the private key
  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  KeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

  // get the public key
  CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
  Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes));
  PublicKey publicKey = certificate.getPublicKey();
}

private static byte[] toByteArray(File file) throws IOException {
  // java 7's try-with-resources statement
  try (FileInputStream in = new FileInputStream(file);
      FileChannel channel = in.getChannel()) {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    channel.transferTo(0, channel.size(), Channels.newChannel(out));
    return out.toByteArray();
  }
}

private static byte[] toDecodedBase64ByteArray(byte[] base64EncodedByteArray) {
  return DatatypeConverter.parseBase64Binary(
      new String(base64EncodedByteArray, Charset.forName("UTF-8")));
}

The main problem was that you had a certificate instead of a public key. The certificate contains the public key, but it cannot be loaded with X509EncodedKeySpec(...), this is why the CertificateFactory has to be used instead.

(By the way here is a great article/tutorial on openssl and Java cryptography usage. I've got my info partly from there.)

Cheeso
  • 189,189
  • 101
  • 473
  • 713
Kohányi Róbert
  • 9,791
  • 4
  • 52
  • 81
  • My key is stored in base64, but when I try to implement the following code to decrypt it: `PKCS8Key pkcs8 = new PKCS8Key( llave, password.toCharArray());` I get the following error: `org.apache.commons.ssl.ProbablyNotPKCS8Exception: asn1 parse failure: java.lang.ClassCastException: org.apache.commons.ssl.asn1.DERApplicationSpecific cannot be cast to org.apache.commons.ssl.asn1.DERSequence`. What should I do instead? – user1084509 Dec 10 '11 at 14:41
  • 1
    @user1084509 The page I've linked says near at the top that *The InputStream can be DER (raw ASN.1) or PEM (base64).* You've said you have a key in DER format. If you have this raw DER key encoded in Base64 then first you have to decode it (probably). If you're using JDK 6, then you can do it with [parseBase64Binary(...)](http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/DatatypeConverter.html#parseBase64Binary(java.lang.String)). I'll update my answer with an example. – Kohányi Róbert Dec 10 '11 at 15:25
  • wowwww! thank you very much! That really worked for my private key. Now the problem comes with the public key, because I decode it using the parseBase64Binary (...) function, but is there an equivalence of `PKCS8Key pkcs8 = new PKCS8Key( llave2, password.toCharArray()); llave2 = pkcs8.getDecryptedBytes();` for the public key which is also DER encoded but has a X509 Format? – user1084509 Dec 10 '11 at 15:48
  • @user1084509 *Now the problem comes with the public key, because I decode it using the parseBase64Binary (...) function* Is it encoded in Base64 like your private key? If yes, you should decode it; otherwise not. The *PKCS8Key* class is only used because if your private key is encrypted (not the same as *encoded*) *PKCS8EncodedKeySpec* just wont't *eat it*. For the certificate you can try passing your key (as a byte array) to the constructor of *X509EncodedKeySpec*. And I don't get the *is there an equivalence of [...]* part of your question. Update your question and include an explanation. – Kohányi Róbert Dec 10 '11 at 16:00
  • I really appreciate your help because this is driving me crazy. I already updated my question, you can check it out. – user1084509 Dec 10 '11 at 16:07
  • By the way, my certificate is also encripted, do you have any idea of how to decrypted? – user1084509 Dec 10 '11 at 16:22
  • Ask your supplier of certificates. If it is really encrypted, then they should specify the algorithm. There is no encryption method specified for certificates, they are supposed to be public (although I have seen encrypted certificates to protect private data in transit). – Maarten Bodewes Dec 11 '11 at 05:13