|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Table of Contents
AbstractThere 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: IntroductionWho 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 JavaThe 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.
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 GenerationPrivate 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 filesFollowing 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
/**
* 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 DataAs 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 SignatureVerifying 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 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 JavaIn 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, SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Now, look at the function named as
/**
* 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 .NETIn this section, we will read the Public key which was generated in the previous section by the Java program. InitializationIn the constructor, initialize PUBLIC_KEY="c:\\netpublic.key"; //Generated by Java Program
RSAKeyInfo = new RSAParameters();
RSA = new RSACryptoServiceProvider();
Reading Public Key in .NETFollowing 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 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 .NETThe signature can be verified once the Modulus and Exponent have been
parsed from the XML and loaded in 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;
}
}
ReferencesAcknowledgementsSpecial thanks to:
| ||||||||||||||||||||||||||||