Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Porting Java Public Key Hash to C# .NET

0.00/5 (No votes)
26 Oct 2004 1  
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

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here