EBICS 3.0 H005 发送 INI 时返回 090004
结论
090004 Invalid Order Data Format 通常表示服务器已经收到 ebicsUnsecuredRequest,但无法按照 INI 的 OrderData 结构解析解压后的 SignaturePubKeyOrderData。
从现有内容看,外层 Base64 和压缩数据以 eJ... 开头,符合常见的 zlib 数据特征,问题大概率不在这里。建议先检查以下内容:
SignaturePubKeyOrderData必须使用 S001 命名空间,不能沿用 H005 外层报文的命名空间。X509Data、X509Certificate、X509IssuerSerial等元素必须属于 XMLDSIG 命名空间。- 必须先将完整的 UTF-8 XML 做 zlib/DEFLATE 压缩,再进行 Base64 编码。不能只压缩证书,也不能使用 GZIP。
PartnerID和UserID必须与外层请求完全一致,包括大小写。X509IssuerName必须取自证书的 issuer,不能用手工拼出的 subject 代替。序列号必须是十进制正整数。- 当前证书已经过期。如果仍在使用这张测试证书,即使修正了 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 格式问题。
建议的排查顺序
- 对当前
OrderData进行 Base64 解码。 - 使用带 zlib wrapper 的 inflater 解压。
- 确认解压结果是 UTF-8 XML,并且没有多余的 PEM 内容或二次 Base64。
- 检查根元素的默认命名空间是否为
http://www.ebics.org/S001。 - 检查所有
X509*元素是否带有ds:前缀,并绑定到 XMLDSIG 命名空间。 - 使用 S001 XSD 校验内层 XML。
- 使用 H005 XSD 校验外层 XML。
- 通过证书 API 读取 issuer 和 serial,不要手工构造。
- 换用未过期且符合银行证书策略的 A005 证书。
- 如果本地两层 schema 校验都能通过,再请银行确认其接受的压缩封装、证书模式和 DN 表示方式。
根据现有信息,最需要先核对的是 S001/XMLDSIG 命名空间、元素顺序和 issuer DN 的生成方式。以 eJx... 开头的 Base64 压缩数据反而不太像首要故障点。
备注:内容仅供参考。
