Using the Java XML Digital Signature API

http://www.java-tips.org/java-ee-tips/xml-digital-signature-api/using-the-java-xml-digital-signatur-2.html

 

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:

  1. Rehash the document data that was received.

  2. Decrypt the encrypted digest with the author's public key that is typically appended to the document.

  3. 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 theSignedInfo 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",
       (ProviderClass.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",
 (ProviderClass.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",
            (ProviderClass.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",
            (ProviderClass.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

  1. Download the sample archive for the Java XML Digital Signature API tip.

  2. Download and install Java WSDP 1.5 from the Java Web Services Developer Pack Downloads page.

  3. 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%
  4. Change to the directory where you downloaded the sample archive. Uncompress the JAR file for the sample archive as follows:
     jar xvf ttJan2005xmldsig.jar
  5. 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.

  6. 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值