How to get public RSA key from unformatted String

2019-04-02 08:24发布

问题:

I have an unformatted public key in a properties file, meaning it only contains the public key hexa value:

K_PUB_CCE =  3082010902820100A515281FAC9ABAA8E966DC1B6EC0F1C431674B4E7BCB718955A34211D5CC6BA53F2C93F67C030A970D4E41341949E6BC3F9336287EEA21702FE663C83D4BAFEE2AAA2EEE7A6AFDC423A159D420E42ABDE2792080249C5D6E25367F804333665CAFB79FD5A59D70B9F69159F4EDAD2DA4B434F41D0EC5217808E7D91FF547C83774E4BDE813302E16377156E52CAF02D1E68371D536AA0E7E32DE484FF4863538DCBC69E8D3E3C1F3CECEA9861DA5516A06D3208F6363B86CF66641BE18C4F41ABD7F1B5CDC9BD964914515DDC58F32F49437BD7E89431C8F484BBCEBA86A5FFF74E01D12E6D1D7EBBF6E5DCD7A9134BF27185F4BD347B23007FF366C0009E14F0203010001

As you can see, it's 538 hexas long. What I wanna achieve is to obtain a java.security.PublicKey with this value.

But when using this method:

private PublicKey createPublicKey(String stringPublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] bytesKey= Hex.decodeHex(stringPublicKey.toCharArray());
    X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(bytesKey);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return  keyFactory.generatePublic(encodedKeySpec);
}

I get the following exception:

java.security.InvalidKeyException: IOException: algid parse error, not a sequence

What am I doing wrong? Is there any other key spec class I should be using?

回答1:

It is rather bothersome to store the public key as a base64 String and then convert it back (since also depending on the platform the format might vary). Using a KeyStore file would be much easier.

In case this is not an option for you, there is another way. You need to input your key (modulus) and the used exponent. The exponent is usually 65537, but there are also ways to find that out for sure (see references). This works for me:

public static void main(String[] args) {
    try{
        // example key generated via: $ openssl genrsa -out tmp-key.pem 2048
        // modulus and exponent extracted via: $ openssl rsa -text -in tmp-key.pem
        // (modulus 514 hex long)
        String pubModulus = "00e45679a14c6cbd4646bbe90b3c820eb19fe8366822ce2e4beed9d158b9e8863bdc4e3f0fbad31144ef193493dff8619a5aaa7504541c5d97c20cd29c87435eb2e4fbbab8e3b02d82353016c91e971e8d497e1699eeb77a833833617369333c4d0d93cd6f1a9e6090fafd4cbf00b1e0fc6478003ed6e762fb921446c84f0f281117e692a5f76e4b75cbb1252436b3268893195d25344cc3e5d5a52560243d62e5ce8a7bd72a89fce5fbf009435901e274c3cca5eab0f2b2057683ed6e3ed851723adbabb4028a7900ddc46d8c894097c07ab071f6af8fc1c520681e0abd7685f4851d360a7c6d425373da806d356a517ae764093e6999d2cc9305f46a7e1744ed";
        String pubExp = "65537"; // most common exponent is 65537 which encodes to AQAB

        PublicKey key = createPublicKey(pubModulus, pubExp);
        byte[] data = encrypt("Secret Message", key);
        System.out.println(""+data.length); // prints "256" in this example code
    } catch(Exception e) {
        e.printStackTrace();
    }
}

public static PublicKey createPublicKey(String stringPublicKey, String exponentString) throws NoSuchAlgorithmException, InvalidKeySpecException {
    try{
        // for key modulus and exponent value as base64 encoded string from key file
        //BigInteger keyInt = new BigInteger(Base64.getDecoder().decode(stringPublicKey.getBytes("UTF-8")));
        //BigInteger exponentInt = new BigInteger(Base64.getDecoder().decode(exponentString.getBytes("UTF-8")));

        // for key modulus and exponent values as hex and decimal string respectively
        BigInteger keyInt = new BigInteger(stringPublicKey,16); // hex base
        BigInteger exponentInt = new BigInteger(exponentString,10); // decimal base

        RSAPublicKeySpec keySpeck = new RSAPublicKeySpec(keyInt, exponentInt);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return  keyFactory.generatePublic(keySpeck);
    } catch(Exception e) {
        e.printStackTrace();
    }

    return null;
}

public static byte[] encrypt(String message, PublicKey key) {
    try{
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] data = cipher.doFinal(message.getBytes());
        return data;
    } catch(Exception e) {
        e.printStackTrace();
    }

    return new byte[1];
}

Edit:

I am sorry, I just saw that you use a Hex string. The original RSA format should be some base64 encoded value. Since you already have the hex value, the BigInteger creation for the modulus should be different (please see source code edit). Also as mentioned in the comments below, the platform issue does not arise for the Hex string or the Base64 format. The only thing to be aware of here, is to specify a format (such as UTF-8) when encoding from String to Base64 with getBytes(). If no format is specified, the platform default will be used.

References:

RSA exponent 65537

Convert RSA Hex String to PublicKey

How to find out your exponent

About the hex key length/format

Encoding String to Base64



回答2:

What you have is almost the hex encoding of the DER encoding of a PKCS#1 public key. This is not the same as the SubjectPublicKeyInfo that is handled by the X509EncodedKeySpec class. I say almost because the encoding is slightly buggy. The modulus is encoded as a negative integer, which is wrong.

The easiest way to handle this is to simply break out the modulus and exponent by hand as in the following small example using your hex string.

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;

public class ProcessPKCS1RSAPublicKey {

    public static void main(String[] args) throws Exception {
        String modulusHex = "A515281FAC9ABAA8E966DC1B6EC0F1C431674B4E7BCB718955A34211D5CC6BA53F2C93F67C030A970D4E41341949E6BC3F9336287EEA21702FE663C83D4BAFEE2AAA2EEE7A6AFDC423A159D420E42ABDE2792080249C5D6E25367F804333665CAFB79FD5A59D70B9F69159F4EDAD2DA4B434F41D0EC5217808E7D91FF547C83774E4BDE813302E16377156E52CAF02D1E68371D536AA0E7E32DE484FF4863538DCBC69E8D3E3C1F3CECEA9861DA5516A06D3208F6363B86CF66641BE18C4F41ABD7F1B5CDC9BD964914515DDC58F32F49437BD7E89431C8F484BBCEBA86A5FFF74E01D12E6D1D7EBBF6E5DCD7A9134BF27185F4BD347B23007FF366C0009E14F";
        BigInteger modulus = new BigInteger(modulusHex, 16);
        String exponentHex = "010001";
        BigInteger exponent = new BigInteger(exponentHex, 16);
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(modulus, exponent);
        KeyFactory keyFac = KeyFactory.getInstance("RSA");
        RSAPublicKey rsaPub = (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        System.out.println(rsaPub);
    }

}


标签: java rsa