PAYATHON 2026

EBICS 3.0 H005 发送 INI 时返回 090004

数据小许

结论

090004 Invalid Order Data Format 通常表示服务器已经收到 ebicsUnsecuredRequest,但无法按照 INI 的 OrderData 结构解析解压后的 SignaturePubKeyOrderData。

从现有内容看,外层 Base64 和压缩数据以 eJ... 开头,符合常见的 zlib 数据特征,问题大概率不在这里。建议先检查以下内容:

  1. SignaturePubKeyOrderData 必须使用 S001 命名空间,不能沿用 H005 外层报文的命名空间。
  2. X509Data、X509Certificate、X509IssuerSerial 等元素必须属于 XMLDSIG 命名空间。
  3. 必须先将完整的 UTF-8 XML 做 zlib/DEFLATE 压缩,再进行 Base64 编码。不能只压缩证书,也不能使用 GZIP。
  4. PartnerID 和 UserID 必须与外层请求完全一致,包括大小写。
  5. X509IssuerName 必须取自证书的 issuer,不能用手工拼出的 subject 代替。序列号必须是十进制正整数。
  6. 当前证书已经过期。如果仍在使用这张测试证书,即使修正了 OrderData 格式,银行也可能在后续校验中拒绝请求。

另外,base64 encrypted 这个说法并不准确。Base64 是编码,zlib 是压缩,两者都不是加密。

正确的两层 XML 命名空间

外层 H005 请求使用:

xmlns="urn:org:ebics:H005"

INI 的内层 OrderData 通常使用:

xmlns="http://www.ebics.org/S001"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"

这里很容易写错。不要把内层文档写成:

<SignaturePubKeyOrderData xmlns="urn:org:ebics:H005">

服务器会按照 S001 schema 解析 INI OrderData。命名空间一旦错误,即使元素名称看起来完全正确,schema 仍会把根元素判定为无效,并返回 090004。

推荐的 SignaturePubKeyOrderData

下面是证书模式下的结构示例,元素顺序也应保持不变:

<?xml version="1.0" encoding="UTF-8"?>
<SignaturePubKeyOrderData
    xmlns="http://www.ebics.org/S001"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <SignaturePubKeyInfo>
        <ds:X509Data>
            <ds:X509IssuerSerial>
                <ds:X509IssuerName>L=Sydhavn,C=DK,CN=*.example.com,O=Claus AS,OU=eng</ds:X509IssuerName>
                <ds:X509SerialNumber>1697464111985</ds:X509SerialNumber>
            </ds:X509IssuerSerial>
            <ds:X509Certificate>MIIDLjCCAhagAwIBAgIGAYs4vtNx...</ds:X509Certificate>
        </ds:X509Data>
        <SignatureVersion>A005</SignatureVersion>
    </SignaturePubKeyInfo>
    <PartnerID>DEPartner</PartnerID>
    <UserID>DeSystemUser</UserID>
</SignaturePubKeyOrderData>

需要注意:

  • ds:X509Certificate 中只能放入 DER 证书的标准 Base64。
  • 不要包含 -----BEGIN CERTIFICATE----- 和 -----END CERTIFICATE-----。
  • 最稳妥的做法是不在证书 Base64 中插入空格或换行。
  • SignatureVersion 不属于 ds 命名空间。
  • PartnerID 和 UserID 也属于默认的 S001 命名空间。
  • XML 标签区分大小写,例如不能把 UserID 写成 UserId。
  • 不要将 XML 编码为 UTF-16,却在声明中写 UTF-8。

如果银行的实现或其 EBICS profile 不接受 X509IssuerSerial,可以尝试只保留 ds:X509Certificate,但应先使用银行提供的 S001 XSD 验证。不同银行对可选 XMLDSIG 子元素的支持不一定完全相同。

Issuer Name 可能存在的问题

这张证书的 issuer 和 subject 相同,因为它是自签名证书。序列号 1697464111985 看起来也与证书中编码的序列号一致。

不过,DN 的字符串序列化方式经常引起兼容问题。不同 API 可能返回:

L=Sydhavn,C=DK,CN=*.example.com,O=Claus AS,OU=eng

也可能按相反顺序返回:

OU=eng,O=Claus AS,CN=*.example.com,C=DK,L=Sydhavn

有些库还会加入空格、转义字符,或者使用不同的属性名称。因此,不要根据证书页面手工拼接 DN,而应直接从证书的 issuer principal 生成 XMLDSIG 所需的值。

在 Java 中,可以先检查实际生成的内容:

X509Certificate certificate = ...;

String issuer = certificate
        .getIssuerX500Principal()
        .getName(X500Principal.RFC2253);

String serial = certificate
        .getSerialNumber()
        .toString();

System.out.println(issuer);
System.out.println(serial);

需要留意,RFC 2253 字符串在语义上等价,不代表所有银行服务器都能正确处理每一种等价写法。如果怀疑问题出在 DN 兼容性上,可以请银行提供一个已被接受的请求格式,或者在 schema 允许时只发送 ds:X509Certificate。

正确的压缩和 Base64 顺序

OrderData 应按以下顺序处理:

完整 SignaturePubKeyOrderData XML
        ↓ UTF-8 编码
zlib/DEFLATE 压缩
        ↓
标准 Base64 编码
        ↓
写入外层 <OrderData>

以下做法都不正确:

  • 使用 GZIP 压缩;
  • 只压缩证书;
  • 先对 XML 做 Base64 编码,再压缩;
  • 使用 Base64 URL-safe alphabet;
  • 将 PEM 文本直接放入 OrderData;
  • 对压缩结果做两次 Base64 编码。

