You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题分析与修复

核心问题

  1. 重复KeyInfo节点:调用xmlsec.template.add_encrypted_key时,xmlsec会自动为EncryptedKey生成一个默认的KeyInfo节点,直接追加自定义的KeyInfo会导致XML结构中出现两个KeyInfo,破坏加密模板的合法性,引发加密失败。
  2. 未定义的命名空间依赖:原代码中DS_NS、WSSE_NS、namespaces、ns函数均未定义,会导致create_key_info_ski生成的XML节点命名空间错误,进一步干扰加密流程。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:53:09