在Java中,是否有更简单的方法来签署XML文档?

20

我正在尝试使用Java对XML文档进行数字签名。我已经按照我找到的一些引用实现了功能,这些引用使用了javax.xml.crypto.dsig包中的各种实现。

但是,我的当前实现与许多我查看的示例一样-它相当冗长,并涉及使用不少于23个不同的API类,来自java.xml.crypto.dsigjavax.xml.transformjava.security等其他包。感觉就像进入了工厂工厂工厂,花费了我几个小时才弄清楚发生了什么。

我的问题是,有更简单的方法吗? 如果我有公共/私有密钥文件,而我想向XML文档添加一个<Signature/>,是否存在一个库,只允许我调用类似于以下内容的东西:

OutputStream signFile(InputStream xmlFile, File privateKey)

...没有所有XMLSignatureFactory / CanonicalizationMethod / DOMSignContext的疯狂吗?

我对密码学不是很熟悉,Java提供的API似乎对像我这样试图熟悉数字签名的开发人员来说非常令人望而生畏。如果所有这些都是必需的或目前没有更友好的API可用,那没问题,我愿意接受这个答案。我只想知道是否在这里不必要地走了艰难的路。


作为另一种解决方案,我能够创建一个单行系统调用并使用http://www.aleksey.com/xmlsec/来签署XML。我没有将其发布为答案,因为它不在问题的范围内(即“在Java中”)。 - Rob Hruska
"factory factory factory" 链接已经失效。编辑:其他几个链接也是如此。 - byxor
3个回答

11

结合 Apache Santuario 的 CreateSignature 示例,我能想到的最简短的代码如下。不包含 main() 及其相关的 output() 部分,共20行。

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);
        }
    }
}

1
尝试了您的建议,但似乎无法将C14N算法从Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS更改为Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS。即使我在代码中进行更改,输出的XML签名仍然显示CanonicalizationMethod为<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />。这里可能出了什么问题?有什么想法吗? - mithrandir

11

谢谢你,Pascal。我在实际操作中遇到了一些问题,尤其是"java.io.IOException: Invalid keystore format",但我成功创建了自己的keystore.pks并使它正常工作。 - Kirby
4
链接已失效。 - Sireesh Yarlagadda

3
我查看了所有签署XML文件的选项,决定采用非标准方法。这些标准太过冗长。此外,我不需要与标准兼容 - 我只需要在一块XML上签名。
"签署"一块XML最简单的方法可能是使用GPG和分离的签名。

不错的想法,可能是迄今为止提供的最简单的想法。它并没有提供javax.xml.crypto或Apache's Santuario的所有可配置性,但这正是使那些其他工具如此复杂的原因。 - Rob Hruska
谢谢。一个实现这个功能的系统采用以下方法:获取XML块,使用RSA公钥和OpenSSL计算签名,然后将该签名嵌入到文本文件中XML块的末尾。您可以在我的afsing.cpp程序中找到该代码,该程序是AFFLIB的一部分,可以从http://afflib.org/下载。 - vy32
接受这个解决方案,因为它是更简单的解决方案。Pascal的解决方案也是合理的,尽管它仍然需要相当数量的代码。 - Rob Hruska
虽然简单而且好用,但在某些环境下可能无法实现,比如必须按特定方式包含签名的情况(例如 SOAP 服务器)。 - Filippo Mazza
我正在寻找使用Java/Sun包的标准方法。有链接吗? - kbec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接