Java 示例:

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

public static String createOrderData(String xml) throws Exception {
    byte[] xmlBytes = xml.getBytes(StandardCharsets.UTF_8);

    ByteArrayOutputStream compressed = new ByteArrayOutputStream();

    // nowrap=false,输出带 zlib wrapper 的数据。
    Deflater deflater = new Deflater(
            Deflater.DEFAULT_COMPRESSION,
            false
    );

    try (DeflaterOutputStream stream =
                 new DeflaterOutputStream(compressed, deflater)) {
        stream.write(xmlBytes);
    }

    return Base64.getEncoder().encodeToString(compressed.toByteArray());
}

zlib 压缩结果经过 Base64 编码后,经常以 eJx... 或 eJy... 开头。因此,仅凭给出的 eJx9...,无法认定压缩方式有误。

不要使用:

new GZIPOutputStream(...)

在没有确认银行要求的情况下,也不要使用:

new Deflater(level, true)

这里的 true 会生成不带 zlib wrapper 的 raw DEFLATE 流,部分 EBICS 服务端无法识别。

必须做一次反向验证

发送前,客户端应自行完成一次 Base64 解码和解压,并对结果进行 XML schema 验证。这样可以很快判断问题出在传输编码还是 XML 内容。

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.InflaterInputStream;

public static String decodeOrderData(String orderData) throws Exception {
    byte[] compressed = Base64.getMimeDecoder().decode(orderData);

    try (InflaterInputStream input =
                 new InflaterInputStream(new ByteArrayInputStream(compressed))) {
        return new String(input.readAllBytes(), StandardCharsets.UTF_8);
    }
}

至少应进行以下检查:

String encoded = createOrderData(signaturePubKeyOrderData);
String decoded = decodeOrderData(encoded);

if (!decoded.equals(signaturePubKeyOrderData)) {
    throw new IllegalStateException("OrderData round-trip failed");
}

然后使用银行实际采用的 S001 XSD 校验 decoded。只检查 XML 是否 well-formed 还不够,元素顺序、命名空间和必填字段等问题只有通过 schema 校验才能发现。

外层 H005 请求示例

下面的 H005 INI 请求可作为结构参考。OrderData 必须由前面的程序动态生成,不能直接复制占位内容:

<?xml version="1.0" encoding="UTF-8"?>
<ebicsUnsecuredRequest
    xmlns="urn:org:ebics:H005"
    Version="H005"
    Revision="1">
    <header authenticate="true">
        <static>
            <HostID>DEHost</HostID>
            <PartnerID>DEPartner</PartnerID>
            <UserID>DeSystemUser</UserID>
            <Product Language="en">Ebics server</Product>
            <OrderDetails>
                <AdminOrderType>INI</AdminOrderType>
            </OrderDetails>
            <SecurityMedium>0000</SecurityMedium>
        </static>
        <mutable/>
    </header>
    <body>
        <DataTransfer>
            <OrderData>BASE64_OF_ZLIB_COMPRESSED_S001_XML</OrderData>
        </DataTransfer>
    </body>
</ebicsUnsecuredRequest>

这里是两个彼此独立的根文档:

  • 外层:urn:org:ebics:H005
  • 解压后的内层:http://www.ebics.org/S001

外层使用 H005,并不意味着内层 INI key-management XML 也应放入 H005 命名空间。

证书本身还需检查什么

给出的证书使用 RSA 2048 位公钥和 SHA-256 签名。这些基本属性并没有直接暴露出能够解释 090004 的密码学错误,但证书本身仍有几个问题需要检查:

  • 有效期为 2023-10-15 至 2024-10-14,目前已经过期。
  • 它看起来是一张为通配符域名生成的 TLS 风格证书,不一定符合银行针对 EBICS 用户签名证书制定的策略。
  • 示例中没有展示证书的 KeyUsage、ExtendedKeyUsage 等扩展。银行是否要求这些扩展,取决于其 EBICS profile。
  • INI 中提交的证书必须对应客户端实际持有的 A005 私钥。
  • 证书中的 RSA 公钥不能与随后 HIA 使用的认证密钥或加密密钥混淆。

如果证书不符合银行策略,服务器也可能返回较为笼统的错误码。不过,如果解压后的 XML 连 schema 校验都无法通过,应先处理 XML 格式问题。

建议的排查顺序

  1. 对当前 OrderData 进行 Base64 解码。
  2. 使用带 zlib wrapper 的 inflater 解压。
  3. 确认解压结果是 UTF-8 XML,并且没有多余的 PEM 内容或二次 Base64。
  4. 检查根元素的默认命名空间是否为 http://www.ebics.org/S001。
  5. 检查所有 X509* 元素是否带有 ds: 前缀,并绑定到 XMLDSIG 命名空间。
  6. 使用 S001 XSD 校验内层 XML。
  7. 使用 H005 XSD 校验外层 XML。
  8. 通过证书 API 读取 issuer 和 serial,不要手工构造。
  9. 换用未过期且符合银行证书策略的 A005 证书。
  10. 如果本地两层 schema 校验都能通过,再请银行确认其接受的压缩封装、证书模式和 DN 表示方式。

根据现有信息,最需要先核对的是 S001/XMLDSIG 命名空间、元素顺序和 issuer DN 的生成方式。以 eJx... 开头的 Base64 压缩数据反而不太像首要故障点。

备注:内容仅供参考。