Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert a X509 Public key to RSA public key

I have a public key in the following format

-----BEGIN PUBLIC KEY----- xxxxxxxx -----END PUBLIC KEY-----

I need to convert this into the following format

-----BEGIN RSA PUBLIC KEY----- xxxxxxxxx -----END RSA PUBLIC KEY-----

Basically, the issue is that I am working with a third party library which is written in Java.

  1. The third party library uses Java class "RSAPublicKeySpec" to generate an instance of type RSAPublicKey from a String.

  2. The String that I am supplying to this third party library is taken from a file which is in the following format:

-----BEGIN PUBLIC KEY----- xxxxxxxx -----END PUBLIC KEY-----

  1. After poking around with the code a bit, I could see that if I use the java class "X509EncodedKeySpec" to load this public key, the signature verification portion of my code works perfectly. However, since the code is a third-party library, I don't have an option of changing the class type in their code. I would need to somehow make sure that the input I am supplying to the library is compatible with "RSAPublicKeySpec" class so that the public key is loaded correctly.
like image 298
sunsin1985 Avatar asked Feb 09 '23 10:02

sunsin1985


1 Answers

The "RSA PUBLIC KEY" format was used in very early SSLeay, which evolved into OpenSSL, but obsoleted before 2000 I believe, which was very early days for Java and I think before Java had any crypto even the restricted kind then allowed for export from the US. In short, the "RSA PUBLIC KEY" format is the RSA-specific format from PKCS#1, whereas "PUBLIC KEY" is the X.509 generic structure that handles numerous (and extensible) algorithm. So it would be interesting to know how the developers of your Java library got themselves into this bizarre limitation. But anyway ...

Although this format is long obsolete, OpenSSL still supports it. If you have the openssl commandline available (could be on another system which you copy the file/data to and back) just do:

openssl rsa -in publickey.pem -out rsapublickey.pem -pubin -RSAPublicKey_out 

Argh! having written the below, now I notice the (more general) dupe at Generating RSA keys in PKCS#1 format in Java (plus several links onward from there) if you use BouncyCastle in Java (or OpenSSL library in C, but you already have the OpenSSL commandline option above).

Anyway, here is an outline of two ways to code in plain Java if you prefer that:

  1. Both of them start by converting the input PEM to bytes. Read the "----BEGIN" line and preferably check it's correct; read all following lines (of base64) up to but not including the "-----END" line; concatenate and decode the base64 to bytes. Java8 provides java.util.Base64; before that you had to fiddle with "internal" classes or add one of several common (but not builtin) libraries like commons-codecs or write it yourself (which isn't that hard). Now choose either step 1 or step 2.

  2. Parse those bytes as the ASN.1 DER encoding of an X.509 SubjectPublicKeyInfo as shown in RFC 5280 including the AlgorithmIdentifier. To be exact: skip the tag and length of the outer sequence; skip the tag, length and contents of the AlgorithmIdentifier -- or better extract the contents and check it is a SEQUENCE of an OID for rsaEncryption and NULL (or possibly omitted) params; then skip the tag and length and first byte (unused-bits) for the BIT STRING and take the (remaining) contents as the encoded key -- which is already the PKCS#1 RSAPublicKey structure you want. Proceed to step 3.

  3. Or use standard JCE to read the X.509 format key: wrap the bytes in an X509EncodedKeySpec, give it to .generatePublic() of a KeyFactory for RSA, and cast the result to RSAPublicKey. Then call .getModulus() and .getPublicExponent() to get the mathematical values and encode them in ASN.1 DER with the structure RSAPublicKey defined in PKCS#1 rfc3447 (used for PKIX/X.509 in rfc3279 2.2.1). BigInteger.toByteArray() gives exactly the big-endian signed two's-complement form that ASN.1 wants, so this consists of: get both .toByteArray() values, add tag=INTEGER (0x02) and length prefixes to each one, then add a tag=SEQUENCE-composite (0x30) and length prefix to their concatenation. Then proceed to step 3.

  4. Now you have the bytes constituting a PKCS#1 RSAPublicKey, convert to PEM: encode to base64; break into lines (at 64 chars) if needed, or always to be safe; and add "BEGIN" and "END" lines, unless not needed.

like image 78
dave_thompson_085 Avatar answered Feb 12 '23 01:02

dave_thompson_085