Apache Santuario 常用工具与 API 详解
Apache Santuario 常用工具与 API 详解:Santuario 是纯 Java 类库,无独立命令行工具,全部能力经 Java API 调用。本文分类速查 JSR-105 签名 API(XMLSignatureFactory、KeyInfoFactory、Reference、Transform、DOMSignContext、DOMValidateContext)与原生 API(Init、ResourceResolver、XMLCipher),附 XMLDSigRI 提供者辨析、jar 诊断工具用法与实测回显,示例均在 Ubuntu 22.04 与 OpenJDK 17 实测。
Apache Santuario 常用工具与 API 详解
目标:详解 Apache Santuario 3.0.6(制品名
xmlsec)的核心 API 与工具用法。Santuario 是纯 Java 类库,没有独立命令行工具,签名、验签与加密全部通过 Java API 完成。本文按「JSR-105 签名 API + 原生 API + jar 诊断工具」分类速查XMLSignatureFactory、KeyInfoFactory、Reference、Transform、DOMSignContext、DOMValidateContext等关键入口,给出已实测的代码示例与真实回显。所有示例在 Ubuntu 22.04.5 + OpenJDK 17 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- Java:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- Santuario 版本:xmlsec 3.0.6(3.x 线最新稳定版)
- 示例工程目录:
/root/xmlsec-demo/(groupIdorg.example,artifactIdxmlsec-demo) - 演示账号:root
版本回显:
$ java -version
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"
二、工具概览
Santuario 以「类库 + jar」形态提供,没有 santuario 之类的命令行程序;XML 签名、验签与加密均通过 Java API 在自己的代码中完成。API 分两层,另有一组 JDK 自带的 jar 诊断工具:
| 层 / 类别 | 包 | 代表入口 | 用途 |
|---|---|---|---|
| JSR-105 标准 API | javax.xml.crypto.dsig.* | XMLSignatureFactory、KeyInfoFactory、Reference、Transform、DOMSignContext、DOMValidateContext | XML 数字签名(标准接口,推荐) |
| Santuario 提供者 | org.apache.jcp.xml.dsig.internal.dom.* | XMLDSigRI | 把 JSR-105 工厂绑定到 Santuario 实现 |
| 原生 API | org.apache.xml.security.* | Init、XMLSignature、ResourceResolver、XMLCipher | 初始化、资源解析、XML 加密 |
| jar 诊断工具 | JDK 自带 | javap、unzip、strings、jps | 查看类签名、算法常量、JVM 参数生效情况 |
💡 说明:JSR-105 是 Java 官方的 XML 数字签名 API(
javax.xml.crypto.dsig包);Santuario 3.0.x 通过XMLDSigRI提供者以名称ApacheXMLDSig接入。原生 API(org.apache.xml.security.*)功能更全(含 XML Encryption),但属实现层接口。提供者回显中的服务清单即能力边界:DOM XMLSignatureFactory; DOM KeyInfoFactory; C14N 1.0, C14N 1.1, Exclusive C14N, Base64, Enveloped, XPath, XPath2, XSLT TransformServices。
三、前置条件
按《Apache Santuario 安装指南》装好 JDK 17 与 Maven 并创建工程,根 pom 引入唯一依赖:
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>3.0.6</version>
</dependency>
编译确认依赖就位:
cd /root/xmlsec-demo && mvn -q compile
下文 $J 指本地仓库中的制品 jar,先赋值:
J=~/.m2/repository/org/apache/santuario/xmlsec/3.0.6/xmlsec-3.0.6.jar
四、JSR-105 签名 API 详解
4.1 XMLSignatureFactory(签名工厂入口)
JSR-105 一切签名对象的构造入口。关键方法(javap 实测节选):
public static javax.xml.crypto.dsig.XMLSignatureFactory getInstance(java.lang.String);
public static javax.xml.crypto.dsig.XMLSignatureFactory getInstance(java.lang.String, java.security.Provider);
public static javax.xml.crypto.dsig.XMLSignatureFactory getInstance(java.lang.String, java.lang.String) throws java.security.NoSuchProviderException;
public static javax.xml.crypto.dsig.XMLSignatureFactory getInstance();
public abstract javax.xml.crypto.dsig.XMLSignature newXMLSignature(javax.xml.crypto.dsig.SignedInfo, javax.xml.crypto.dsig.keyinfo.KeyInfo);
public abstract javax.xml.crypto.dsig.XMLSignature newXMLSignature(javax.xml.crypto.dsig.SignedInfo, javax.xml.crypto.dsig.keyinfo.KeyInfo, java.util.List<? extends javax.xml.crypto.dsig.XMLObject>, java.lang.String, java.lang.String);
public final javax.xml.crypto.dsig.keyinfo.KeyInfoFactory getKeyInfoFactory();
public abstract javax.xml.crypto.dsig.XMLSignature unmarshalXMLSignature(javax.xml.crypto.dsig.XMLValidateContext) throws javax.xml.crypto.MarshalException;
public abstract javax.xml.crypto.dsig.XMLSignature unmarshalXMLSignature(javax.xml.crypto.XMLStructure) throws javax.xml.crypto.MarshalException;
| 方法 | 说明 |
|---|---|
getInstance("DOM", provider) | 获取 DOM 形态工厂并绑定 Santuario 提供者 |
newXMLSignature(SignedInfo, KeyInfo) | 构造 XMLSignature 签名对象 |
unmarshalXMLSignature(XMLValidateContext) | 从文档反序列化 Signature,供验签 |
getKeyInfoFactory() | 取同提供者的 KeyInfoFactory |
newReference / newSignedInfo / newDigestMethod / newSignatureMethod / newCanonicalizationMethod / newTransform | 构造 Reference、SignedInfo 及各算法对象 |
工厂获取与提供者确认(示例代码):
// 显式传入 Santuario 提供者,避免解析到 JDK 内置实现
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM", new XMLDSigRI());
System.out.println("[Info] XMLSignatureFactory provider: " + fac.getProvider().getName()
+ " - " + fac.getProvider().getInfo());
实测回显:
[Info] XMLSignatureFactory provider: ApacheXMLDSig - Apache Santuario XMLDSig (DOM XMLSignatureFactory; DOM KeyInfoFactory; C14N 1.0, C14N 1.1, Exclusive C14N, Base64, Enveloped, XPath, XPath2, XSLT TransformServices)
4.2 XMLDSigRI 提供者与 JDK 内置实现辨析
JDK 的 java.xml.crypto 模块内置了一个与 Santuario 包名相同的提供者 org.jcp.xml.dsig.internal.dom.XMLDSigRI。不显式传参时,getInstance("DOM") 会解析到 JDK 内置实现而非 Santuario。用以下诊断类观察——创建 /root/xmlsec-demo/src/main/java/org/example/DiagExample.java:
package org.example;
import org.apache.xml.security.Init;
import javax.xml.crypto.dsig.XMLSignatureFactory;
public class DiagExample {
public static void main(String[] args) throws Exception {
// A) 未显式指定提供者时,getInstance("DOM") 解析到哪个实现
XMLSignatureFactory def = XMLSignatureFactory.getInstance("DOM");
System.out.println("[A] default getInstance(\"DOM\") provider: " + def.getProvider().getName()
+ " | class: " + def.getProvider().getClass().getName());
// B) 原生 API 的初始化状态(org.apache.xml.security.Init)
System.out.println("[B] Init.isInitialized() before: " + Init.isInitialized());
Init.init();
System.out.println("[B] Init.isInitialized() after : " + Init.isInitialized());
}
}
实测输出:
$ mvn -q compile exec:java -Dexec.mainClass=org.example.DiagExample
[A] default getInstance("DOM") provider: XMLDSig | class: org.jcp.xml.dsig.internal.dom.XMLDSigRI
[B] Init.isInitialized() before: false
[B] Init.isInitialized() after : true
显式 new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI()(classpath 上的 Santuario jar 生效)后,provider 名变为 ApacheXMLDSig(回显见 4.1)。提供者类签名(javap 实测):
$ javap -classpath $J org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI
Compiled from "XMLDSigRI.java"
public final class org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI extends java.security.Provider {
static final long serialVersionUID;
public org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI();
...
}
⚠️ 注意:JDK 内置实现与 Santuario 实现行为不完全一致(例如 secure validation 属性的读取方);使用 Santuario 时必须显式传入其 XMLDSigRI 实例。
4.3 KeyInfoFactory 与 KeyInfo / KeyValue
KeyInfoFactory 负责构造 KeyInfo(密钥信息结构),通过工厂的 getKeyInfoFactory() 获取,与 XMLSignatureFactory 同提供者(提供者回显中的 DOM KeyInfoFactory 即对应它)。签名示例把 RSA 公钥以 KeyValue 形式内嵌:
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
| 方法 | 说明 |
|---|---|
newKeyValue(PublicKey) | 用公钥构造 KeyValue 结构(RSA 公钥生成 RSAKeyValue) |
newKeyInfo(List<XMLStructure>) | 组装 KeyInfo 结构,可携带多个密钥子元素 |
getKeyInfoFactory() | 从 XMLSignatureFactory 获取同提供者实例 |
签名产物 signed.xml 中的实测形态:<KeyInfo><KeyValue><RSAKeyValue><Modulus>公钥模数 Base64 值</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue></KeyInfo>,验签方可直接取用公钥模数与指数。
4.4 Reference 与 Transform(摘要与转换)
Reference 描述「对什么内容、经何转换、用什么摘要算法」计算摘要。签名示例对整个文档(URI="")做 enveloped 转换 + SHA256 摘要:
DigestMethod dm = fac.newDigestMethod(DigestMethod.SHA256, null);
Transform tf = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
Reference ref = fac.newReference("", dm, Collections.singletonList(tf), null, null);
| 要素 | 示例取值 | 说明 |
|---|---|---|
| URI | ""(空串) | 覆盖整个文档 |
| Transform | http://www.w3.org/2000/09/xmldsig#enveloped-signature | 摘要计算时剔除 Signature 自身,避免自引用 |
| DigestMethod | http://www.w3.org/2001/04/xmlenc#sha256 | SHA256 摘要 |
摘要算法支持清单(DOMDigestMethod.class 字符串实测):
http://www.w3.org/2000/09/xmldsig#sha1
http://www.w3.org/2001/04/xmldsig-more#sha224
http://www.w3.org/2001/04/xmlenc#sha256
http://www.w3.org/2001/04/xmldsig-more#sha384
http://www.w3.org/2001/04/xmlenc#sha512
另有 RIPEMD160 与 SHA3_224/256/384/512 实现类常量;MD5 在 newDigestMethod 构造期即被拒绝,实测抛 NoSuchAlgorithmException: unsupported algorithm(算法限制细节见《Apache Santuario 配置参数详解》)。
4.5 SignedInfo 与 XMLSignature(签名信息与签名对象)
SignedInfo 汇集规范化方法、签名算法与 Reference 列表;XMLSignature 用它构造并执行签名:
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref));
XMLSignature signature = fac.newXMLSignature(si, ki);
| 方法 | 说明 |
|---|---|
newCanonicalizationMethod(INCLUSIVE, null) | 规范化方法,示例对应 http://www.w3.org/TR/2001/REC-xml-c14n-20010315 |
newSignatureMethod(RSA_SHA256, null) | 签名算法,产物 URI 为 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 |
newSignedInfo(c14n, sigMethod, references) | 组装 SignedInfo |
signature.sign(DOMSignContext) | 执行签名,Signature 追加到指定节点 |
签名方法家族(DOMSignatureMethod.class 常量实测):HMAC 系列、RSA(SHA1/SHA224/SHA256/SHA384/SHA512 withRSA)、RSAandMGF1(RSA-PSS)、ECDSA(含 SHA3 变体)、DSA(SHA1withDSA / SHA256withDSA)、EDDSA(ED25519 / ED448)等。
4.6 DOMSignContext 与 DOMValidateContext(签名与验签上下文)
两个上下文分别承载签名与验签的密钥及目标节点,均实现 javax.xml.crypto.XMLCryptoContext,可用 setProperty 设置按次生效的属性:
// 签名:私钥 + Signature 挂载位置(根元素,签名作为其最后一个子节点)
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), doc.getDocumentElement());
signature.sign(dsc);
// 验签:公钥 + Signature 节点
org.w3c.dom.NodeList sigs = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
DOMValidateContext vc = new DOMValidateContext(publicKey, sigs.item(0));
vc.setProperty("org.apache.jcp.xml.dsig.secureValidation", Boolean.TRUE);
XMLSignature signature = fac.unmarshalXMLSignature(vc);
boolean coreValid = signature.validate(vc);
验签通过实测回显:
$ mvn -q compile exec:java -Dexec.mainClass=org.example.ValidateExample
signature valid: true
ref[0] validity status: true
篡改金额后验签失败实测回显(ValidateTamperedExample,把 12800.00 改为 99999.00):
$ mvn -q compile exec:java -Dexec.mainClass=org.example.ValidateTamperedExample
[Info] Amount before tamper: 12800.00
[Info] Amount after tamper: 99999.00
signature valid: false
💡 说明:
signature valid: true表示核心验证(引用验证 + 签名验证)全部通过;ref[0] validity status: true为第 0 个 Reference 的摘要验证结果。
五、原生 API 与 jar 诊断工具
5.1 Init(原生层初始化)
原生层(org.apache.xml.security.*)的初始化入口(javap 实测):
$ javap -classpath $J org.apache.xml.security.Init
Compiled from "Init.java"
public class org.apache.xml.security.Init {
public static final java.lang.String CONF_NS;
public org.apache.xml.security.Init();
public static final synchronized boolean isInitialized();
public static synchronized void init();
static {};
}
Init.init() 幂等,全局调用一次即可;它读取 jar 内置配置 org/apache/xml/security/resource/config.xml(附 config.dtd)。初始化状态实测 false → true(见 4.2 DiagExample 的 [B] 输出)。配置资源清单(jar tf 实测):
org/apache/xml/security/resource/config.dtd
org/apache/xml/security/resource/config.xml
5.2 ResourceResolver(资源解析器注册)
外部引用(本地文件、HTTP、XPointer 等)如何取回由 ResourceResolver 的静态注册方法控制(javap 实测):
$ javap -classpath $J org.apache.xml.security.utils.resolver.ResourceResolver | grep -E 'register|getInstance'
public static void register(java.lang.String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
public static void registerAtStart(java.lang.String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
public static void register(org.apache.xml.security.utils.resolver.ResourceResolverSpi, boolean);
public static void registerClassNames(java.util.List<java.lang.String>) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
public static void registerDefaultResolvers();
jar 内置 5 个解析器实现(jar tf 实测):
org/apache/xml/security/utils/resolver/implementations/ResolverAnonymous.class
org/apache/xml/security/utils/resolver/implementations/ResolverDirectHTTP.class
org/apache/xml/security/utils/resolver/implementations/ResolverFragment.class
org/apache/xml/security/utils/resolver/implementations/ResolverLocalFilesystem.class
org/apache/xml/security/utils/resolver/implementations/ResolverXPointer.class
registerAtStart 将解析器插到查找链最前、优先级最高;registerDefaultResolvers 恢复内置默认集合。安全起见,业务只需处理同文档引用(URI=""、#id)时,可只保留 Fragment/XPointer 相关解析器,移除 DirectHTTP 等外链解析能力。
5.3 XMLSignature(原生签名对象)
原生层 org.apache.xml.security.signature.XMLSignature 直接以 Document 为入口构造签名对象,多组重载(javap 实测节选):
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, java.security.Provider) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, int) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, int, java.security.Provider) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, java.lang.String) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, java.lang.String, java.security.Provider) throws org.apache.xml.security.exceptions.XMLSecurityException;
public org.apache.xml.security.signature.XMLSignature(org.w3c.dom.Document, java.lang.String, java.lang.String, int, java.lang.String) throws org.apache.xml.security.exceptions.XMLSecurityException;
重载参数覆盖 BaseURI、签名算法标识、摘要位数与 JCA Provider 等变体,具体以 javap 输出为准;原生层使用前需完成 Init.init()(见 5.1)。
5.4 XMLCipher(XML 加密入口)
XML Encryption 入口 org.apache.xml.security.encryption.XMLCipher 通过静态工厂创建(javap 实测全部 5 个重载):
$ javap -classpath $J org.apache.xml.security.encryption.XMLCipher | grep -E 'getInstance|encryptData|decryptToByteArray' | head -5
public static org.apache.xml.security.encryption.XMLCipher getInstance(java.lang.String) throws org.apache.xml.security.encryption.XMLEncryptionException;
public static org.apache.xml.security.encryption.XMLCipher getInstance(org.apache.xml.security.encryption.Serializer, java.lang.String) throws org.apache.xml.security.encryption.XMLEncryptionException;
public static org.apache.xml.security.encryption.XMLCipher getInstance(java.lang.String, java.lang.String) throws org.apache.xml.security.encryption.XMLEncryptionException;
public static org.apache.xml.security.encryption.XMLCipher getInstance(java.lang.String, java.lang.String, java.lang.String) throws org.apache.xml.security.encryption.XMLEncryptionException;
public static org.apache.xml.security.encryption.XMLCipher getInstance() throws org.apache.xml.security.encryption.XMLEncryptionException;
算法以 XML Encryption 标准算法 URI 字符串传入首个参数;其余重载用于自定义序列化器、JCA provider 等场景。创建加密对象后经 encryptData / decryptToByteArray 完成加解密(方法签名见上方 javap 输出)。
5.5 jar 诊断工具(javap / unzip / strings / jps)
排查「类签名、算法支持、属性名、字节码版本、JVM 参数」时,JDK 自带工具即可完成,实测用法:
# 查看类公开签名(如 Init、ResourceResolver、XMLCipher)
javap -classpath $J org.apache.xml.security.Init
# 查看 class 字节码版本(xmlsec 3.0.6 为 major version 52,即 Java 8 编译目标)
javap -verbose -classpath $J org.apache.xml.security.Init | grep major
# 提取 class 内字符串常量(属性名、算法 URI、错误消息)
unzip -p $J org/apache/jcp/xml/dsig/internal/dom/Utils.class | strings | grep -iE 'secure'
# 列出 jar 内资源
unzip -l $J | grep 'resource/config'
# 确认 JVM 堆参数生效
jps -v | grep -F 'Xmx512m'
strings 实测回显(Utils.class,secure validation 属性名来源):
secureValidation
(org.apache.jcp.xml.dsig.secureValidation
jps 实测回显(进程号以实际为准):
$ jps -v | grep -F 'Xmx512m'
6855 ValidateExample -Xms256m -Xmx512m
六、注意事项
⚠️ 注意:
- 必须显式
XMLSignatureFactory.getInstance("DOM", new XMLDSigRI()):不传提供者会解析到 JDK 内置实现(provider 名XMLDSig而非ApacheXMLDSig),Santuario 特有行为不可依赖 JDK 实现。- 解析待签名/验签文档必须
DocumentBuilderFactory.setNamespaceAware(true),否则无法按命名空间定位 Signature 节点。- secure validation 属性挂在单次上下文上:每次创建
DOMSignContext/DOMValidateContext后都要重新setProperty。- 生产统一使用 SHA256 及以上摘要算法;MD5 在构造期即被拒绝,不要尝试绕过。
- 首次运行出现 SLF4J 提示不是错误:xmlsec 未绑定日志实现,SLF4J 回退 NOP 日志,不影响功能。
七、常见问题
Q1:provider 名是 XMLDSig 还是 ApacheXMLDSig?
XMLDSig 是 JDK 内置实现(未显式传提供者);ApacheXMLDSig 是 Santuario(显式传入 org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI)。用 4.2 的 DiagExample 输出即可判别。
Q2:运行示例输出 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" 是错误吗?
不是错误。xmlsec 依赖 slf4j-api 1.7.36 但未绑定日志实现,SLF4J 回退 NOP,不影响签名与验签。实测提示原文:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
如需日志输出,可自行引入 slf4j-simple 等绑定包。
Q3:signature valid: false 怎么排查?
按命中率依次检查:签名后文档被改动(含空格、换行、缩进变化);签名与验签使用不同密钥对;序列化时启用 indent 破坏字节一致性;解析未开启 namespace aware。
Q4:怎样快速确认某个算法是否被支持?
两种方式:其一,javap / unzip -p 配合 strings 查看 DOMDigestMethod.class 与 DOMSignatureMethod.class 的算法 URI 常量(用法见 5.5);其二,直接 newDigestMethod / newSignatureMethod 试构造,不支持的算法在构造期即抛 NoSuchAlgorithmException(MD5 实测)。secure validation 开启时还有每 Reference 最多 5 个 Transform 的硬限制。
八、参考链接
- Apache Santuario 官网:http://santuario.apache.org/
- JSR-105(XML Digital Signature API)规范:https://www.jcp.org/en/jsr/detail?id=105
- JDK 17 java.xml.crypto 模块(javax.xml.crypto.dsig 包)文档:https://docs.oracle.com/en/java/javase/17/docs/api/java.xml.crypto/javax/xml/crypto/dsig/package-summary.html
- W3C XML Signature 语法与处理规范:https://www.w3.org/TR/xmldsig-core/
- W3C XML Encryption 语法与处理规范:https://www.w3.org/TR/xmlenc-core/