Porting Java Public Key Hash to C# .NET






4.62/5 (40 votes)
Apr 7, 2004
7 min read

262327

4771
This tutorial is an effort to overcome problems faced by the developers who want to sign data using Java Key Store and want to verify it on .NET platform. The tutorial demonstrates how to export the Public Key using Java to .NET compatible Public Key (XML format).
Table of Contents
- Abstract
- Section 1: Introduction
- Section 2: Signature Generation in Java
- Section 3: Generating .NET compatible Public Key
- Section 4: Reading Public Key and verifying the Data in .NET
- References
Abstract
There is perhaps no software engineering topic of more timely importance than data security and integrity. Attacks are costly, whether the attack comes from inside or out, and some attacks can expose a software company to liability for damages. Staying on top of the most up-to-date techniques and tools is one key to application security and integrity. Developing cross platform applications to ensure security is a big problem which has been encountered by a number of developers.
This tutorial is an effort to overcome such problems faced by the developers who want to sign the data on one platform and want to verify it on another, using popular public-key digital signature known as RSA.
Starting with a Java program to illustrate how to generate the keys, sign and verify the data using the generated keys, both by breaking down the functions of the application and by viewing the final execution result. Next, the tutorial discusses how to export the public key, generated by Java, to be used later by .NET framework to verify the data in .NET applications. Finally, this tutorial discusses how to read the Public Key generated by Java program in .NET (using C#) and verify the data using this Public Key. In the end of the tutorial, different definitions of terms are also provided to equip the beginners as well.
Section 1: Introduction
Who should read?
The main ideas behind this tutorial revolve around signing a data in Java and then verifying it in .NET using C#. However, readers who are interested to start learning the signing & verifying of data using Java Cryptographic APIs and language can also use this tutorial. This tutorial assumes that reader knows how to read and write basic Java & C# .NET applications.
Before going through this tutorial, it is highly recommended that you should have basic knowledge of terms for Cryptography and Security packages defined in Java and .NET framework. For interested readers, the definitions of terms is provided in the Glossary at the end of the tutorial. Even if you know the terms, just go through them to re-memorize them.
Section 2: Signature generation in Java
The zip file includes the SecurityManager.java. This Java class is responsible for signing the data and verifying the data using private and public keys respectively. It also generates the XML Public key for .NET framework. This class has the following responsibilities.
- Initialize Signature Parameters
- Generate Public and Private Keys
- Store/Read generated Public and Private keys
- Sign the input data
- Verify the input data
- Store the Public key in .NET compatible (XML) format
Although it is not fair to keep all the things in one class; but just to keep it simple, everything has been kept in one class. Reader may alter the code and may eliminate functions such as storing/reading the keys on/from HDD to some other class.
The code has been well documented with comments, and hopefully, the reader will not find it difficult to understand.
Public and Private Key Generation
Private Key is mandatory for digital signature generation. When you generate the Private Key, the corresponding Public key is automatically generated. For performance, Private / Public Keys are generated only once and stored at physical location of the HDD. The methodology adopted in the current code is to store the public key at some application path. However, for web applications, key may be streamed along with data. See figure.
The following function generates the Public and Private Keys and stores them at the specified location. This function also invokes functions to generate the compatible .NET Public Key. It takes the size of the hash algorithm as a parameter and generates the corresponding key.
Now, let's look at the corresponding code:
/**
* Generates the keys for given size.
* @param size - Key Size [512|1024]
*/
public void generateKeys(int size) {
try {
System.out.println("Generating Keys");
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(size);
keypair = keyGen.genKeyPair();
//Get the KeyPair for given algorithm and size.
privateKey = keypair.getPrivate(); //Extract Private Key
publicKey = keypair.getPublic(); // Extract Public Key
// Get bytes of the public and private keys
byte[] privateKeyBytes = privateKey.getEncoded(); //Get the key bytes
byte[] publicKeyBytes = publicKey.getEncoded(); // Get the key bytes
//write bytes to corresponding files after encoding
//them to Base 64 using Base64Encoder Class.
writeKeyBytesToFile(new
BASE64Encoder().encode(privateKeyBytes).getBytes(),
PRIVATE_KEY_FILE);
String encodedValue = new BASE64Encoder().encode(publicKeyBytes);
writeKeyBytesToFile(encodedValue.getBytes(), PUBLIC_KEY_FILE);
//Generate the Private Key, Public Key and Public Key in XML format.
PrivateKey privateKey =
KeyFactory.getInstance("RSA").generatePrivate(new
PKCS8EncodedKeySpec(privateKeyBytes));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new
X509EncodedKeySpec(publicKeyBytes));
//Create and Instance of RSAPublicKey class with X509 encoding
RSAPublicKey rsaPublicKey =
(RSAPublicKey)
KeyFactory.getInstance("RSA").generatePublic(new
X509EncodedKeySpec(publicKeyBytes));
//Get the RSAPublicKey as XML store the public key in XML string
//to make compatible .Net public key
//file. See getRSAPublicKeyAsXMLString() for details
String xml = getRSAPublicKeyAsXMLString(rsaPublicKey);
//Store the XML (Generated .Net public key file) in file
writeKeyBytesToFile(xml.getBytes(), DOT_NET_PUBLIC_KEY_FILE);
}
catch (java.security.NoSuchAlgorithmException e) {
System.out.println(
"No such algorithm. Please check the JDK version."+e.getCause());
}
catch (java.security.spec.InvalidKeySpecException ik) {
System.out.println(
"Invalid Key Specs. Not valid Key files."+ ik.getCause());
}
catch (UnsupportedEncodingException ex) {
System.out.println(ex);
}
catch (ParserConfigurationException ex) {
System.out.println(ex);
}
catch (TransformerException ex) {
System.out.println(ex);
}
catch (IOException ioe) {
System.out.println("Files not found on specified path. "+
ioe.getCause());
}
}
Once the keys are generated, they can be stored at specific path in a simple file to avoid re-generation of Private and Public keys. It increases the performance. There may be a requirement to generate new keys each time for each request. If you want to generate the keys each time then invoke this function each time in main function or in your application before signing the data.
Public and Private Key initialization from stored files
Following two functions read the public and private keys. Whenever you have to sign the data, you only require a private key. Therefore, I have provided an additional function to initialize only the private key. The basic work flow of this function is described in the figure.
Let's look at the code now. As by name, it is clear the
initializePrivateKey
only initializes the private key, but if
you wish to initialize both private and public keys, invoke the other
function named as initializeKeys
.
/**
* Initialize only the private key.
*/
private void initializePrivateKey() {
try {
//Read key files back and decode them from BASE64
BASE64Decoder decoder = new BASE64Decoder();
byte[] privateKeyBytes = decoder.decodeBuffer(new
String(readPrivateKeyFile()));
// Convert back to public and private key objects
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new
PKCS8EncodedKeySpec(privateKeyBytes);
privateKey = keyFactory.generatePrivate(privateKeySpec);
}
catch (IOException io) {
System.out.println("Private Key File Not found."+
io.getCause());
}
catch (InvalidKeySpecException e) {
System.out.println("Invalid Key Specs. Not valid Key files."
+ e.getCause());
}
catch (NoSuchAlgorithmException e) {
System.out.println("There is no such algorithm." +
" Please check the JDK ver."+ e.getCause());
}
}
/**
* Initializes the public and private keys.
*/
private void initializeKeys() {
try {
//Read key files back and decode them from BASE64
BASE64Decoder decoder = new BASE64Decoder();
byte[] privateKeyBytes = decoder.decodeBuffer(new
String(readPrivateKeyFile()));
byte[] publicKeyBytes = decoder.decodeBuffer(new
String(readPublicKeyFile()));
// Convert back to public and private key objects
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec =
new PKCS8EncodedKeySpec(privateKeyBytes);
privateKey = keyFactory.generatePrivate(privateKeySpec);
EncodedKeySpec publicKeySpec =
new X509EncodedKeySpec(publicKeyBytes);
publicKey = keyFactory.generatePublic(publicKeySpec);
}
catch (IOException io) {
System.out.println("Public/ Private Key File Not found."
+ io.getCause());
}
catch (InvalidKeySpecException e) {
System.out.println("Invalid Key Specs. Not valid Key files."
+ e.getCause());
}
catch (NoSuchAlgorithmException e) {
System.out.println("There is no such algorithm." +
" Please check the JDK ver."+ e.getCause());
}
}
Signing the Data
As it is evident from the function name, that function is responsible for signing the data sent as bytes. Prior to this function invocation, the private key initialization is must. Either read the existing private key or generate a new private key. See figure.
/**
* Signs the data and return the signature for a given data.
* @param toBeSigned Data to be signed
* @return byte[] Signature
*/
public byte[] signData(byte[] toBeSigned) {
if (privateKey == null) {
initializePrivateKey();
}
try {
Signature rsa = Signature.getInstance("SHA1withRSA");
rsa.initSign(privateKey); //initialize the signature with provided key
rsa.update(toBeSigned); //sign the data
return rsa.sign(); //return the signature in bytes
}
catch (NoSuchAlgorithmException ex) {
System.out.println(ex);
}
catch (InvalidKeyException in) {
System.out.println("Invalid Key file." +
"Please chekc the key file path"+ in.getCause());
}
catch (SignatureException se) {
System.out.println(se);
}
return null;
}
Verify data and Signature
Verifying the data is fairly simple. The following function is responsible for verifying the signature for corresponding data, sent as bytes. The signature is verified with the Public Key. See figure.
/**
* Verifies the signature for the given bytes using the public key.
* @param signature Signature
* @param data Data that was signed
* @return boolean True if valid signature else false
*/
public boolean verifySignature(byte[] signature, byte[] data) {
try {
// Initialize the keys before verifying the signature
initializeKeys();
//Initialize the signature engine instance with public key
sign.initVerify(publicKey);
// load the data to be verified
sign.update(data);
// verify the data loaded in previous step
// with given signature and return the
return sign.verify(signature);
//result
}
catch (SignatureException e) {
e.printStackTrace();
}
catch (InvalidKeyException e) {
}
return false;
}
Section 3: Generating .NET compatible Public Key
.NET RSA Public Key XML
.NET RSA Public Key contains Modulus and Exponent which can be extracted
from the Java Public key. Before we look into the Java code details, let's
look at the XML which can be transformed into the RSAParameters structure.
The Modulus
represents the Modulus parameter for the RSA
algorithm while Exponent
represents the Exponent parameter for
the RSA Algorithm. The RSA algorithm class in .NET is available under the
namespace System.Security.Cryptography.RSA
.
The .NET RSA Public XML key structure is given below.
<?xml version="1.0" encoding="UTF-8"?>
<RSAKeyValue>
<Modulus>uKI+0wG6eILUPRNf6ImqRdez/nLxsV0LHGsuvYR0LDVrXz8Y7sYSlpAkn1HpJI8US8Sx5bJzvBib
vKv0pAa7UQ==
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
Generating .NET XML Public Key from Java
In this section, we will discuss the methodology adopted to export the Java public key class as .NET compatible public key. The exported XML key will be later loaded in our .NET application and we will verify the signature by using this key.
Let's look at the code.
Recall from our Key Generation process that we invoked some functions to generate the Public key in XML format.
//Create and
Instance of RSAPublicKey class with X509 encoding key specs
RSAPublicKey rsaPublicKey = (RSAPublicKey)
KeyFactory.getInstance("RSA").
generatePublic(new X509EncodedKeySpec(publicKeyBytes));
//Get the RSAPublicKey as XML store the public key
//in XML string to make compatible .Net public key
//file. See getRSAPublicKeyAsXMLString() for details
String xml = getRSAPublicKeyAsXMLString(rsaPublicKey);
//Store the XML (Generated .Net public key file) in file
writeKeyBytesToFile(xml.getBytes(), DOT_NET_PUBLIC_KEY_FILE);
If you see the first line, X509EncodedKeySpec
has been used.
This class represents the ASN.1 encoding of a public key, encoded according
to the ASN.1 type SubjectPublicKeyInfo
. The
SubjectPublicKeyInfo
syntax is defined in the X.509 standard as
follows:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Now, look at the function named as
getRSAPublicKeyAsXMLString
:
/**
* Gets the RSA Public key as XML string.
* @param key RSAPublicKey
* @return String XML representation of RSA Public Key.
* @throws UnsupportedEncodingException
* @throws ParserConfigurationException
* @throws TransformerException
*/
private String getRSAPublicKeyAsXMLString(RSAPublicKey key) throws
UnsupportedEncodingException,
ParserConfigurationException,
TransformerException {
Document xml = getRSAPublicKeyAsXML(key);
Transformer transformer =
TransformerFactory.newInstance().newTransformer();
StringWriter sw = new StringWriter();
transformer.transform(new DOMSource(xml), new StreamResult(sw));
return sw.getBuffer().toString();
}
/**
* Gets the RSA Public Key as XML. The idea is to make the key readable
for
* .Net platform. The generated key is compatible with .Net key structure.
* @param key RSAPublicKey
* @return Document XML document.
* @throws ParserConfigurationException
* @throws UnsupportedEncodingException
*/
private Document getRSAPublicKeyAsXML(RSAPublicKey key) throws
ParserConfigurationException,
UnsupportedEncodingException {
Document result =
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element rsaKeyValue = result.createElement("RSAKeyValue");
result.appendChild(rsaKeyValue);
Element modulus = result.createElement("Modulus");
rsaKeyValue.appendChild(modulus);
byte[] modulusBytes = key.getModulus().toByteArray();
modulusBytes = stripLeadingZeros(modulusBytes);
// KeyManager.write("c:\\mod.c",
// new sun.misc.BASE64Encoder().encode(modulusBytes));
// //Stored it for testing purposes
modulus.appendChild(result.createTextNode(
new String(new sun.misc.BASE64Encoder().encode(modulusBytes))));
Element exponent = result.createElement("Exponent");
rsaKeyValue.appendChild(exponent);
byte[] exponentBytes = key.getPublicExponent().toByteArray();
// KeyManager.write("C:\\exponenet.c",
// new sun.misc.BASE64Encoder().encode(exponentBytes));
// //stored it for testing purposes
exponent.appendChild(result.createTextNode(
new String(new sun.misc.BASE64Encoder().encode(exponentBytes))));
return result;
}
The above mentioned functions allow us to generate the XML file which contains the Public Key Information. We store this file on the hard disk or stream it over the network.
Section 4: Reading Public Key and verifying the Data in .NET
In this section, we will read the Public key which was generated in the previous section by the Java program.
Initialization
In the constructor, initialize RSAParameters
class and
RSACryptoServiceProvider
.
PUBLIC_KEY="c:\\netpublic.key"; //Generated by Java Program
RSAKeyInfo = new RSAParameters();
RSA = new RSACryptoServiceProvider();
Reading Public Key in .NET
Following function reads the file from the specific path on HDD. One may
use network stream to read the XML file. Parse the modulus data and exponent
data and assign them to variables. Convert the strings from Base64 and
assign them to RSAParameters.Modulus
and
RSAParameters.Exponent
. ImportParameters
function
of RSACryptoServiceProvider
is used to load the parameters for
RSA algorithm.
private void readKey()
{
// read the XML formated public key
try
{
XmlTextReader reader = new XmlTextReader(PUBLIC_KEY);
while(reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if(reader.Name=="Modulus")
{
reader.Read();
modStr= reader.Value;
}
else if(reader.Name=="Exponent")
{
reader.Read();
expStr= reader.Value;
}
}
}
if(modStr.Equals("") ||expStr.Equals(""))
{
//throw exception
throw new Exception("Invalid public key");
}
RSAKeyInfo.Modulus = Convert.FromBase64String(modStr);
RSAKeyInfo.Exponent = Convert.FromBase64String(expStr);
RSA.ImportParameters(RSAKeyInfo);
}
catch(Exception e)
{
throw new Exception("Invalid Public Key.");
}
}
Verifying the Signature in .NET
The signature can be verified once the Modulus and Exponent have been
parsed from the XML and loaded in RSACryptoServiceProvider
.
verifySignature
method is invoked to verify the signature for
given data. It verifies the specified signature data by comparing it to the
signature computed for the specified data. See figure.
public bool verifySignature(byte[]
signature , string signedData)
{
byte[] hash = Convert.FromBase64String(signedData);
try
{
if(RSA.VerifyData(hash,"SHA1",signature))
{
//Console.WriteLine("The signature is valid.");
return true;
}
else
{
//Console.WriteLine("The signature is not valid.");
return false;
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
return false;
}
}
References
Acknowledgements
Special thanks to:
- Dr. Iman Gholampur
- Philip Ross
- Mitch Gallant