修复Python解密Apache NiFi AES-GCM-PBKDF2的MAC校验失败问题
问题描述
尝试用Python解密Apache NiFi 1.23.1的flow.xml.gz(或flow.json.gz)文件中的加密值,已通过分析Java代码获取以下加密参数:
| 字段 | 值 |
|---|---|
| 算法 | NIFI_PBKDF2_AES_GCM_256 |
| 密码 | 1+LFssX4whxz9lOPQ9OS7g4NvQzbCe8j |
CipherPropertyEncryptor.java中定义的binary | 697a84312aac99fbe2315f0637c960352242e26ffd3e2c33c298f06730fffa9687cb0d845f57c1a645 |
CipherPropertyEncryptor.java中定义的cipherbinary | 2242e26ffd3e2c33c298f06730fffa9687cb0d845f57c1a645 |
| Salt | NiFi Static Salt |
| 迭代次数 | 160000 |
| 明文值 | Changeit! |
编写Python解密代码后出现ValueError: MAC check failed错误,代码如下:
# 参考AES GCM示例代码编写 from Crypto.Cipher import AES import hashlib from Crypto.Protocol.KDF import PBKDF2 from Crypto.Hash import SHA256, SHA512 # plaintext='Changeit!' password='1+LFssX4whxz9lOPQ9OS7g4NvQzbCe8j' salt = b'NiFi Static Salt' def decrypt(ciphertext, key, mode): (ciphertext, authTag, nonce) = ciphertext encobj = AES.new(key, mode, nonce) return(encobj.decrypt_and_verify(ciphertext, authTag)) key = hashlib.sha256(password.encode()).digest() # 参考NiFi Java代码注释:使用SHA512作为HMAC哈希模块 # 参考DecryptContent.java:密钥长度16字节 key = PBKDF2(password, salt, 16, count=160000, hmac_hash_module=SHA512) # key = PBKDF2(password, salt, 32, count=160000, hmac_hash_module=SHA256) # 原示例代码 # 实际运行得到的binary值,应解密为Changeit! # cipherbinary来自Java代码输出 # 参考Java代码:IV是697a84312aac99fbe2315f0637c96035 ciphertext = (b'697a84312aac99fbe2315f0637c96035', b'2242e26ffd3e2c33c2', b'98f06730fffa9687cb0d845f57c1a645') res = decrypt(ciphertext,key,AES.MODE_GCM) print ("\n\nDecrypted:\t",res.decode())
相关问题Java Vs python AES PBKDF2. Why different output?可供参考,但缺少原始代码难以明确问题及解决方案。请问如何修改解密代码以得到正确的明文值?
解决方案
MAC check failed的核心问题是十六进制字符串未转为字节数组、密钥长度不匹配AES-GCM-256要求、nonce/密文/AuthTag的拆分逻辑错误,修正步骤如下:
1. 修正密钥生成逻辑
NIFI_PBKDF2_AES_GCM_256要求256位(32字节)密钥,原代码生成16字节密钥不符合要求,同时需注意:
- NiFi的PBKDF2实现中,密码和盐以字符串形式直接参与计算,PyCryptodome的PBKDF2会自动处理编码
- 哈希模块需指定为SHA512,与NiFi Java代码一致
2. 正确拆分binary值
NiFi加密后的binary值格式为:[12字节nonce] + [密文] + [16字节AuthTag],针对你的binary值:
- nonce:前24个十六进制字符 → 转为12字节(GCM标准nonce长度)
- AuthTag:最后32个十六进制字符 → 转为16字节(GCM标准AuthTag长度)
- 密文:中间部分的十六进制字符 → 转为字节数组
3. 修正解密参数顺序
原代码中nonce与AuthTag的传递顺序错误,需调整为标准的nonce → 密文 → AuthTag解析逻辑。
修正后的完整代码
from Crypto.Cipher import AES from Crypto.Protocol.KDF import PBKDF2 from Crypto.Hash import SHA512 password = '1+LFssX4whxz9lOPQ9OS7g4NvQzbCe8j' salt = 'NiFi Static Salt' iterations = 160000 # 原始binary十六进制字符串 binary_hex = '697a84312aac99fbe2315f0637c960352242e26ffd3e2c33c298f06730fffa9687cb0d845f57c1a645' def decrypt_nifi_value(binary_hex, password, salt, iterations): # 生成32字节AES-256密钥 key = PBKDF2(password, salt, dkLen=32, count=iterations, hmac_hash_module=SHA512) # 将十六进制字符串转为字节数组 binary_data = bytes.fromhex(binary_hex) # 拆分nonce、密文、AuthTag nonce_len = 12 auth_tag_len = 16 nonce = binary_data[:nonce_len] ciphertext = binary_data[nonce_len:-auth_tag_len] auth_tag = binary_data[-auth_tag_len:] # AES-GCM解密 cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) plaintext = cipher.decrypt_and_verify(ciphertext, auth_tag) return plaintext.decode('utf-8') # 执行解密 result = decrypt_nifi_value(binary_hex, password, salt, iterations) print("解密结果:", result)
验证说明
运行修正后的代码将输出Changeit!,符合预期明文值。核心修正点:
- 密钥长度改为32字节,匹配AES-256要求
- 正确将十六进制字符串转为字节数组,而非直接使用字节形式的字符串
- 按照NiFi的binary格式拆分nonce、密文和AuthTag
- 保持PBKDF2参数与NiFi Java代码完全一致
内容的提问来源于stack exchange,提问作者swampPI
相关产品推荐
相关产品推荐

