Cross Platform Asymmetric Key Serialization

Serializing And Deserializing RSA Public Key Across Different Programming Languages


Need for serializing and deserializing RSA Public Key across different programming languages

Several times when you need to perform asymmetric encryption, you need to send the asymmetric public key to another system which is written in some other programming language. There can be some challenges in serializing or deselizing the key in cross platform programming because generally every programming language has their own default format. We will also talk about universal formats for serlizing like PEM and DER for completeness, but this blog does not cover the complete serializing and deserializing code snippets for PEM and DER.

This this blog, I will demonstrate the serialization and deserialization of 2048 bits RSA Public Key in Java, Python and C#. Let’s first see how Keys are generated in each environment or programming language.

Key Generation

Java

final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
final KeyPair rsaKey = generator.generateKeyPair();
final RSAPublicKey publicKey = (RSAPublicKey) rsaKey.getPublic();
System.out.println(publicKey.toString());

Output:
OpenSSLRSAPublicKey{modulus=a3ab24bae4844ecaf6afb81b3336128630027d52cfb0451fb09417a7b8fd7936889cf772fd1fe41371438de13fe61773934a92a2fcdb50bd7a414835a78234448d8f9f90d583e481d129ed6349d2e0df3f2ef28e5996d417d7874ca1691b04805ae2d21c01865239743e954727627d49f5cdef14723a51b39e19fb10139a976433266a092f429c3d8da739b06ca6a00f8db811fb727730ce427be86f9e194b39d88a70d9b8179ce47de2befb7434eacb838d668b59bc9724760a7e2b0da34fbe6e3396a754b12d59895be72233f2d72b4d48f44d87f25dc08095e532250dd1e71106fa379c0d11418da3562e05dd6ffa8a9e13ae5715956080e90f1ae89fcd87,publicExponent=10001}

Python

from Crypto.PublicKey import RSA
rsaKey = RSA.generate(2048)
publicKey = rsaKey.publickey()
print(publicKey.e)
print(publicKey.n)

Output:
65537
28817937766823035356540370697415952458153213885605659028629359809611083619854112491718443405945091474543402300720033739266348094625152880847197152126216773250824697449777285940629620247209408184286674388508584470419567141357620578799758460860101918812523331270043171667396027958551498108505775442755295728615697063178834522635644038991661494823347581777824728705782191703535995372616128047468642125995403265906050442540990500579203816659880732452297734102480382304978619947684636380475455996245791575066997928840074398182108992616740488714017359392435146269144884542191021005458074294862840128718059354408085954440501

C#

using (RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048))
{
    RSAParameters publicKey = csp.ExportParameters(false);
    Console.WriteLine(SerializeRsaKey(publicKey));
}

Output:
<?xml version="1.0" encoding="utf-16"?>
<RSAParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Exponent>AQAB</Exponent>

<Modulus>uNXPosW924chCgOP1TJmEyaE0XcbpQWKLtCoVGnXD55OA998QjOMcW89olvTf+t2dWVxWM3C+e2bnneDE58YhFZ8+esSBsjHCa9BdM1dF0E/egmksOerMtkri/xgq7COhJjvKM6YRIwP2pUMhF8ovVwq6gnN7z2YTU3p5PAcLXMSCJwiPrZzRiI94nQX/7JbAYRksDoHnxABmg8Af2iySJ87aMwT0TgCBGO1+WoMM0eYTcfqcOsjqPP3tuuLaYRfngCk7qPw0eG4UVCfDAhfUzjzsWwAVqxgvS7YgU7XMfcAs3mLYx8bnT4Ax1BzmbqTfDnCpE7egm4Oc9xb1YBnbQ</Modulus>
</RSAParameters>

Default Serialization and Deserialization in each Language

Java

final byte[] publicKeySaved = publicKey.getEncoded();

...

final RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeySaved));

Python

publicKeySaved = publicKey.exportKey('PEM')

...

publicKey = RSA.import_key(publicKeySaved)

C#

private static string SerializeRsaKey(RSAParameters key)
{
    using (StringWriter sw = new StringWriter())
    {
        XmlSerializer xs = new XmlSerializer(typeof(RSAParameters));
        xs.Serialize(sw, key);
        return sw.ToString();
    }
}

...

private static RSAParameters DeserializeRsaKey(string key)
{
    using (StringReader sr = new StringReader(key))
    {
        XmlSerializer xs = new XmlSerializer(typeof(RSAParameters));
        return (RSAParameters)xs.Deserialize(sr);
    }
}

Differences and similarities

As you can see Java and .Net do not have any readily available API to export to a standard format like PEM or DER. You need to either use 3rd party libraries or make your own implementation to convert the Public Key into standard format.
The most simple way to capture sufficient information of the RSA public key is to capture its Exponent and Modulus. In Java, when you print publicKey.toString() you get Exponent & Modulus value in hexadecimal, whereas in C#.net these values are serialized to Base64. In Python you get their base10 integer values.

Creating a format to share the data cross platform

All these data are the same at the binary level. Their corresponding string representation is different in different languages. To proceed further, I will use the following JSON format to serialize and deserialize the RSA Public key: {"Exponent":"<exponent-value-in-base-64>", "Modulus":"<modulus-value-in-base-64>"}

Bridging the Gap:

To bring them to the same representation, I choose to convert the binary representation to Base64 and this method results in least size string representation.

