PyCryptodome decrypt_and_verify is returning "ValueError: MAC check failed"

PyCryptodome decrypt_and_verify is returning "ValueError: MAC check failed"

我有这个Java代码

byte[] decoded= Base64.getDecoder().decode(str.getBytes(StandardCharsets.UTF_8));

byte[] copyfrom12= Arrays.copyOfRange(decoded, 0, Integer.parseInt("12"));
SecretKeySpec secretkeyspec= new SecretKeySpec("0123456789012345".getBytes(), "AES");
            
Cipher cipher= Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(2, secretkeyspec, new GCMParameterSpec(Integer.parseInt("16") * 8, copyfrom12));

return new String(cipher.doFinal(decoded, Integer.parseInt("12"), decoded.length - Integer.parseInt("12")),StandardCharsets.UTF_8);

我认为上面的Java代码是不言自明的,这只是意见,如果不是请问我

我尝试转换为 Python,这就是我的尝试,但我没有得到预期的解密输出,而只有使用库 PyCryptodome 的字节,我收到此错误:

ValueError: MAC check failed

key = b'0123456789012345'
cipher = AES.new(key.encode("utf8"), AES.MODE_GCM, nonce=decoded[:12])
plaintext = cipher.decrypt_and_verify(v5,v5[-16:])
print("plaintext: ", plaintext

plaintext 不包含预期的解密值。我在这里错过了什么?我的转换仍然不完整吗?还是我遗漏了某些部分?

评论里有人说应该这样切IV|ciphertext|tag,但是怎么切呢?

Java 代码在 GCM 模式下使用 AES-128。密文由 nonce(12 字节)、actual 密文和身份验证标签(16 字节)的串联组成。随机数在authentication/decryption之前分开。在 authentication/decryption 期间,密文和标签在 Java 代码中被串联处理。仅当身份验证成功时才返回解密数据,否则生成 AEADBadTagException,参见 doFinal().

PyCryptodome 提供了方法decrypt_and_verify() 进行认证和解密,它需要密文和标记作为单独的 参数,与Java 方不同。仅当身份验证成功时才返回解密数据,否则引发 ValueError

请注意,PyCryptodome 还提供了分别执行身份验证和解密的方法verify() and decrypt()。一个可以想象的用例是在需要多个 decrypt() 调用时对较大的密文进行分块处理。但是,出于安全原因,未经事先成功验证,不得使用解密数据。

在PyCryptodome代码中,nonce、密文和tag首先要分开。然后就可以进行鉴权和解密了。下面的 PyCryptodome 代码是使用 PyCryptodome 进行身份验证和解密的可能实现(一步和两步过程):

import base64
from Crypto.Cipher import AES

# Separate IV|ciphertext|tag
encryptedDataB64 = 'MDEyMzQ1Njc4OTAxT8G0PHUg00CQ5QYKMxLaHIwrIsy6PIAAA+7mLA9LdXtUftf1Pwv9zFJEHmapbsyfc/HKwxQp1EH3FzA='
encryptedData = base64.b64decode(encryptedDataB64)
nonce = encryptedData[:12]
ciphertext = encryptedData[12:-16]
tag = encryptedData[-16:]

# Authenticate/Decrypt (one step), s. https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#gcm-mode
key = b'0123456789012345'
try:
    cipher = AES.new(key, AES.MODE_GCM, nonce)
    decryptedData = cipher.decrypt_and_verify(ciphertext, tag)
    print(decryptedData) # use authenticated data
except ValueError:
    print('Decryption failed')

#  Authenticate/Decrypt (two step), s. https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html#aes
key = b'0123456789012345'
cipher = AES.new(key, AES.MODE_GCM, nonce)
decryptedData = cipher.decrypt(ciphertext)
try:
    cipher.verify(tag) 
    print(decryptedData) # use authenticated data
except ValueError:
    print('Decryption failed')

输出:

b'The quick brown fox jumps over the lazy dog'
b'The quick brown fox jumps over the lazy dog'