Java生成XML数字签名

一、生成证书文件

1、安装openssl,装好后输入openssl进入openssl交互界面

[root@kolbe ~]# yum install openssl
[root@kolbe ~]# openssl
OpenSSL>

2、生成RSA私钥文件,并输出到当前目录,命名为 rsa_private_key.pem 2018

OpenSSL> genrsa -out rsa_private_key.pem 2048

Generating RSA private key, 2048 bit long modulus
....................................................................................................................+++
....................................................................................................................................+++
e is 65537 (0x10001)

3、将RSA私钥转换成PKCS8格式,并输出到当前目录,命名为 pkcs8_rsa_private_key.pem

OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -out pkcs8_rsa_private_key.pem -nocrypt

writing RSA key

4、根据私钥文件生成RSA公钥文件,并输出到当前目录,命名为 rsa_public_key.pem

OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

5、通过私钥文件创建csr证书文件,并输出到当前目录,命名为 rsa_cert.csr

OpenSSL> req -new -out rsa_cert.csr -key rsa_private_key.pem


You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----

# 输入国家代码
Country Name (2 letter code) [XX]:cn

# 输入省份
State or Province Name (full name) []:sichuan

# 输入城市
Locality Name (eg, city) [Default City]:chengdu

# 输入公司
Organization Name (eg, company) [Default Company Ltd]:kolbe

# 输入部门
Organizational Unit Name (eg, section) []:kolbe

# 输入姓名
Common Name (eg, your name or your server's hostname) []:kolbe

# 输入邮箱
Email Address []:kolbe@email.com

Please enter the following 'extra' attributes
to be sent with your certificate request

# 输入密码(留空)
A challenge password []:

# 可选的公司(留空)
An optional company name []:

6、自签署证书,并输出到当前目录,命名为 rsa_cert.pem

OpenSSL> x509 -req -in rsa_cert.csr -out rsa_cert.pem -signkey rsa_private_key.pem

Signature ok    
subject=/C=cn/ST=sichuan/L=chengdu/O=kolbe/OU=kolbe/CN=kolbe/emailAddress=kolbe@email.com
Getting Private key

 

至此我们共生成了五个证书文件:

rsa_private_key.pem、pkcs8_rsa_private_key.pem、rsa_public_key.pem、rsa_cert.csr、rsa_vert.pem

二、Java 为 Xml 生成 Signature 

1、Key加载工具类

package cn.kolbe.xml.generator;

import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Decoder;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 证书加载工具
 *
 * @author Kolbe
 */
public class KeyUtil {

    /**
     * 从指定路径获取公钥
     *
     * @param filePath 公钥文件路径
     * @return 公钥
     */
    public static PublicKey loadPublicKey(String filePath) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)));
            StringBuilder sb = new StringBuilder();
            String s;
            while ((s = reader.readLine()) != null) {
                if (s.charAt(0) != '-') {
                    sb.append(s);
                    sb.append('\r');
                }
            }
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(sb.toString());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            IOUtils.closeQuietly(reader);
        }
    }

    /**
     * 从指定文件路径获取私钥
     *
     * @param filePath 私钥文件路径
     * @return 私钥
     */
    public static PrivateKey loadPrivateKey(String filePath) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)));
            StringBuilder sb = new StringBuilder();
            String s;
            while ((s = reader.readLine()) != null) {
                if (s.charAt(0) != '-') {
                    sb.append(s);
                    sb.append('\r');
                }
            }
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(sb.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            IOUtils.closeQuietly(reader);
        }
    }

    /**
     * 从指定路定,加载签署过的证书文件
     *
     * @param filePath 证书文件路径
     * @return 证书文件
     */
    public static Certificate loadCertificate(String filePath) {
        InputStream is = null;
        try {
            is = new FileInputStream(filePath);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate certificate = (X509Certificate) cf.generateCertificate(is);
            return certificate;
        } catch (Exception e) {
            throw new RuntimeException("Load certificate error:", e);
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

}

 

2、KeyInfo 选择器

package cn.kolbe.xml.generator;

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import java.security.Key;
import java.security.PublicKey;

/**
 * @author Kolbe
 */
public class X509KeySelector extends KeySelector {

    private PublicKey key;

    public X509KeySelector(PublicKey key) {
        this.key = key;
    }

    @Override
    public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
        return new KeySelectorResult() {
            @Override
            public Key getKey() {
                return key;
            }
        };
    }
}

 

3、Xml签名请求参数

package cn.kolbe.xml.generator;

/**
 * Xml签名请求参数
 *
 * @author Kolbe
 */
public class XmlSignatureParam {

    /**
     * 私钥地址(PKCS8格式)
     */
    private String privateKeyPath;

    /**
     * 公钥地址
     */
    private String publicKeyPath;

    /**
     * 证书文件地址(经过签署过的证书文件)
     */
    private String certificatePath;

    /**
     * 待生成签名的xml文件路径
     */
    private String sourceXmlPath;

    /**
     * 目标生成签名的xml文件路径
     */
    private String targetXmlPath;

