XML必须在发送方本地加密后传输,服务端无法解密明文;需用AES-GCM+RSA-OAEP组合加密,校验auth_tag,避免DOM解析失真,解密后须UTF-8显式解码。

XML内容必须先加密再传输,不能依赖HTTPS
端到端加密的核心是:XML数据在发送方本地完成加密,密文上传后,服务端无法解密或查看明文——这意味着哪怕你用HTTPS上传,也不能把原始XML直接发过去。常见错误是误以为TLS = E2EE,结果服务端日志、数据库、运维权限都能看到XML明文。
实操要点:
- 加密必须在应用层完成(比如用
crypto.subtle或PyCryptodome),不是由HTTP库或网关代劳 - XML结构本身不影响加密逻辑,但需注意:加密前是否要规范化(如
xml.etree.ElementTree.canonicalize)取决于签名需求;纯加密可跳过 - 推荐用AES-GCM(对称)+ RSA-OAEP(密钥封装)组合:用RSA公钥加密AES随机密钥,AES密钥加密XML字符串(UTF-8编码后)
- 务必校验密文完整性——GCM模式自带认证标签,别用CBC等无认证模式
Python示例:用PyCryptodome加密XML并上传
假设你已有public_key.pem和待传XML字符串xml_str:
from Crypto.Cipher import AES, PKCS1_OAEP from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes import base641. 生成随机AES密钥
aes_key = get_random_bytes(32) # AES-256 cipher_aes = AES.new(aes_key, AES.MODE_GCM) xml_bytes = xml_str.encode('utf-8') ciphertext, auth_tag = cipher_aes.encrypt_and_digest(xml_bytes)
2. 用RSA公钥加密AES密钥
with open('public_key.pem', 'rb') as f: rsa_key = RSA.import_key(f.read()) cipher_rsa = PKCS1_OAEP.new(rsa_key) encrypted_aes_key = cipher_rsa.encrypt(aes_key)
3. 构造上传载荷(JSON或表单字段)
payload = { 'encrypted_xml': base64.b64encode(ciphertext).decode(), 'auth_tag': base64.b64encode(auth_tag).decode(), 'iv': base64.b64encode(cipher_aes.nonce).decode(), 'encrypted_aes_key': base64.b64encode(encrypted_aes_key).decode() }
然后用requests.post(..., json=payload)上传
注意:PKCS1_OAEP比PKCS1_v1_5更安全,避免填充预言攻击;auth_tag必须随密文一起传,服务端验证失败必须拒收。
前端JavaScript加密需绕过DOM解析陷阱
浏览器里直接加密XML字符串即可,但常见坑是:用DOMParser解析后再序列化,可能引入空白归一化、命名空间重写、编码变更(如&变&),导致解密后XML不可用。
正确做法:
- 若XML来自
或fetch响应,直接对原始string加密(确保已知编码为UTF-8) - 避免用
new XMLSerializer().serializeToString(doc)——它不保证字节级保真 - 使用
crypto.subtle时,用new TextEncoder().encode(str)转Uint8Array,别用atob/btoa处理二进制 - 密钥派生若用PBKDF2,密码不能硬编码,且
iterations至少100000
服务端解密失败的三个高频原因
上传后服务端解密报错,八成出在这三处:
-
InvalidTag(PyCryptodome)或OperationError(Web Crypto):auth_tag长度不对(GCM要求16字节),或base64解码后没还原成原始字节 - RSA解密失败:检查公钥格式是否为PEM PKCS#8(
-----BEGIN PUBLIC KEY-----),而非传统PKCS#1(-----BEGIN RSA PUBLIC KEY-----) - XML解析异常:解密后得到的是UTF-8字节流,但服务端用
str.decode('latin-1')或默认系统编码解码,导致乱码——必须显式用.decode('utf-8')
密钥管理、密文存储、审计日志这些环节,一旦脱离“发送方加密→传输→接收方解密”这个闭环,就不是E2EE。真正的难点不在加解密函数调用,而在确保整个链路没有明文落盘、无调试输出、无中间人缓存副本。









