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

修复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中定义的binary697a84312aac99fbe2315f0637c960352242e26ffd3e2c33c298f06730fffa9687cb0d845f57c1a645
CipherPropertyEncryptor.java中定义的cipherbinary2242e26ffd3e2c33c298f06730fffa9687cb0d845f57c1a645
SaltNiFi 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:51:11