|
This Tech Tip reprinted with permission by java.sun.com
XML digital signatures have been on programmers' wish lists
for some time. The good news is that JSR 105
now defines a standard Java technology API for providing XML digital
signatures. This API is now part of the Java
Web Services Development Pack (Java WSDP) 1.5.
The Java WSDP 1.5 includes an implementation of the JSR 105 Proposed
Final Draft. (Note that this is only the "proposed" final draft. The
libraries might change before the final release.) The stated purpose of
the Java XML Digital Signature API is to provide a vendor-neutral
implementation of the W3C Recommendations for XML-Signature Syntax and
Processing.
What is a digital signature? Much like its pen and paper
counterpart, a digital signature assures to anyone reading data that
the author is indeed who he or she claims to be. A digital signature
provides what cryptographers call "authentication." It also ensures
that the content of the data is exactly the same as what the author
signed (that is, nothing has been added or removed). In the world of
cryptography, this is known as "integrity."
Performing a digital signature involves two steps. In the first step,
the data is run through a hashing algorithm. A typical hashing
algorithm scans through the data and generates a number of some size --
this is typically called a "digest." If the same data is run through
the hashing algorithm again, the same digest should be generated. Good
hashing algorithms vary the digest unpredictably if the slightest
change is made in the data. This makes it impossible to reverse
engineer the original data, given the digest.
The second step in producing a digital signature is to encrypt the
digest using the private key of the author. If you're not familiar with
the terms public key or private key, then you've probably never heard
of public key cryptography. The basic concepts of public key
cryptography are simple: anything that is encrypted using an
individual's private key, can only be decrypted using the same
individual's public key. The reverse is also true: anything encrypted
using an individual's public key, can only be decrypted using the same
individual's private key. The two keys are mathematically linked. After
encrypting the digest with the user's private key, the resulting
scrambled data is then appended to the original document data.
Because public keys can be shared with anyone, and private
keys
should be known only to the signing author, verifying a digital
signature is simple. The steps are:
- Rehash the document data that was received.
- Decrypt the encrypted digest with the author's public key
that is typically appended to the document.
- Compare the two digests. If they are equal, the signature
is valid.
If the two digests are not equal, then either the document has
been
altered, or the author of the document is not the same as the
individual that signed it. However this information does not indicate
which of those two faults (or both) have occurred.
The Java XML Digital Signature API
There are two parts of the JSR 105 API. The first part allows Java
developers to create XML digital signatures for their data. The second
part allows third-party developers to create provider implementations
of the XML Digital Signature API and register them. This tip covers the
first part only. It presents a simple example of how to sign an XML
document and then verify that signature. Note that the XML signatures
generated by JSR 105 can be applied to both XML and binary data. The
resulting signature is created in XML.
An XML Signature takes one of three forms. Assuming that the XML
signature is contained in a <Signature>
element, the only difference between the three forms is where the <Signature>
element is located with respect to the document data. The three forms
are:
- Detached. A detached signature is over data that is
external to the signature element. That is:
<Signature> </Signature> <DocumentData> </DocumentData>
- Enveloping. An enveloping signature is a signature over
data that is inside the signature element.
<Signature> <DocumentData> </DocumentData> </Signature>
- Enveloped. An enveloped signature is a signature that is
contained inside the data that it is signing.
<DocumentData> <Signature> </Signature> </DocumentData>
Let's assume that the data to be signed is contained in an <Envelope>
element. Here is what the XML document looks like before applying a
signature:
<?xml version="1.0" encoding="UTF-8"?> <Envelope xmlns="urn:envelope"> <!--data is here--> </Envelope>
Here's what the XML document looks like after the data is
signed
with an enveloped signature (note that these examples have been
re-formatted and indented for easier reading):
<?xml version="1.0" encoding="UTF-8"?> <Envelope> <!--data is here--> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <!-- see below for the contents of this element --> </SignedInfo> <SignatureValue> <!-- see below for the contents of this element --> </SignatureValue> <KeyInfo> <!-- see below for the contents of this element --> </KeyInfo> </Signature> </Envelope>
A number of elements are added inside of the <Signature>
element
by the XML digital signature process. These elements are <SignedInfo>,
<SignatureValue> element, and <KeyInfo>.
The <SignedInfo> element
contains signature information, as well as references to the data that
is to be signed. In actuality, <SignedInfo>
is the element that the signature is calculated over. Here is how this
happens:
- The data that the references point to is transformed,
canonicalized (see below) and digested.
- The
<SignedInfo>
element containing those references is canonicalized, digested and
signed.
Here is what a <SignedInfo>
element might look like:
<CanonicalizationMethod Algorithm= "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> <SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#dsa-sha1"/> <Reference URI=""> <Transforms> <Transform Algorithm= "http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> </Transforms> <DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1"/> <DigestValue>uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue> </Reference>
The elements within the <SignedInfo>
element are:
<CanonicalizationMethod>.
Describes the algorithm that the signature process used to generate a
canonical version of the SignedInfo
element. A canonical version of an XML document resolves trivial
variances from different XML generators, such as explicit
namespace-scoped elements or whitespaces, that would generate different
signatures for documents that are otherwise logically equivalent. For
more information on why canonicalization is necessary, see the W3C
document Canonical
XML Version 1.0.
<SignatureMethod>.
Identifies the method used to generate the signature over the <SignedInfo>
element. In this example, the DSA (Digital Signature Algorithm) is used
after the SignedInfo digest is calculated
with the SHA (Secure Hashing Algorithm) 1.
<Reference> -- One
or more <Reference>
elements are listed that scope the data affected by their child
elements. In the example, the URI attribute contains an empty string
(""), that refers to the root element of the XML document (and so
includes the entire document). First a transformation is performed on
the data to ensure that the enveloped signature is not included in any
subsequent calculations. Next, the data is digested using the digest
algorithm listed. Finally, the digest value that was generated for the
document by that algorithm is shown.
The second element, <SignatureValue>,
is
relatively simple. It contains an arbitrarily-large text-encoded number
which is the scrambled signature for the document in question. For
example:
KedJuTob5gtvYx9qM3k3gm7kbLBwVbEQRl26S2tmXjqNND7MRGtoew==
Anyone who receives the XML document must be able to validate
this
signature. So the original document author (the signer) must append his
or her public key to the document. (In reality, the public key should
be authenticated with a certificate authority, but that's beyond the
scope of this tip.) The public key is stored in the third element, <KeyInfo>.
Here is an example of a raw DSA key. The value is in the <KeyValue>
element (a subelement of <KeyInfo>):
<KeyValue> <DSAKeyValue> <P> /KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxe Eu0ImbzRMqzVDZkVG9xD7nN1kuFw== </P> <Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q> <G>Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/ XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA== </G> <Y>qV38IqrWJG0V/ mZQvRVi1OHw9Zj84nDC4jO8P0axi1gb6d+475yhMjSc/ BrIVC58W3ydbkK+Ri4OKbaRZlYeRA== </Y> </DSAKeyValue> </KeyValue>
Using the XML Digital Signature API
Here is an example that can be used to generate an XML digital
signature (the example is adapted from the examples provided for the
API with Java WSDP 1.5):
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
Reference ref =
fac.newReference("",
fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(
fac.newTransform(Transform.ENVELOPED, null)),
null, null);
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1,
null),
Collections.singletonList(ref));
KeyPairGenerator kpg =
KeyPairGenerator.getInstance("DSA");
kpg.initialize(512);
KeyPair kp = kpg.generateKeyPair();
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki =
kif.newKeyInfo(Collections.singletonList(kv));
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().
parse(new FileInputStream("myfile"));
DOMSignContext dsc = new DOMSignContext
(kp.getPrivate(), doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(
new DOMSource(doc),
new StreamResult(
new FileOutputStream("mySignedFile"));
|
The following lines of code create the XML signature factory,
which is
used to sign the document:
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
|
The code then creates Reference and SignedInfo
objects. These map directly to their respective elements in the XML
signature.
Reference ref =
fac.newReference("",
fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(
fac.newTransform(Transform.ENVELOPED, null)),
null, null);
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1,
null),
Collections.singletonList(ref));
|
Next, the JCA classes are used to generate a 512-bit DSA
key,
and
create a KeyInfo object that includes the
public key. The KeyInfo object also maps to
the <KeyInfo> element in the
signature.
KeyPairGenerator kpg =
KeyPairGenerator.getInstance("DSA");
kpg.initialize(512);
KeyPair kp = kpg.generateKeyPair();
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki =
kif.newKeyInfo(Collections.singletonList(kv));
|
Finally, the code uses the JAXP DocumentBuilderFactory
to create a document from the filename given, create an XML signature
object, and use the TransformerFactory to
write the signature to an OutputStream. The
result is sent to the file mySignedFile.
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().
parse(new FileInputStream("myfile"));
DOMSignContext dsc = new DOMSignContext
(kp.getPrivate(), doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(
new DOMSource(doc),
new StreamResult(
new FileOutputStream("mySignedFile"));
|
Validating the Signature
Here is the source code needed to validate the signature:
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().parse(new
FileInputStream("mySignedFile"));
NodeList nl =
doc.getElementsByTagNameNS(XMLSignature.XMLNS,
"Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
String providerName = System.getProperty(
"jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
DOMValidateContext valContext = new DOMValidateContext
(new KeyValueKeySelector(), nl.item(0));
XMLSignature signature =
fac.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext);
if (coreValidity == false) {
System.err.println("Signature failed");
} else {
System.out.println("Signature passed");
}
}
|
The first section of code uses the JAXP DocumentBuilderFactory
to create a namespace-aware Document object
from the file mySignedFile:
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().parse(new
FileInputStream("mySignedFile"));
|
Next, it programmatically extracts the <Signature>
element (and all its children) from the XML:
NodeList nl =
doc.getElementsByTagNameNS(XMLSignature.XMLNS,
"Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
|
Then it creates an XMLSignatureFactory
and DOMValidateContext
object for use in unmarshalling the XML signature:
String providerName = System.getProperty(
"jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
DOMValidateContext valContext = new DOMValidateContext
(new KeyValueKeySelector(), nl.item(0));
|
After an appropriate XMLSignature
object is
obtained, the program calls the validate()
method using the appropriate context to determine whether the signature
is valid:
XMLSignature signature =
fac.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext);
if (coreValidity == false) {
System.err.println("Signature failed");
} else {
System.out.println("Signature passed");
}
}
|
For more information about the Digital Signature API, see Chapter
4: Java XML Digital Signature API in the Java Web Services
Tutorial.
Running the Sample Code for the Java XML
Digital
Signature API Tip
- Download the sample
archive for the Java XML Digital Signature API tip.
- Download and install Java WSDP 1.5 from the Java
Web Services Developer Pack Downloads page.
- Set your executable
PATH to
include the Ant application, which is located
in the apache-ant/bin directory of Java WSDP
1.5. For example, if you installed Java WSDP 1.5 in C:\jwsdp-1.5
in the Windows environment, enter the commands:
set JWSDP_HOME=C:\jwsdp-1.5 set ANT_HOME=%JWSDP_HOME%\apache-ant set PATH=%ANT_HOME%\bin;%PATH%
- Change to the directory where you downloaded the
sample
archive. Uncompress the JAR file for the sample archive as follows:
jar xvf ttJan2005xmldsig.jar
- Change to the
xmldsig base
directory. Edit the Ant script (build.xml)
to point to the base directory of your Java WSDP 1.5 installation.
- Run the
build.xml Ant
script by entering:
ant
on the command line.
In response, you should see the following:
Buildfile: build.xml
compile:
run: [java] Generating DSA Key Pair... [java] Signing XML Document... [java] Signed XML Output Save to File: envelopedSignature.xml [java] Retrieving Signed XML File At: envelopedSignature.xml [java] Searching for Element... [java] Verifying Signature... [java] Signature Is Valid
BUILD SUCCESSFUL
Copyright (c) 2004-2005 Sun Microsystems, Inc.
All Rights Reserved.
Related Tips
|
You can share your information about this topic using the form below!
Please do not post your questions with this form! Thanks.