Java使用PFX密钥库私钥解密W3标准XML遇密钥大小错误求助
XML解密错误:Wrong key size 解决方案
问题背景
需遵循W3标准解密XML响应,加密流程已完成,但解密时抛出密钥尺寸错误,期望解密后得到带数字签名的XML。
加密后的响应XML结构(命名空间已简化):
<xenc:EncryptedData Type="xmlenc#Element" xmlns:xenc="xmlenc#"> <xenc:EncryptionMethod Algorithm="xmlenc#tripledes-cbc"/> <dsig:KeyInfo xmlns:dsig="xmldsig#"> <xenc:EncryptedKey Recipient="name:55bb96ec-..."> <xenc:EncryptionMethod Algorithm="xmlenc#rsa-1_5"/> <dsig:KeyInfo> <dsig:KeyName>55bb96ec-....</dsig:KeyName> </dsig:KeyInfo> <xenc:CipherData> <xenc:CipherValue>nO6Hic8FxebFy.....</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedKey> </dsig:KeyInfo> <xenc:CipherData> <xenc:CipherValue>j9v2IznqpwCunQiZG....</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData>
抛出错误:
Exception in thread "main" java.security.InvalidKeyException: Wrong key size at com.sun.crypto.provider.DESedeCrypt.init(DESedeCrypt.java:69) at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:93) at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591) at com.sun.crypto.provider.DESedeCipher.engineInit(DESedeCipher.java:197) at javax.crypto.Cipher.implInit(Cipher.java:809) at javax.crypto.Cipher.chooseProvider(Cipher.java:867) at javax.crypto.Cipher.init(Cipher.java:1399) at javax.crypto.Cipher.init(Cipher.java:1330) at testDecrypt.main(testDecrypt.java:90)
现有手动解密代码:
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; //import com.sun.org.apache.xml.internal.security.utils.Base64; public class testDecrypt { static { org.apache.xml.security.Init.init(); } public static void main(String[] args) throws Exception { String xmlInput = "ENCRYPTED XML FOLDER\\XENC FILE.xml"; String keyStorePW = "PFX password"; String keyStore = "PFX FILE FOLDER\\PFX File.pfx"; //build document from input xml file DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(xmlInput); //load the Private Key from Keystore KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(new FileInputStream(keyStore), keyStorePW.toCharArray()); String alias = ks.aliases().nextElement(); PrivateKey privKey = (PrivateKey)ks.getKey(alias, keyStorePW.toCharArray()); System.out.println("Private Key: "+privKey); //Get the encrypted Key and data element from input xml NodeList cipherValueNodes = doc.getElementsByTagName("xenc:CipherValue"); Element encKey = null; Element encData = null; for(int i = 0; i < cipherValueNodes.getLength(); i++) { if (i == 0) { encKey = (Element)cipherValueNodes.item(i); } else { encData = (Element)cipherValueNodes.item(i); } } System.out.println("Encrypted key: "+encKey.getTextContent()); System.out.println("Encrypted data: "+encData.getTextContent()); //Start decryption of encrypted Key using the private key Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, privKey); byte[] encryptedKeyBytes = Base64.getDecoder().decode(encKey.getTextContent()); byte[] decryptedKey = cipher.doFinal(encryptedKeyBytes); DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(decryptedKey); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede"); SecretKey key = keyFactory.generateSecret(desEdeKeySpec); System.out.println("Key size:"+key.getEncoded().length); //decrypt the encrypted data element using the secrek key int ivLen = 8; byte[] ivBytes = new byte[ivLen]; byte[] encryptedDataBytes = Base64.getDecoder().decode(encData.getTextContent()); System.arraycopy(encryptedDataBytes, 0, ivBytes, 0, ivLen); IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); Cipher cipherData = Cipher.getInstance("DESede/CBC/NoPadding"); cipherData.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); String decryptedData = new String(cipher.doFinal(encryptedDataBytes, ivLen, encryptedDataBytes.length - ivLen)); System.out.println("DEcrypted Data: "+decryptedData); //write the Decrypted XML File encryptionFile = new File("FILE OUTPUT DIRECTORY\\decryptedInfo2.xml"); FileOutputStream f = new FileOutputStream(encryptionFile); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(f); transformer.transform(source, result); f.close(); System.out.println( "Wrote document containing encrypted data to " + encryptionFile.toURI().toURL().toString() ); } }
错误原因分析
- RSA填充方式错误:XMLenc规范中
xmlenc#rsa-1_5对应RSA/ECB/PKCS1Padding,而非代码中使用的NoPadding。错误的填充方式会导致解密出的3DES密钥长度不符合24字节要求,触发密钥尺寸错误。 - 手动处理IV和密文不符合规范:XMLenc的3DES-CBC加密中,IV通常会被包含在加密数据的结构中(或由算法自动处理),手动拆分前8字节作为IV的方式可能不符合实际加密时的处理逻辑。
- 未使用专业XMLenc库:手动解析DOM并执行加解密容易遗漏规范细节,应该用Apache Santuario(xmlsec)库直接处理整个EncryptedData元素。
修正后的解决方案
使用Apache Santuario库处理XML解密,步骤如下:
1. 添加依赖(Maven)
<dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>2.4.3</version> </dependency>
2. 修正后的解密代码
import org.apache.xml.security.encryption.XMLCipher; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.keyresolver.KeyResolver; import org.apache.xml.security.keys.keyresolver.implementations.RSAKeyValueResolver; import org.apache.xml.security.keys.keyresolver.implementations.X509CertificateResolver; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; public class XmlDecryptor { static { org.apache.xml.security.Init.init(); // 注册密钥解析器 KeyResolver.register(new RSAKeyValueResolver()); KeyResolver.register(new X509CertificateResolver()); } public static void main(String[] args) throws Exception { String encryptedXmlPath = "ENCRYPTED XML FOLDER\\XENC FILE.xml"; String pfxPath = "PFX FILE FOLDER\\PFX File.pfx"; String pfxPassword = "PFX password"; String outputPath = "FILE OUTPUT DIRECTORY\\decryptedInfo.xml"; // 加载PFX证书库,获取私钥和证书 KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream(pfxPath), pfxPassword.toCharArray()); String alias = keyStore.aliases().nextElement(); PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, pfxPassword.toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); // 解析加密后的XML文档 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new File(encryptedXmlPath)); // 获取EncryptedData元素 Element encryptedDataElement = (Element) doc.getElementsByTagNameNS(XMLCipher.XMLENC_NS, "EncryptedData").item(0); // 初始化XMLCipher,用于解密 XMLCipher xmlCipher = XMLCipher.getInstance(); xmlCipher.init(XMLCipher.DECRYPT_MODE, null); // 设置密钥解析器,用私钥解密EncryptedKey xmlCipher.setKEK(privateKey); // 执行解密,替换EncryptedData为原始内容 xmlCipher.doFinal(doc, encryptedDataElement); // 输出解密后的XML TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(new FileOutputStream(outputPath)); transformer.transform(source, result); System.out.println("解密完成,输出文件:" + outputPath); } }
代码说明
- 直接使用
XMLCipher类处理整个EncryptedData元素,自动遵循XMLenc规范解析加密算法、密钥信息 - 通过
setKEK传入私钥,自动解密EncryptedKey得到3DES密钥 - 无需手动拆分IV和密文,库会自动处理算法细节
- 注册密钥解析器,确保能正确识别XML中的密钥信息
内容的提问来源于stack exchange,提问作者juniorDev
相关产品推荐
相关产品推荐