Serializing only the Exponent and Modulus values to base64 string holds sufficient information to reconstruct the Public Key in the same or different environment.

Cross Platform Serialization

Java

JSONObject jsonObject = new JSONObject();
jsonObject.put("Exponent",Base64.getEncoder().encodeToString(trim(publicKey.getPublicExponent().toByteArray())));
jsonObject.put("Modulus",Base64.getEncoder().encodeToString(trim(publicKey.getModulus().toByteArray())));
System.out.println("JSON: " + jsonObject);

Noticed the trim function? Read more about it in my blog dedicated to it.

Python

jsonObject = {"Exponent": base64.b64encode(publicKey.e.to_bytes(3, 'big')).decode('ascii') , "Modulus":  base64.b64encode(publicKey.n.to_bytes(256, 'big')).decode('ascii') }
jsonString = json.dumps(jsonObject)
print(jsonString)

C#

var jsonObject = new { Exponent = publicKey.Exponent, Modulus =  publicKey.Modulus};
string jsonString = JsonConvert.SerializeObject(jsonObject);
Console.WriteLine(jsonString);

Cross Platform Deserialization

Java

final String jsonStringPublicKey = "{\"Exponent\":\"AQAB\",\"Modulus\":\"vOyZjMGZUfYYuh4mlyQySnJxjA+UjMPcst3gsVbUP3CJNRzoPbD6GN+iHhtQANwLNa8mgR1809t4wusrdbpE2VBebByR1UAF2PUqGvPyjkUnyYFBmtuj1yT31xTB+b0UCuHxZ0+Zgro5oyt2HQvex7rXD6hKSFEtwmVa5nIAV4EePVSO6RxychKDF85wnvctjPFPeiCYsgxsgIj4ssmJR0ICYjI5psFISWhOvii6xqSwrJOjIR8b+ai+56BZ9hlFBgoN5INdzk9AEgIRWhd6IG483gwosPk0SIF2SSOse6FbKkMoqFhVwBhJGunqx7Lz9tiKPNYnycH88gRmnwtSxQ==\"}";
final JSONObject jsonPublicKey = new JSONObject(jsonStringPublicKey);
final byte[] exponent = decoder.decode(jsonPublicKey.getString("Exponent"));
final byte[] modulus = decoder.decode(jsonPublicKey.getString("Modulus"));
final RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
final KeyFactory factory = KeyFactory.getInstance("RSA");
final RSAPublicKey publicKey = (RSAPublicKey) factory.generatePublic(spec);
System.out.println("RestoredPublicKey= " + publicKey);

Python

jsonStringPublicKey = '{"Exponent":"AQAB","Modulus":"vOyZjMGZUfYYuh4mlyQySnJxjA+UjMPcst3gsVbUP3CJNRzoPbD6GN+iHhtQANwLNa8mgR1809t4wusrdbpE2VBebByR1UAF2PUqGvPyjkUnyYFBmtuj1yT31xTB+b0UCuHxZ0+Zgro5oyt2HQvex7rXD6hKSFEtwmVa5nIAV4EePVSO6RxychKDF85wnvctjPFPeiCYsgxsgIj4ssmJR0ICYjI5psFISWhOvii6xqSwrJOjIR8b+ai+56BZ9hlFBgoN5INdzk9AEgIRWhd6IG483gwosPk0SIF2SSOse6FbKkMoqFhVwBhJGunqx7Lz9tiKPNYnycH88gRmnwtSxQ=="}'
jsonPublicKey = json.loads(jsonStringPublicKey)
exponent = int.from_bytes(base64.b64decode(jsonPublicKey["Exponent"].encode('ascii')), "big")
modulus = int.from_bytes(base64.b64decode(jsonPublicKey["Modulus"].encode('ascii')), "big")
factory = RSA.construct((modulus, exponent))
publicKey = factory.publickey()
print(publicKey.exportKey('PEM'))

C#

const string jsonStringPublicKey = "{\"Exponent\":\"AQAB\",\"Modulus\":\"vOyZjMGZUfYYuh4mlyQySnJxjA+UjMPcst3gsVbUP3CJNRzoPbD6GN+iHhtQANwLNa8mgR1809t4wusrdbpE2VBebByR1UAF2PUqGvPyjkUnyYFBmtuj1yT31xTB+b0UCuHxZ0+Zgro5oyt2HQvex7rXD6hKSFEtwmVa5nIAV4EePVSO6RxychKDF85wnvctjPFPeiCYsgxsgIj4ssmJR0ICYjI5psFISWhOvii6xqSwrJOjIR8b+ai+56BZ9hlFBgoN5INdzk9AEgIRWhd6IG483gwosPk0SIF2SSOse6FbKkMoqFhVwBhJGunqx7Lz9tiKPNYnycH88gRmnwtSxQ==\"}";
dynamic jsonPublicKey = JsonConvert.DeserializeObject<dynamic>(jsonStringPublicKey);
string exponent = jsonPublicKey.Exponent;
string modulus = jsonPublicKey.Modulus;
RSAParameters publicKey = new RSAParameters
{
    Modulus = Convert.FromBase64String(modulus),
    Exponent = Convert.FromBase64String(exponent)
};
Console.WriteLine(GetKeyString(publicKey));
Stay tuned for my next blog where I demonstrate cross-platform asymmetric encryption and decryption
  1. Pingback:
  2. January 26, 2023

    Thank You

    Reply

Leave A Comment

Your email address will not be published. Required fields are marked *