Overview
Security is a main concern in any application development especially where there is an exchange of critical information with an external system. There are ways to securely send and receive data such as HTTPS and Public Key Cryptography. In todays world, XML has become a standard means for data communication between applications. Recent specifications from W3C on XML Signature addresses the issue of securely sending and receiving data that complies with most of the algorithms, PGP, RSA DSA etc. In this article we will explore XML Signature specification from W3C and its implementation in .NET using C#.
Description
In any application when there is a need to access data from an external application, security plays an important role. Applications should take all possible measures to ensure that the information is not tampered with during its transmission. A couple of solutions that come to any developers mind are to use HTTPS or public key cryptography. HTTPS adds performance constraints and public key cryptography requires both sender and receiver to agree on a specific algorithm or tool. Furthermore the entire document is signed or encrypted in a HTTPS or Public key scenario. There is no option to partially sign a document. W3C has released a new set of standards, XML Signature, to digitally sign an XML Document. The biggest advantage is that application written in any language (Java based or Microsoft based) can understand the SignedXml and can validate the signature. Microsoft.NET Framework exposes a namespace specifically to address XML signature.
Differences between XML Signature and Public Key Cryptography: Before we try to understand about XML signature let us take a look at the differences between digital signature and public key encryption.
Public Key Encryption |
Digital Signature |
Complete Data is encrypted |
Only the Message Digest is Encrypted |
Public Key is used to encrypt the data and private key is used to decrypt the data. |
Private key is used to encrypt the MessageDigest and Public key is used to decrypt to get the Digest Value and then recalculate the MessageDigest to check if the data was tampered. |
The above table gives basic information on Encryption and Digital Signature. Given a situation where data encryption is not required but its authentication is, digital signature is the preferred way of implementation. In the next section we will explore a scenario where XML Signature can be used and how Microsoft .NET provides functionality to sign an XML Document.
System Requirements
Windows 2000 Professional/ Server.Microsoft .NET Framework SDK (Release candidate).
Business Scenario
Consider any business scenario like online shopping; where customers enter details about the products they are interested in, shipping address and also the Credit Card Information. Lets say all these details are stored across pages and are put into one XML Document. There are so many things that could happen in the background like placing an order to the concerned products department, informing the shipping department to ship the order and asking the payment department to charge the credit card. These can happen in stages and the information is transmitted between applications. There are a couple of things that must be taken into account. Firstly, Whether or not the data need to be encrypted. Secondly does it require only a part of the document to be secure? In a scenario where there is no need to encrypt the data but only a part of the document needs to be validated for any change in its content, there XML Signature can be used to Sign a part of or the complete document.
Now that we have identified the scenario to use XML Signature let us discuss about a scenario where it will reduce the development efforts. Consider a situation where the information from one company can go to multiple companies via FTP. Encryption using Public key Cryptography is the ideal solution. However, if the data is not critical but still needs to be authenticated to verify validity then digital signature is preferred. Using any tools like PGP, RSA to achieve this might result in increased development effort if any new business partner requires a different algorithm or tool. How about using the same algorithm like PGP, RSA but with a standard like XML Signature to achieve the same. XML signature supports multiple algorithms like RSA DSA, PGP etc and the standards are defined by W3C. Any application that can understand the XML signed as per W3C recommendations would be able to decipher the XML signed using Microsoft .NET.
Before we get into the details of implementations in .NET we will first take a look at W3C Specification on XML Signature and then we will discuss how to implement it using C#.
W3C Specification
Let us take a brief look at W3C Specification on XML Signature and then we will explore how .NET helps in reducing the development time and adding flexibility in developing a Secured Application.
<Signature ID?>
<SignedInfo>
<CanonicalizationMethod/>
<SignatureMethod/>
(<Reference URI? >
(<Transforms>)?
<DigestMethod>
<DigestValue>
</Reference>)+
</SignedInfo>
<SignatureValue>
(<KeyInfo>)?
(<Object ID?>)*
</Signature>
This standard specification defines a common place in a SignedXML to look for SigningKey, Signature value, Digestvalue and Data. We will take a closer look at this XML Specification and how to sign an XML Document using C#.
Implementation in Microsoft .NET
SignedXML is the wrapper around W3C Specification to compute and verify the signature. In order to sign an XML Document, the required elements are Data to be signed and Key used to sign the data. Microsoft .NET provides classes and methods to perform these operations. SignedXML has methods to hold the XML data to be signed, transformation to be applied, SigningKey etc. Before we begin to explore the details of SignedXML let us take a look at various classes in Cryptography.XML namespace.
DataObject
DataObject is a placeholder to hold the Data to be Signed. DataObject in .NET refers to <Object> xml tag in W3C Specification. DataObject has two properties: Data and ID.
Data: To get/set the Data to be signedID: This can be used to reference data in a different location.
The following code will create a DataObject and set the Data as CreditCardInfo.xml.
XmlDocument objdocument = new XmlDocument();
objdocument.Load("CreditCardInfo.xml");
DataObject dataObject = new DataObject();
dataObject.Data = objdocument.ChildNodes;
dataObject.Id = "CreditCardInfo";
Alternatively you can refer to a file in the internet/intranet using the Property ID of DataObject.
Reference
Reference Object in .NET Framework represents <Reference> xml tag in W3C Specification. There can be more than one Reference tag in a Signed XML document. Reference gives the flexibility to add multiple Data to be signed and apply different transformations for each DataObject. Digest values are specific to a Reference URI. Take a closer look at Reference tag in W3C Specification.
(<Reference URI? >
(<Transforms>)?
<DigestMethod>
<DigestValue>
</Reference>)
The following code will create a Reference Object and set a reference to the DataObject that we previously created.
Reference reference = new Reference();
reference.Uri = "#CreditCardInfo ";
Reference Class in .NET Framework gives the flexibility to add Transformations. Transformations are applied to the XML Document (DataObject) before the Digest is calculated.
AddTransform: Adds the Transformation Object to the Reference object. The transformation object could be anything like XPath, XSLT, etc.
DSAKey
In order to sign an XML Document KeyPair should be generated.
The following line of code is used to create a key file (using DSA Algorithm), which is then used to sign the XML Document.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;
public class CreateDSAKeyPair
{
static void Main(String[] args)
{
// Create example data to sign.
DSA objDSAkey = DSA.Create();
string strDSAKeyXML = objDSAkey.ToXmlString(true);
XmlDocument objXMLdocument = new XmlDocument();
objXMLdocument.LoadXml(strDSAKeyXML);
objXMLdocument.Save("XMLSignatureDSAKeyPair.xml");
// Save only the PublicKey.
strDSAKeyXML = objDSAkey.ToXmlString(false);
objXMLdocument = new XmlDocument();
objXMLdocument.LoadXml(strDSAKeyXML);
objXMLdocument.Save("XMLSignatureDSAPublicKey.xml");
}
}
Note: After the KeyPair is generated you have an option to save only the PublicKey or with PrivateKey also.
In the above example the key is stored in two different files. One file has PrivateKey information along with PublicKey and the other file has only PublicKey information.
The following line of code is used to extract both Public and PrivateKey information.
string strDSAKeyXML = objDSAkey.ToXmlString(true);
The following line of code is used to extract only the PublicKey information.
strDSAKeyXML = objDSAkey.ToXmlString(false);
SignedXML
The important element required to sign an XML Document is the key. Keys can be generated using classes of DSA, RSA etc provided by .NET Framework. Now that we have defined the necessary information like DataObject, Reference and Key required to sign an XML Document, we will take a look at SignedXML Class in .NET.
SignedXML class in .NET Framework combines these bits and pieces of information required for signing an XML Document and generating a SignatureValue. SignedXML exposes the following methods and property.
SigningKey |
To set the Key used to Sign the XML Document. |
AddObject |
To add the DataObject (Data to be signed) to the SignedXML Object. |
AddReference |
To add Reference to the DataObject along with any transformation Information. |
ComputeSignature |
This calculates the XML Signature with the information provided.(SigningKey, DataObject, Reference) |
We will now define the process of using the generated key to sign the XML Document using Signed XML. The steps involved in using SignedXML to sign an XML Document are as follows.Set the Signing Key
Add the DataObject to SignedXML
Add the Reference Object to Signed XML
ComputeSignature
SignedXML has a property called SigningKey to set the signingkey.Set the SigningKey using KeyFile already generated.
The following line of code will load the generated key file and set the SigningKey of SignedXML.
XmlDocument objDSAKeyPairXMLdocument = new XmlDocument();
objDSAKeyPairXMLdocument.Load("XMLSignatureDSAKeyPair.xml");
string strDSAKeyPairXML = objDSAKeyPairXMLdocument.InnerXml;
Console.WriteLine (strDSAKeyPairXML );
SignedXml signedXml = new SignedXml();
DSA objDSAkeyPair = DSA.Create();
objDSAkeyPair.FromXmlString(strDSAKeyPairXML );
signedXml.SigningKey = objDSAkeyPair ;
Now that we have set the Key to sign the XML document, we will explore how to add a DataObject to the signed XML. The following line of code will add the DataObject to the SignedXML.
signedXml.AddObject(dataObject);
The following line of code will add the Reference object to the SignedXML.
signedXml.AddReference(reference);
Finally Generate/ Compute the signature information.
signedXml.ComputeSignature();
The following will generate the XML representation of SignedXML
XmlElement xmlSignature = signedXml.GetXml(); Console.WriteLine(xmlSignature.OuterXml);
The following is generated SignedXML
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" />
<Reference URI="#CreditCardInfo">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>kABZnLK9RI0Zyyi/ASSa0HBnajM=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>sITbBgmlV6ajJo4qIx40HtXwa2jOAlmkhCeQF31mglIudvrTKy1sCQ==</SignatureValue>
<Object Id="CreditCardInfo">
<CreditCardInfoxmlns=""><CC>
<CCNumber>12345678</CCNumber><Amount>100</Amount><FirstName>Sitaraman</FirstName>
<LastName>Lakshminarayanan </LastName>
<Address1> 100 Robinson Avenue</Address1>
<City> Philadelphia </City>
<Zip>12345</Zip>
</CC></CreditCardInfo>
</Object>
</Signature>
Now we look at the above XML (Digitally Signed) and compare it with the W3C Specification,Signature - represents the entire signed XML. SignedInfo - represents the set of reference.
Reference-Each reference represents a URI to identify the DataObject, represents transforms to apply any transformation. (In our case we didnt apply any Transformation) and represents DigestMethod an algorithm used to calculate the DigestValue.
Verifying Signature Now that the XML Document is signed, the next step in the process is to verify the signature. In order to check the validity of the signed XML Document, the KeyValue should be known, that is, PublicKey of the SignedXML Document should be known. KeyInfo is not sent as a part of the original document. It is optional and left to the application developer to decide whether to send the KeyInfo or not. For Security reasons it is highly recommended to sign XML Document with PrivateKey and the PublicKey is given to the business partner separately. The business partner will then use the public key to validate the signed XML.
KeyInfo
KeyInfo is another class in System.Cryptography.Xml namespace. This gives the flexibility to add any KeyValues such as RSA, DSA, PGP, etc. KeyInfo in .NET Framework represents the KeyInfo XML tag in W3C specification. KeyInfo allows the application developer to add any KeyValue (DSA, RSA, PGP, etc) using the AddClause method.
Lets take approach one to send the KeyInfo along with the signed XML Document.
Add the following code before ComputeSignature.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new DSAKeyValue(objDSAkeyPair));
signedXml.KeyInfo = keyInfo;
Here the KeyValue is sent along with the SignedXML.
<Signature xmlns="http://www.w3.org/2000/09/xmldsig>#"
<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315>" /
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1 http://www.w3.org/2000/09/xmldsig>" /
<Reference URI="#CreditCardInfo">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1 http://www.w3.org/2000/09/xmldsig>" /
<DigestValue>YThiNybfinJgzNdeBfVfz7Jh0ME=</DigestValue>
</Reference></SignedInfo>
<SignatureValue>P+Gnw+pJxEkYwzHCZIQS3RAI12Aiu7EEBcwOlu7YwHH5q2JjclTIsA==</SignatureValue>
<KeyInfo>
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig>#"
<DSAKeyValue>
<P>prie04ZftW9Jtt1eeAxCvk+0B96Tw3uPKhM2mmWZ7BcyXzjtMmpG5hH0oyRNd9g0hTInryHLnPlE2NyQHWNAPZRy
EL9RaH9U0wgVy9XSxY+ui/AeHSp9OyUAn9N9rpMEzNnN7ckW+SPB2IflI0VDc4EnMrCv45Rvxp8iAOn5L4E=</P>
<Q>31zFNW6wagbbBalx+q2dd9glSSE=</Q><G>BHO7TrqbuPAQmuiMlLNLFOQ0hD0NIhxkdu2zDeOjTnHWa4hgM/XDhH
SrinznRmp4AmWmnVdYbNovaN1h
/mpO4JFBIOhHjU7xXKNy/uhwJu9qfaLkmH5maAzWBC6gFmlms9gamoAhMr61sNcoAQ55yGnrPv3BOw0ikoFramlRRMQ=</G>
<Y>cNdQOAuPl02sBfs7coCSNNGod+gbArBiF+gq0QtYPR8iHL9EVi6tTr5+xI7wuBBcrRuRUeyj7kVmt7T2j9qGOyjekl5Cnke5u
1prdYw8vPPVqQ9v/rssDCD/I5igu
3gE8oxzJmNy3juuIkGJUkTCWexhtWqfWW1DkrlHJ+v3Qdk=</Y>
<J>vxUYKQOB4saiJZfRxRvbuuXvPmX/gNYth+yVhneMZho2cMqNEhxzkTTE5xdOqesNYCo1V0ucEss7dpv7hrhLUjUoG3WQc
GEBxAsBC12rxEflm3DOjtVzliPYZRWGeTHOeYUYTGvG+3r0Jb+A</J>
<Seed>tkzHHTdA34yGU08X38PZ4W0z6aQ=</Seed><PgenCounter>Am0=</PgenCounter>
</DSAKeyValue>
</KeyValue>
</KeyInfo>
<Object Id="CreditCardInfo">
<CreditCardInfo xmlns="">
<CCNumber>12345678</CCNumber><Amount>100</Amount>
<FirstName>Sitaraman </FirstName>
<LastName>Lakshminarayanan </LastName>
<Address1> 100 Robinson Avenue </Address1>
<City> Philadelphia </City>
<Zip>12345</Zip>
</CreditCardInfo>
</Object>
</Signature>
Let us take a second approach to verify the signature with PublicKey.(When a Document is Digitally signed, the sender uses KeyPair to sign the document i.e. a Digest value of the Document is created using a Hash Algorithm and then the senders Private Key is used to encrypt the MessageDigest to create the SignatureValue. Now when the receiving application receives the signed XML Document, it uses the public key to decrypt the data to retrieve the MessageDigest. Receiving application then creates the MessageDigest and compares to find whether the data has been modified or not.)
Note: When there are multiple DataObjects even if one fails to match with the DigestValue the core validation fails.
Load the PublicKey File.
XmlDocument objDSAKeyPairXMLdocument = new XmlDocument();
objDSAKeyPairXMLdocument.Load("XMLSignatureDSAPublicKey.xml");
string strDSAKeyPairXML = objDSAKeyPairXMLdocument.InnerXml;
Add the KeyInfo
The following line of code is required, as the KeyInfo was not sent as part of SignedXML document.
// Create a SignedXml.
SignedXml signedXml = new SignedXml();
DSA objDSAkeyPair = DSA.Create();
objDSAkeyPair.FromXmlString(strDSAKeyPairXML);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new DSAKeyValue(objDSAkeyPair));
signedXml.KeyInfo = keyInfo;
Load the SignedXML Document
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
xmlDocument.LoadXml(signedxmldata);
Verify the Signature
The method CheckSignature() on SignedXML will check the signature value.
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
if (signedXml.CheckSignature())
{
Console.WriteLine("Signature check OK");
}
else
{
Console.WriteLine("Signature check FAILED");
}
Extending to WebService
XML Signature can leverage the Web Services to create, manage and sign XML documents at one place.