Python实现带X509SubjectKeyIdentifier的SOAP XML加密报错求助
SOAP Body加密问题:启用X509SubjectKeyIdentifier时xmlsec加密失败
我需要对SOAP XML文档的Body部分进行加密,该操作在SoapUI中可正常执行:使用证书,将Subject Key Identifier作为密钥标识类型,采用AES256-CBC编码与RSA 1_5加密算法,并勾选创建加密密钥选项。我尝试用以下Python代码复现该配置:
from zeep.wsse.utils import base64 from zeep.wsse.utils import get_security_header import xmlsec from lxml import etree # 注意:原代码中未定义的命名空间和工具函数需补充 # SOAP_NS、DS_NS、WSSE_NS、namespaces、ns函数需提前定义 # extract_ski函数需实现 def encrypt(envelope, certfile): """Encrypt body contents of given SOAP envelope using given X509 cert.""" doc = etree.fromstring(envelope) security = get_security_header(doc) # Create a keys manager and load the cert into it. manager = xmlsec.KeysManager() key = xmlsec.Key.from_file(certfile, xmlsec.KeyFormat.CERT_PEM, None) manager.add_key(key) body = doc.find(ns(SOAP_NS, 'Body')) enc_data = xmlsec.template.encrypted_data_create( body, xmlsec.constants.TransformAes256Cbc, type=xmlsec.EncryptionType.CONTENT, ns='xenc', ) xmlsec.template.encrypted_data_ensure_cipher_value(enc_data) key_info = xmlsec.template.encrypted_data_ensure_key_info( enc_data, ns='ds') enc_key = xmlsec.template.add_encrypted_key( key_info, xmlsec.constants.TransformRsaPkcs1) #enc_key.append(create_key_info_ski(certfile)) xmlsec.template.encrypted_data_ensure_cipher_value(enc_key) enc_ctx = xmlsec.EncryptionContext(manager) # Generate a per-session AES key (will be encrypted using the cert). enc_ctx.key = xmlsec.Key.generate( xmlsec.constants.KeyDataAes, 256, xmlsec.constants.KeyDataTypeSession) # Ask XMLSec to actually do the encryption. enc_data = enc_ctx.encrypt_xml(enc_data, body) # Move the EncryptedKey node up into the wsse:Security header. security.append(enc_key) # Create DataReference element add_data_reference(enc_key, enc_data) enc_data.remove(key_info) return doc def create_key_info_ski(certfile): key_info = etree.Element(f"{{{DS_NS}}}KeyInfo", nsmap=namespaces) security_token_reference = etree.Element(f"{{{WSSE_NS}}}SecurityTokenReference") key_identifier = etree.Element(f"{{{WSSE_NS}}}KeyIdentifier", ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier", EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary") key_identifier.text = extract_ski(certfile) security_token_reference.append(key_identifier) key_info.append(security_token_reference) return key_info
当不使用X509SubjectKeyIdentifier时(即注释掉#enc_key.append(create_key_info_ski(certfile))行),一切正常;但启用该行时会报错:
xmlsec.Error: (1, 'failed to encrypt xml')
请问密钥使用是否正确?我遗漏了什么?
编辑补充
输入信封示例:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:wse="http://.../wse"> <soap:Header/> <soap:Body> <wse:GetDateTime/> </soap:Body> </soap:Envelope>
预期输出:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:wse="http://.../wse"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <xenc:EncryptedKey Id="EK-ADF72CB9C6AC5D5A531716899228423321" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <wsse:SecurityTokenReference> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">...</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>...</xenc:CipherValue> </xenc:CipherData> <xenc:ReferenceList> <xenc:DataReference URI="#ED-ADF72CB9C6AC5D5A531716899228424322"/> </xenc:ReferenceList> </xenc:EncryptedKey> </wsse:Security> </soap:Header> <soap:Body> <xenc:EncryptedData Id="ED-ADF72CB9C6AC5D5A531716899228424322" Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"> <wsse:Reference URI="#EK-ADF72CB9C6AC5D5A531716899228423321"/> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>...</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData> </soap:Body> </soap:Envelope>
问题分析与修复
核心问题
- 重复KeyInfo节点:调用
xmlsec.template.add_encrypted_key时,xmlsec会自动为EncryptedKey生成一个默认的KeyInfo节点,直接追加自定义的KeyInfo会导致XML结构中出现两个KeyInfo,破坏加密模板的合法性,引发加密失败。 - 未定义的命名空间依赖:原代码中
DS_NS、WSSE_NS、namespaces、ns函数均未定义,会导致create_key_info_ski生成的XML节点命名空间错误,进一步干扰加密流程。 - SKI提取函数缺失:
extract_ski函数未实现,无法正确获取证书的Subject Key Identifier值。
修复步骤
1. 补充命名空间与工具函数
在代码开头添加以下定义:
SOAP_NS = "http://www.w3.org/2003/05/soap-envelope" DS_NS = "http://www.w3.org/2000/09/xmldsig#" WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" namespaces = { "ds": DS_NS, "wsse": WSSE_NS, "xenc": "http://www.w3.org/2001/04/xmlenc#" } def ns(namespace, tag): """生成带命名空间的XML标签""" return f"{{{namespace}}}{tag}"
2. 实现SKI提取函数
使用cryptography库提取证书的Subject Key Identifier:
from cryptography import x509 from cryptography.hazmat.backends import default_backend import base64 def extract_ski(certfile): """从PEM证书中提取Subject Key Identifier并转为Base64编码""" with open(certfile, 'rb') as f: cert = x509.load_pem_x509_certificate(f.read(), default_backend()) ski_ext = cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) return base64.b64encode(ski_ext.value.digest).decode('utf-8')
3. 修改EncryptedKey的KeyInfo替换逻辑
在encrypt函数中,替换自动生成的KeyInfo而非追加:
enc_key = xmlsec.template.add_encrypted_key( key_info, xmlsec.constants.TransformRsaPkcs1) # 移除xmlsec自动生成的默认KeyInfo existing_key_info = enc_key.find(ns(DS_NS, 'KeyInfo')) if existing_key_info is not None: enc_key.remove(existing_key_info) # 添加自定义的SKI KeyInfo enc_key.append(create_key_info_ski(certfile)) xmlsec.template.encrypted_data_ensure_cipher_value(enc_key)
4. 调整加密流程顺序
确保在调用enc_ctx.encrypt_xml前完成所有模板修改,因为加密过程依赖最终的合法XML结构。
验证说明
修改后,EncryptedKey节点会包含唯一的、带有SKI标识的KeyInfo,符合预期输出的结构,xmlsec就能正常完成加密流程。
内容的提问来源于stack exchange,提问作者francosy
相关产品推荐
相关产品推荐