    public String getPrivateKeyPath() {
        return privateKeyPath;
    }

    public XmlSignatureParam setPrivateKeyPath(String privateKeyPath) {
        this.privateKeyPath = privateKeyPath;
        return this;
    }

    public String getPublicKeyPath() {
        return publicKeyPath;
    }

    public XmlSignatureParam setPublicKeyPath(String publicKeyPath) {
        this.publicKeyPath = publicKeyPath;
        return this;
    }

    public String getSourceXmlPath() {
        return sourceXmlPath;
    }

    public XmlSignatureParam setSourceXmlPath(String sourceXmlPath) {
        this.sourceXmlPath = sourceXmlPath;
        return this;
    }

    public String getTargetXmlPath() {
        return targetXmlPath;
    }

    public XmlSignatureParam setTargetXmlPath(String targetXmlPath) {
        this.targetXmlPath = targetXmlPath;
        return this;
    }

    public String getCertificatePath() {
        return certificatePath;
    }

    public XmlSignatureParam setCertificatePath(String certificatePath) {
        this.certificatePath = certificatePath;
        return this;
    }
}

 

4、Xml签名工具类

package cn.kolbe.xml.generator;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.XPathFilter2ParameterSpec;
import javax.xml.crypto.dsig.spec.XPathType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Xml签名工具类
 *
 * @author Kolbe
 */
public class XmlSignatureUtil {


    public static void main(String[] args) throws Exception {
        XmlSignatureParam param = new XmlSignatureParam();
        param
                // 私钥必需使用PKCS8格式
                .setPrivateKeyPath("/opt/key/pkcs8_rsa_private_key.pem")
                .setPublicKeyPath("/opt/key/rsa_public_key.pem")
                // 证书必须是经过签署过的
                .setCertificatePath("/opt/key/rsa_cert.pem")
                .setSourceXmlPath("/opt/key/source.xml")
                .setTargetXmlPath("/opt/key/target.xml");
        signature(param);
        System.out.println("Xml verify result:" + verify(param));
    }


    /**
     * 对指定xml文件进行签名
     *
     * @param param 请求参数
     * @throws Exception 签名异常
     */
    public static void signature(XmlSignatureParam param) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(param.getSourceXmlPath()));
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
        List<Transform> transforms = new ArrayList<>();
        // 只校验文档的 Header 和 Body 部分
        XPathType xPathType = new XPathType("//*[local-name()='Header' or local-name()='Body']", XPathType.Filter.INTERSECT);
        XPathFilter2ParameterSpec xPathFilter = new XPathFilter2ParameterSpec(Collections.singletonList(xPathType));
        Transform transform = fac.newTransform(Transform.XPATH2, xPathFilter);
        transforms.add(transform);
        DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA256, null);
        Reference reference = fac.newReference("", digestMethod, transforms, null, null);

        CanonicalizationMethod c14nWithCommentMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null);
        SignatureMethod signatureMethod = fac.newSignatureMethod(("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"), null);
        SignedInfo signedInfo = fac.newSignedInfo(c14nWithCommentMethod, signatureMethod, Collections.singletonList(reference));

        KeyInfoFactory keyInfoFac = fac.getKeyInfoFactory();
        KeyInfoFactory factory = KeyInfoFactory.getInstance("DOM");

        X509Data x509Data = factory.newX509Data(Collections.singletonList(KeyUtil.loadCertificate(param.getCertificatePath())));
        KeyInfo keyInfo = keyInfoFac.newKeyInfo(Collections.singletonList(x509Data));

        XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo);
        DOMSignContext dsc = new DOMSignContext(KeyUtil.loadPrivateKey(param.getPrivateKeyPath()), doc.getDocumentElement());
        // 添加 Signature 部分的命名空间
        dsc.setDefaultNamespacePrefix("ns3");

        signature.sign(dsc);
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(new File(param.getTargetXmlPath()));
        transformer.transform(source, result);
    }


    /**
     * 对指定带签名的xml文件进行校验
     *
     * @param param 请求参数
     * @return true 校验成功, false 校验失败
     * @throws Exception 异常
     */
    public static boolean verify(XmlSignatureParam param) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder builder = dbf.newDocumentBuilder();
        Document doc = builder.parse(param.getTargetXmlPath());
        Node nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
        DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(KeyUtil.loadPublicKey(param.getPublicKeyPath())), nl);
        XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
        XMLSignature signature = factory.unmarshalXMLSignature(valContext);
        return signature.validate(valContext);
    }

}

 

5、待签名的Xml文件示例 source.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Shipment xmlns="http://www.kolbe.cn/Shipment" xmlns:ns2="http://www.w3.org/2001/04/xmlenc#" xmlns:ns3="http://www.w3.org/2000/09/xmldsig#">
  <Header>
    <Customer>Kolbe</Customer>
  </Header>
  <Body>
    <Device>
      <DeviceNo>100000000001</DeviceNo>
      <DeviceType>1</DeviceType>
    </Device>
  </Body>
</Shipment>

 

6、签名过的Xml文件示例 target.xml

<?xml version="1.0" encoding="utf-8"?>

