Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exporting RSA key object to XML in Java

I am successfully running RSA encryption/decryption in Java. This is how I generated the key.

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(1024);
        KeyPair keypair = kpg.generateKeyPair();
        oos.writeObject(keypair);

But now I need to integrate my system with .Net code. Is it possible to export this KeyPair object into XML in the following format(as that .Net code can only accept keys in XML format):

<RSAKeyValue>
    <Modulus>.....</Modulus>
    <Exponent>......</Exponent>
    <P>.....</P>
    <Q>....</Q>
    <DP>.......</DP>
    <DQ>......</DQ>
    <InverseQ>.........</InverseQ>
    <D>........</D>
</RSAKeyValue>
like image 960
dvl Avatar asked Mar 03 '11 09:03

dvl


2 Answers

Try this:

// key pair is in 'kp'
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPrivateCrtKeySpec ks = kf.getKeySpec(
    kp.getPrivate(), RSAPrivateCrtKeySpec.class);
System.out.println("<RSAKeyValue>");
System.out.println("    <Modulus>" + ks.getModulus() + "</Modulus>");
System.out.println("    <Exponent>" + ks.getPublicExponent() + "</Exponent>");
System.out.println("    <P>" + ks.getPrimeP() + "</P>");
System.out.println("    <Q>" + ks.getPrimeQ() + "</Q>");
System.out.println("    <DP>" + ks.getPrimeExponentP() + "</DP>");
System.out.println("    <DQ>" + ks.getPrimeExponentQ() + "</DQ>");
System.out.println("    <InverseQ>" + ks.getCrtCoefficient() + "</InverseQ>");
System.out.println("    <D>" + ks.getPrivateExponent() + "</D>");
System.out.println("</RSAKeyValue>");

This will work for all RSA key pairs which internally use the 'CRT' representation, and allow export; this is the case for the key pairs that the JDK will generate by default with the code you show.

(Here I print out the key on System.out instead of writing it to a file, but you get the idea.)

like image 163
Thomas Pornin Avatar answered Oct 23 '22 04:10

Thomas Pornin


Thomas Pornin's solution is essentially correct but didn't work for me because the methods, e.g. getModulus(), return BigInteger which results in a numeric string, whereas the standard .Net XML format uses Base64 encoded bytes.

I used "getModulus().toByteArray()" to get the bytes. Then I needed to trim the first element of the array (except for Exponent) because there's an unwanted zero byte. (I presume because BigInteger is signed it adds an extra byte so the leading bit can indicate sign).

I've posted the code on GitHub.

The main bit is:

static String getPrivateKeyAsXml(PrivateKey privateKey) throws Exception{
    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
    RSAPrivateCrtKeySpec spec = keyFactory.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
    StringBuilder sb = new StringBuilder();

    sb.append("<RSAKeyValue>" + NL);
    sb.append(getElement("Modulus", spec.getModulus()));
    sb.append(getElement("Exponent", spec.getPublicExponent()));
    sb.append(getElement("P", spec.getPrimeP()));
    sb.append(getElement("Q", spec.getPrimeQ()));
    sb.append(getElement("DP", spec.getPrimeExponentP()));
    sb.append(getElement("DQ", spec.getPrimeExponentQ()));
    sb.append(getElement("InverseQ", spec.getCrtCoefficient()));
    sb.append(getElement("D", spec.getPrivateExponent()));
    sb.append("</RSAKeyValue>");

    return sb.toString();
}

static String getElement(String name, BigInteger bigInt) throws Exception {
    byte[] bytesFromBigInt = getBytesFromBigInt(bigInt);
    String elementContent = getBase64(bytesFromBigInt);
    return String.format("  <%s>%s</%s>%s", name, elementContent, name, NL);
}

static byte[] getBytesFromBigInt(BigInteger bigInt){
    byte[] bytes = bigInt.toByteArray();
    int length = bytes.length;

    // This is a bit ugly.  I'm not 100% sure of this but I presume
    // that as Java represents the values using BigIntegers, which are
    // signed, the byte representation contains an 'extra' byte that
    // contains the bit which indicates the sign.
    //
    // In any case, it creates arrays of 129 bytes rather than the
    // expected 128 bytes.  So if the array's length is odd and the
    // leading byte is zero then trim the leading byte.
    if(length % 2 != 0 && bytes[0] == 0) {
        bytes = Arrays.copyOfRange(bytes, 1, length);
    }

    return bytes;
}

static String getBase64(byte[] bytes){
    return Base64.getEncoder().encodeToString(bytes);
}
like image 45
codybartfast Avatar answered Oct 23 '22 04:10

codybartfast