In enterprise environments, XML remains a fundamental format for data exchange, document storage, and web services. XML Digital Signatures (XML-DSig) provide a standardized way to ensure the integrity, authenticity, and non-repudiation of XML documents. For Java developers working with financial transactions, government documents, healthcare records, or any system requiring legally binding digital signatures, XML-DSig offers a robust, W3C-standardized solution for signing XML content.
What are XML Digital Signatures?
XML Digital Signatures are a W3C recommendation that defines how to digitally sign XML documents. Unlike simple file signatures, XML-DSig can sign specific parts of an XML document, handle multiple signatures, and embed signature information directly within the signed XML. Key features include:
- Enveloped Signatures: Signature is embedded within the signed XML document
- Enveloping Signatures: Signature contains the signed data within itself
- Detached Signatures: Signature is separate from the signed data
- Multiple References: A single signature can cover multiple document parts
- Transform Support: Content can be transformed before signing (canonicalization, XPath, etc.)
Why XML Digital Signatures are Essential for Java Applications
- Legal Compliance: Many regulations (e-invoicing, e-government) require XML signatures
- Web Services Security: WS-Security relies on XML signatures for message integrity
- Document Integrity: Ensure XML documents haven't been tampered with
- Non-Repudiation: Prove the origin of XML documents
- Selective Signing: Sign only relevant parts of large XML documents
- Standardized Format: Interoperable across different platforms and vendors
Java XML Digital Signature API (JSR 105)
Java provides a built-in API for XML Digital Signatures through the javax.xml.crypto.dsig package, available since Java 6.
1. Maven Dependencies
<dependencies> <!-- Java XML Digital Signature API (built-in) --> <!-- No additional dependencies needed for basic functionality --> <!-- Apache Santuario (enhanced features, more algorithms) --> <dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>4.0.2</version> </dependency> <!-- For DOM manipulation --> <dependency> <groupId>org.w3c</groupId> <artifactId>dom</artifactId> <version>2.3.0-jaxb-1.0.6</version> </dependency> <!-- For XML parsing --> <dependency> <groupId>javax.xml.parsers</groupId> <artifactId>jaxp-api</artifactId> <version>1.4.5</version> </dependency> </dependencies>
Basic XML Digital Signature Examples
1. Creating an Enveloped Signature
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
public class XMLSignatureExample {
public static void main(String[] args) throws Exception {
System.out.println("=== XML Digital Signature Examples ===\n");
// 1. Create a simple enveloped signature
createEnvelopedSignature();
// 2. Create a detached signature
createDetachedSignature();
// 3. Verify a signature
verifySignature();
// 4. Sign with X.509 certificate
signWithCertificate();
}
public static void createEnvelopedSignature() throws Exception {
System.out.println("\n--- Enveloped Signature ---");
// Create XML document to sign
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
// Create root element
Element root = doc.createElementNS(null, "PurchaseOrder");
root.setAttribute("id", "PO-12345");
Element customer = doc.createElement("Customer");
customer.setTextContent("Acme Corporation");
root.appendChild(customer);
Element amount = doc.createElement("Amount");
amount.setTextContent("1250.00");
root.appendChild(amount);
Element description = doc.createElement("Description");
description.setTextContent("Office supplies");
root.appendChild(description);
doc.appendChild(root);
// Get the XML Digital Signature factory
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create reference to the whole document (with enveloped transform)
Reference ref = fac.newReference(
"", // Empty URI means whole document
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null, // No type
null // No ID
);
// Create SignedInfo
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Generate key pair (in practice, load from keystore)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
// Create KeyInfo
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
// Create and sign the signature
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Save signed document
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
System.out.println("\nSignature embedded successfully");
}
public static void createDetachedSignature() throws Exception {
System.out.println("\n--- Detached Signature ---");
// Create document with ID reference
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
Element root = doc.createElementNS(null, "Invoice");
Element invoiceId = doc.createElement("InvoiceID");
invoiceId.setAttribute("Id", "invoice-001");
invoiceId.setTextContent("INV-2024-001");
root.appendChild(invoiceId);
Element amount = doc.createElement("Amount");
amount.setTextContent("2999.99");
root.appendChild(amount);
doc.appendChild(root);
// Add ID attribute to the element we want to sign
invoiceId.setIdAttribute("Id", true);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Reference to specific element by ID
Reference ref = fac.newReference(
"#invoice-001", // Reference to element with this ID
fac.newDigestMethod(DigestMethod.SHA256, null),
null, // No transforms
null, // No type
null // No ID
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Generate key pair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
// Create KeyInfo
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
// Create signature as a separate element
Element sigParent = doc.createElement("Signature");
doc.getDocumentElement().appendChild(sigParent);
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), sigParent);
dsc.setIdAttributeNS(invoiceId, null, "Id");
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Save
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
}
public static void verifySignature() throws Exception {
System.out.println("\n--- Signature Verification ---");
// Load a signed document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder()
.parse(new FileInputStream("signed-invoice.xml"));
// Find Signature element
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
System.out.println("No signature found");
return;
}
Element sigElement = (Element) nl.item(0);
// Create validation context
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
DOMValidateContext valContext = new DOMValidateContext(new KeySelector() {
@Override
public KeySelectorResult select(KeyInfo keyInfo,
Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context) {
try {
// Extract public key from KeyInfo
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = (KeyValue) keyInfo.getContent().get(0);
PublicKey publicKey = kv.getPublicKey();
return () -> publicKey;
} catch (KeyException e) {
throw new RuntimeException(e);
}
}
}, sigElement);
// Validate signature
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
boolean valid = signature.validate(valContext);
System.out.println("Signature valid: " + valid);
// Check core validation status
if (!valid) {
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("Signature validation status: " + sv);
List<Reference> refs = signature.getSignedInfo().getReferences();
for (int i = 0; i < refs.size(); i++) {
boolean refValid = refs.get(i).validate(valContext);
System.out.println("Reference[" + i + "] validity status: " + refValid);
}
}
}
public static void signWithCertificate() throws Exception {
System.out.println("\n--- Signing with X.509 Certificate ---");
// Load keystore (in practice, use proper keystore)
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("keystore.jks"), "password".toCharArray());
// Get private key and certificate
String alias = "mykey";
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "password".toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
// Create document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
Element root = doc.createElement("Contract");
root.setAttribute("id", "CT-1001");
root.setTextContent("This is a legal contract...");
doc.appendChild(root);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create reference
Reference ref = fac.newReference(
"",
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, null)),
null,
null
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Create KeyInfo with certificate
KeyInfoFactory kif = fac.getKeyInfoFactory();
X509Data x509Data = kif.newX509Data(Collections.singletonList(cert));
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data));
// Sign
DOMSignContext dsc = new DOMSignContext(privateKey, doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Save
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
System.out.println("\nDocument signed with certificate");
}
}
Advanced XML Signature Features with Apache Santuario
1. Multiple References and Transforms
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.transforms.params.XPathContainer;
public class AdvancedXMLSignature {
static {
// Initialize Apache XML Security
org.apache.xml.security.Init.init();
}
public static void createAdvancedSignature() throws Exception {
System.out.println("\n--- Advanced Signature with Multiple References ---");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
// Create complex document with multiple elements
Element root = doc.createElement("Contract");
Element parties = doc.createElement("Parties");
Element party1 = doc.createElement("Party");
party1.setAttribute("id", "party-1");
party1.setTextContent("Acme Corp");
parties.appendChild(party1);
Element party2 = doc.createElement("Party");
party2.setAttribute("id", "party-2");
party2.setTextContent("Beta Ltd");
parties.appendChild(party2);
root.appendChild(parties);
Element terms = doc.createElement("Terms");
terms.setAttribute("id", "terms");
terms.setTextContent("Payment terms: Net 30");
root.appendChild(terms);
Element amounts = doc.createElement("Amounts");
Element amount1 = doc.createElement("Amount");
amount1.setAttribute("id", "amt-1");
amount1.setTextContent("1000.00");
amounts.appendChild(amount1);
Element amount2 = doc.createElement("Amount");
amount2.setAttribute("id", "amt-2");
amount2.setTextContent("2500.00");
amounts.appendChild(amount2);
root.appendChild(amounts);
doc.appendChild(root);
// Mark elements with IDs
party1.setIdAttribute("id", true);
party2.setIdAttribute("id", true);
terms.setIdAttribute("id", true);
amount1.setIdAttribute("id", true);
amount2.setIdAttribute("id", true);
// Create XMLSignature object
XMLSignature sig = new XMLSignature(doc, null,
XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256);
// Add signature element to document
root.appendChild(sig.getElement());
// Create transforms for each reference
Transforms transforms1 = new Transforms(doc);
transforms1.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms1.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);
// Reference 1: Sign both parties
sig.addDocument("#party-1", transforms1, Constants.ALGO_ID_DIGEST_SHA256);
sig.addDocument("#party-2", transforms1, Constants.ALGO_ID_DIGEST_SHA256);
// Reference 2: Sign terms with XPath filter
Transforms transforms2 = new Transforms(doc);
transforms2.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
XPathContainer xpath = new XPathContainer(doc);
xpath.setXPath("//Terms");
transforms2.addTransform(Transforms.TRANSFORM_XPATH, xpath.getElement());
sig.addDocument("", transforms2, Constants.ALGO_ID_DIGEST_SHA256);
// Reference 3: Sign amounts
Transforms transforms3 = new Transforms(doc);
transforms3.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms3.addTransform(Transforms.TRANSFORM_BASE64_DECODE);
sig.addDocument("#amt-1", transforms3, Constants.ALGO_ID_DIGEST_SHA256);
sig.addDocument("#amt-2", transforms3, Constants.ALGO_ID_DIGEST_SHA256);
// Generate key and sign
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
sig.sign(kp.getPrivate());
// Add KeyInfo
sig.addKeyInfo(kp.getPublic());
// Output
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
System.out.println("\nAdvanced signature created successfully");
}
}
2. Signature with XPath Filtering
public class XPathSignatureExample {
public static void signSelectedElements() throws Exception {
System.out.println("\n--- XPath Filtered Signature ---");
Document doc = createDocument();
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create XPath filter to sign only specific elements
String xpathExpr = "//Invoice/Items/Item[@type='taxable']";
TransformParameterSpec xpathSpec = new TransformParameterSpec(
Collections.singletonMap("XPath", xpathExpr)
);
// Create reference with XPath transform
Reference ref = fac.newReference(
"",
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(
fac.newTransform(Transform.XPATH, xpathSpec)
),
null,
null
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Generate keys and sign
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(),
doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Output
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
}
private static Document createDocument() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
Element invoice = doc.createElement("Invoice");
invoice.setAttribute("id", "INV-001");
Element items = doc.createElement("Items");
// Taxable item
Element item1 = doc.createElement("Item");
item1.setAttribute("type", "taxable");
item1.setTextContent("Computer");
items.appendChild(item1);
// Non-taxable item
Element item2 = doc.createElement("Item");
item2.setAttribute("type", "non-taxable");
item2.setTextContent("Food");
items.appendChild(item2);
invoice.appendChild(items);
doc.appendChild(invoice);
return doc;
}
}
Working with X.509 Certificates
1. Certificate Chain Validation
public class CertificateValidation {
public static void validateSignatureChain(String signedFile) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(signedFile));
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Find signature
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
Element sigElement = (Element) nl.item(0);
// Create validation context with certificate validation
DOMValidateContext valContext = new DOMValidateContext(
new X509KeySelector(), sigElement);
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate signature
boolean valid = signature.validate(valContext);
System.out.println("Signature valid: " + valid);
if (valid) {
// Validate certificate chain
X509Certificate cert = extractCertificate(signature);
validateCertificateChain(cert);
}
}
private static X509Certificate extractCertificate(XMLSignature signature)
throws Exception {
KeyInfo keyInfo = signature.getKeyInfo();
if (keyInfo != null) {
for (Object content : keyInfo.getContent()) {
if (content instanceof X509Data) {
X509Data x509Data = (X509Data) content;
for (Object data : x509Data.getContent()) {
if (data instanceof X509Certificate) {
return (X509Certificate) data;
}
}
}
}
}
throw new SecurityException("No certificate found");
}
private static void validateCertificateChain(X509Certificate cert) {
try {
// Check expiration
cert.checkValidity();
System.out.println("Certificate is within validity period");
// In production, build and validate certificate chain
// using PKIXCertPathValidator
System.out.println("Certificate subject: " + cert.getSubjectDN());
System.out.println("Certificate issuer: " + cert.getIssuerDN());
System.out.println("Valid from: " + cert.getNotBefore());
System.out.println("Valid to: " + cert.getNotAfter());
} catch (Exception e) {
System.err.println("Certificate validation failed: " + e.getMessage());
}
}
static class X509KeySelector extends KeySelector {
@Override
public KeySelectorResult select(KeyInfo keyInfo,
Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
for (Object content : keyInfo.getContent()) {
if (content instanceof X509Data) {
X509Data x509Data = (X509Data) content;
for (Object data : x509Data.getContent()) {
if (data instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) data;
return () -> cert.getPublicKey();
}
}
}
}
throw new KeySelectorException("No key found");
}
}
}
2. PKCS#12 Keystore Integration
public class PKCS12Signature {
public static void signWithPKCS12(String p12File, String password) throws Exception {
// Load PKCS#12 keystore
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(p12File)) {
ks.load(fis, password.toCharArray());
}
// Get private key and certificate
Enumeration<String> aliases = ks.aliases();
String alias = null;
while (aliases.hasMoreElements()) {
String a = aliases.nextElement();
if (ks.isKeyEntry(a)) {
alias = a;
break;
}
}
if (alias == null) {
throw new SecurityException("No private key found");
}
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
// Create document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
Element root = doc.createElement("SignedDocument");
root.setTextContent("This document is signed with PKCS#12 certificate");
doc.appendChild(root);
// Create signature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
Reference ref = fac.newReference(
"",
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, null)),
null,
null
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Add certificate chain to KeyInfo
KeyInfoFactory kif = fac.getKeyInfoFactory();
List<Object> x509Content = new ArrayList<>();
for (Certificate cert : chain) {
x509Content.add(cert);
}
X509Data x509Data = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data));
// Sign
DOMSignContext dsc = new DOMSignContext(privateKey, doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Output
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(System.out));
System.out.println("\nDocument signed with PKCS#12 certificate");
}
}
Web Services Security (WS-Security)
1. SOAP Message Signing
public class SOAPSignature {
public static void signSOAPMessage() throws Exception {
System.out.println("\n--- SOAP Message Signing ---");
// Create SOAP message
MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage soapMsg = msgFactory.createMessage();
SOAPPart soapPart = soapMsg.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPBody body = envelope.getBody();
// Add business data
QName bodyName = new QName("http://example.com/", "ProcessOrder");
SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
bodyElement.addChildElement("OrderID").setValue("ORD-12345");
bodyElement.addChildElement("Amount").setValue("500.00");
soapMsg.saveChanges();
// Extract DOM document
Document doc = soapPart.getEnvelope().getOwnerDocument();
// Create signature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Sign the body (with WS-Security requirements)
Reference ref = fac.newReference(
"#" + bodyElement.getElementName().getLocalName(),
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(
fac.newTransform(Transform.EXCLUSIVE, null)
),
"http://www.w3.org/2000/09/xmldsig#object",
null
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
// Generate keys
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
// Add signature to SOAP header (WS-Security)
SOAPHeader header = envelope.getHeader();
if (header == null) {
header = envelope.addHeader();
}
Element wsseHeader = doc.createElementNS(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"wsse:Security"
);
header.appendChild(wsseHeader);
// Sign
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), wsseHeader);
dsc.setDefaultNamespacePrefix("ds");
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Output signed SOAP message
soapMsg.writeTo(System.out);
}
}
Performance and Best Practices
1. Caching and Performance Optimization
public class SignaturePerformanceOptimizer {
// Cache key selectors and factories
private static final Map<String, KeySelector> KEY_SELECTOR_CACHE = new ConcurrentHashMap<>();
private static final XMLSignatureFactory SIGNATURE_FACTORY =
XMLSignatureFactory.getInstance("DOM");
// Reusable transformer
private static final TransformerFactory TRANSFORMER_FACTORY =
TransformerFactory.newInstance();
// Thread-local document builders
private static final ThreadLocal<DocumentBuilder> DOCUMENT_BUILDER =
ThreadLocal.withInitial(() -> {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
return dbf.newDocumentBuilder();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
public static Document createNewDocument() {
return DOCUMENT_BUILDER.get().newDocument();
}
public static void validateSignatureBatch(List<Document> signedDocs)
throws Exception {
// Pre-parse all documents to cache canonicalization results
List<DOMValidateContext> contexts = new ArrayList<>();
for (Document doc : signedDocs) {
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() > 0) {
Element sigElement = (Element) nl.item(0);
DOMValidateContext ctx = new DOMValidateContext(
new CachingKeySelector(), sigElement);
contexts.add(ctx);
}
}
// Validate in parallel
contexts.parallelStream().forEach(ctx -> {
try {
XMLSignature signature = SIGNATURE_FACTORY.unmarshalXMLSignature(ctx);
signature.validate(ctx);
} catch (Exception e) {
System.err.println("Validation failed: " + e.getMessage());
}
});
}
static class CachingKeySelector extends KeySelector {
@Override
public KeySelectorResult select(KeyInfo keyInfo,
Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
// Cache based on key info content
String keyId = generateKeyId(keyInfo);
return () -> KEY_SELECTOR_CACHE.computeIfAbsent(keyId, k -> {
try {
return extractPublicKey(keyInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private String generateKeyId(KeyInfo keyInfo) {
return Integer.toHexString(keyInfo.hashCode());
}
private PublicKey extractPublicKey(KeyInfo keyInfo) throws Exception {
for (Object content : keyInfo.getContent()) {
if (content instanceof KeyValue) {
return ((KeyValue) content).getPublicKey();
}
}
throw new KeySelectorException("No key found");
}
}
}
2. Security Best Practices
public class SignatureSecurity {
// 1. Always validate signatures before processing
public static void processSignedDocument(Document doc) throws Exception {
// Verify signature first
if (!verifySignature(doc)) {
throw new SecurityException("Invalid signature");
}
// Only then process the document
processDocument(doc);
}
// 2. Check for multiple signatures
public static boolean hasValidSignatures(Document doc) throws Exception {
NodeList signatures = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (signatures.getLength() == 0) {
return false; // No signature
}
// All signatures must be valid
for (int i = 0; i < signatures.getLength(); i++) {
if (!validateSingleSignature((Element) signatures.item(i))) {
return false;
}
}
return true;
}
// 3. Prevent XML signature wrapping attacks
public static boolean isSignatureWrappingAttack(Document doc) {
// Check for duplicate IDs
Set<String> ids = new HashSet<>();
findIds(doc.getDocumentElement(), ids);
// If we have duplicate IDs, might be wrapping attack
return ids.size() != countElementsWithId(doc);
}
private static void findIds(Element element, Set<String> ids) {
if (element.hasAttribute("Id") || element.hasAttribute("ID") ||
element.hasAttribute("id")) {
String id = element.getAttribute("Id");
if (id.isEmpty()) id = element.getAttribute("ID");
if (id.isEmpty()) id = element.getAttribute("id");
if (ids.contains(id)) {
throw new SecurityException("Duplicate ID detected: " + id);
}
ids.add(id);
}
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i) instanceof Element) {
findIds((Element) children.item(i), ids);
}
}
}
// 4. Use secure algorithms
public static final Set<String> SECURE_ALGORITHMS = Set.of(
SignatureMethod.RSA_SHA256,
SignatureMethod.RSA_SHA384,
SignatureMethod.RSA_SHA512,
SignatureMethod.ECDSA_SHA256,
"http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
);
public static boolean isSecureAlgorithm(String algorithmURI) {
return SECURE_ALGORITHMS.contains(algorithmURI);
}
}
Integration with XML Processing Frameworks
1. JAXB Integration
public class JAXBSignatureIntegration {
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class PurchaseOrder {
@XmlAttribute
private String id;
private String customer;
private BigDecimal amount;
@XmlElement(name = "Signature", namespace = "http://www.w3.org/2000/09/xmldsig#")
private Element signature;
// Getters and setters
}
public static PurchaseOrder signPurchaseOrder(PurchaseOrder po, PrivateKey key)
throws Exception {
// Marshal to DOM
JAXBContext context = JAXBContext.newInstance(PurchaseOrder.class);
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
context.createMarshaller().marshal(po, doc);
// Sign
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
Reference ref = fac.newReference(
"",
fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, null)),
null,
null
);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref)
);
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(
kif.newKeyValue(key.getPublic())
));
DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
// Unmarshal back with signature
return context.createUnmarshaller()
.unmarshal(doc, PurchaseOrder.class).getValue();
}
}
Conclusion
XML Digital Signatures provide Java developers with a robust, standardized mechanism for ensuring the integrity and authenticity of XML documents. The Java XML Digital Signature API (JSR 105) combined with powerful extensions like Apache Santuario enables comprehensive signature creation and validation capabilities for enterprise applications.
Whether implementing e-invoicing systems, securing web services, or building document management solutions, XML-DSig offers the flexibility to sign entire documents or specific elements, support multiple signatures, and integrate with X.509 certificate infrastructures. By following security best practices—validating signatures before processing, preventing wrapping attacks, using secure algorithms—developers can build systems that meet the highest security and compliance requirements.
Java Programming Intermediate Topics – Modifiers, Loops, Math, Methods & Projects (Related to Java Programming)
Access Modifiers in Java:
Access modifiers control how classes, variables, and methods are accessed from different parts of a program. Java provides four main access levels—public, private, protected, and default—which help protect data and control visibility in object-oriented programming.
Read more: https://macronepal.com/blog/access-modifiers-in-java-a-complete-guide/
Static Variables in Java:
Static variables belong to the class rather than individual objects. They are shared among all instances of the class and are useful for storing values that remain common across multiple objects.
Read more: https://macronepal.com/blog/static-variables-in-java-a-complete-guide/
Method Parameters in Java:
Method parameters allow values to be passed into methods so that operations can be performed using supplied data. They help make methods flexible and reusable in different parts of a program.
Read more: https://macronepal.com/blog/method-parameters-in-java-a-complete-guide/
Random Numbers in Java:
This topic explains how to generate random numbers in Java for tasks such as simulations, games, and random selections. Random numbers help create unpredictable results in programs.
Read more: https://macronepal.com/blog/random-numbers-in-java-a-complete-guide/
Math Class in Java:
The Math class provides built-in methods for performing mathematical calculations such as powers, square roots, rounding, and other advanced calculations used in Java programs.
Read more: https://macronepal.com/blog/math-class-in-java-a-complete-guide/
Boolean Operations in Java:
Boolean operations use true and false values to perform logical comparisons. They are commonly used in conditions and decision-making statements to control program flow.
Read more: https://macronepal.com/blog/boolean-operations-in-java-a-complete-guide/
Nested Loops in Java:
Nested loops are loops placed inside other loops to perform repeated operations within repeated tasks. They are useful for pattern printing, tables, and working with multi-level data.
Read more: https://macronepal.com/blog/nested-loops-in-java-a-complete-guide/
Do-While Loop in Java:
The do-while loop allows a block of code to run at least once before checking the condition. It is useful when the program must execute a task before verifying whether it should continue.
Read more: https://macronepal.com/blog/do-while-loop-in-java-a-complete-guide/
Simple Calculator Project in Java:
This project demonstrates how to create a basic calculator program using Java. It combines input handling, arithmetic operations, and conditional logic to perform simple mathematical calculations.
Read more: https://macronepal.com/blog/simple-calculator-project-in-java/