<Shipment xmlns="http://www.kolbe.cn/Shipment" xmlns:ns2="http://www.w3.org/2001/04/xmlenc#" xmlns:ns3="http://www.w3.org/2000/09/xmldsig#">  
  <Header> 
    <Customer>Kolbe</Customer> 
  </Header>  
  <Body> 
    <Device> 
      <DeviceNo>100000000001</DeviceNo>  
      <DeviceType>1</DeviceType> 
    </Device> 
  </Body>  
  <ns3:Signature>
    <ns3:SignedInfo>
      <ns3:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <ns3:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <ns3:Reference URI="">
        <ns3:Transforms>
          <ns3:Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">
            <ns3:XPath xmlns:ns3="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">//*[local-name()='Header' or local-name()='Body']</ns3:XPath>
          </ns3:Transform>
        </ns3:Transforms>
        <ns3:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ns3:DigestValue>Q98K9JWULpTG9cBXMKPIdL0lg8f16r7GlK6uerOsztM=</ns3:DigestValue>
      </ns3:Reference>
    </ns3:SignedInfo>
    <ns3:SignatureValue>Lneio8EOWK6wIQtNy9jttFh6eXyk7h0kzuytA33r3ALSeO/cg79PpNyXEONgKqJN+fDd0t0endFN mp57nxNLoZiP5GLm3pVGbIv9K8+NQgjO1INk2b0NO/yYlf/lshX8ux8vP3qS5pN3zmEhG5nNFZtE cLLh5z4hJDCb8DNdxIrfwUi/RLAZoUJX+j3eA0QSs69qioH5SbHRozaXfRFyMuI5um6qG2krCNzD ef6qGih/CDOMS4A0tXSBiPqn/ibg6jZDIlHebaw8VV9iDXQtUNkW29jZxaL25/zQLOR4RnvNXI/E ChACumvk3RvYJEdv3kYozqxVZlaHFr0hXT6KIQ==</ns3:SignatureValue>
    <ns3:KeyInfo>
      <ns3:X509Data>
        <ns3:X509Certificate>MIIDgDCCAmgCCQD8woqWbwfU4jANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCY24xEDAOBgNV BAgMB3NpY2h1YW4xEDAOBgNVBAcMB2NoZW5nZHUxDjAMBgNVBAoMBWtvbGJlMQ4wDAYDVQQLDAVr b2xiZTEOMAwGA1UEAwwFa29sYmUxHjAcBgkqhkiG9w0BCQEWD2tvbGJlQGVtYWlsLmNvbTAeFw0x NzEyMTQwODU2MjJaFw0xODAxMTMwODU2MjJaMIGBMQswCQYDVQQGEwJjbjEQMA4GA1UECAwHc2lj aHVhbjEQMA4GA1UEBwwHY2hlbmdkdTEOMAwGA1UECgwFa29sYmUxDjAMBgNVBAsMBWtvbGJlMQ4w DAYDVQQDDAVrb2xiZTEeMBwGCSqGSIb3DQEJARYPa29sYmVAZW1haWwuY29tMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm9j6WBIwrz9r0tL1fgvEAsmYreyjfKgkEZf0PAl85MvI4Y+o 9hCKRcw3GV0o/W52jEMxYuxSQpJuidzyo9KgEdUnwKa8FWvPoH0CsAMdIAwB0H8Iro0T+FVEcLtH ZPwIoXC0P3EGzkWgVPSkaDM9aml3+oG/FQXR5/bIDTv+k6CxwZMBuBpTq1n6ZMgtPEX0zZCN5n/t iYby6smrWq/KboMZpovo7SyhfQMZbNAto1pFfq6NXwqH62gReify97GztDYbO3H3jgiheLM9udPK Y4uj/1fnTInO444KoTOrQPNJ/+CrQ0PvK9yOQqKeTaYaBoefhzs8GQ9WcxbDiDm6DQIDAQABMA0G CSqGSIb3DQEBCwUAA4IBAQCCSSMy7Q/KBYy5Fk36AjPhw5BgCotPWsQCMELQNsVE/+660ovDDFpd WW5K3QAdTSFYMROvSdNbAifzLvNEZCFGr+tImz4jBAsh8ADkvQ3dr/puiGmHdon2M4hk3TLPrcQC LTE5rYWDq/LR35D7bqFrzvno0+of1slaguIJQ7RxAioqCcoRIKrs4psI5NbM8Hc0AWYLYQgHfjqR YyWXdhkUqd0Z8cEptG22tKKzVIJyYiX9lX6fcrYIDcETWB9cRKT/4bu0RzRHEYrQxYRWMmH8vV1V v2xEfAF9eICpER3hvFRVjz2vtuyjOcJWb6zZEqF7A4HTgHhPfJH1lpTO0g4o</ns3:X509Certificate>
      </ns3:X509Data>
    </ns3:KeyInfo>
  </ns3:Signature>
</Shipment>

 

项目中只用到了commons-io包,没引用别的特殊依赖包

 

转载于:https://my.oschina.net/kolbe/blog/1589927

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值