Is there an easier way to sign an XML document in Java?
Have look at Apache XML Security. To use the package to generate and verify a signature, checkout the samples in src_samples/org/apache/xml/security/samples/signature/
.
Building from the Apache Santuario CreateSignature
example, the shortest thing I could come up with is this. Without the main()
and its accompanying output()
, it's 20 lines
import java.io.*;import java.security.Key;import java.security.KeyStore;import java.security.cert.X509Certificate;import javax.xml.parsers.DocumentBuilderFactory;import org.apache.commons.io.IOUtils;import org.apache.xml.security.Init;import org.apache.xml.security.c14n.Canonicalizer;import org.apache.xml.security.signature.XMLSignature;import org.apache.xml.security.transforms.Transforms;import org.apache.xml.security.utils.Constants;import org.apache.xml.security.utils.ElementProxy;import org.w3c.dom.Document;public class CreateSignature { private static final String PRIVATE_KEY_ALIAS = "test-alias"; private static final String PRIVATE_KEY_PASS = "test"; private static final String KEY_STORE_PASS = "test"; private static final String KEY_STORE_TYPE = "JKS"; public static void main(String... unused) throws Exception { final InputStream fileInputStream = new FileInputStream("test.xml"); try { output(signFile(fileInputStream, new File("keystore.jks")), "signed-test.xml"); } finally { IOUtils.closeQuietly(fileInputStream); } } public static ByteArrayOutputStream signFile(InputStream xmlFile, File privateKeyFile) throws Exception { final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile); Init.init(); ElementProxy.setDefaultPrefix(Constants.SignatureSpecNS, ""); final KeyStore keyStore = loadKeyStore(privateKeyFile); final XMLSignature sig = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA); final Transforms transforms = new Transforms(doc); transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1); final Key privateKey = keyStore.getKey(PRIVATE_KEY_ALIAS, PRIVATE_KEY_PASS.toCharArray()); final X509Certificate cert = (X509Certificate)keyStore.getCertificate(PRIVATE_KEY_ALIAS); sig.addKeyInfo(cert); sig.addKeyInfo(cert.getPublicKey()); sig.sign(privateKey); doc.getDocumentElement().appendChild(sig.getElement()); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(doc)); return outputStream; } private static KeyStore loadKeyStore(File privateKeyFile) throws Exception { final InputStream fileInputStream = new FileInputStream(privateKeyFile); try { final KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE); keyStore.load(fileInputStream, KEY_STORE_PASS.toCharArray()); return keyStore; } finally { IOUtils.closeQuietly(fileInputStream); } } private static void output(ByteArrayOutputStream signedOutputStream, String fileName) throws IOException { final OutputStream fileOutputStream = new FileOutputStream(fileName); try { fileOutputStream.write(signedOutputStream.toByteArray()); fileOutputStream.flush(); } finally { IOUtils.closeQuietly(fileOutputStream); } }}
I looked at all of the options for signing XML files and decided to go with a non-standard approach. The standards were all way too verbose. Also, I didn't need compatibility with the standards---I just needed signatures on a block of XML.
Probably the easiest way to "sign" a block of XML is to use GPG with a detached signature.