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));
Thank You