Java AES GCM加密后Python解密失败,求解决方案
Java AES-GCM加密结果的Python解密方案
现有固定Java AES-GCM加密代码(无法修改),在Python中解密时频繁遇到密钥解码错误和cryptography.exceptions.InvalidTag异常,以下是问题分析和修正后的Python解密代码。
固定Java加密代码
import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AESEncryptionUtil { public static void main(String[] args) { String encString = "Hello, World!"; String secKey = "hellow world"; String encrypted = encrypt(encString, secKey); System.out.println("Encrypted (Java): " + encrypted); String decrypted = decrypt(encrypted, secKey); System.out.println("Decrypted (Java): " + decrypted); } private static final Logger logger = LoggerFactory.getLogger(AESEncryptionUtil.class); private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; private static final int IV_LENGTH_BYTE = 12; private static final int SALT_LENGTH_BYTE = 16; private static final Charset UTF_8 = StandardCharsets.UTF_8; public static String encrypt(String pText, String secKey) { try { if (pText == null || pText.equals("null")) { return null; } byte[] salt = getRandomNonce(SALT_LENGTH_BYTE); byte[] iv = getRandomNonce(IV_LENGTH_BYTE); byte[] keyBytes = secKey.getBytes(StandardCharsets.UTF_16); SecretKeySpec skeySpec = new SecretKeySpec(Arrays.copyOf(keyBytes, 16), "AES"); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte[] cipherText = cipher.doFinal(pText.getBytes()); byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) .put(iv) .put(salt) .put(cipherText) .array(); return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); } catch (Exception ex) { logger.error("Error while encrypting:", ex); } return null; } public static String decrypt(String cText, String secKey) { try { if (cText == null || cText.equals("null")) { return null; } byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8)); ByteBuffer bb = ByteBuffer.wrap(decode); byte[] iv = new byte[IV_LENGTH_BYTE]; bb.get(iv); byte[] salt = new byte[SALT_LENGTH_BYTE]; bb.get(salt); byte[] cipherText = new byte[bb.remaining()]; bb.get(cipherText); byte[] keyBytes = secKey.getBytes(StandardCharsets.UTF_16); SecretKeySpec skeySpec = new SecretKeySpec(Arrays.copyOf(keyBytes, 16), "AES"); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, UTF_8); } catch (Exception ex) { logger.error("Error while decrypting:", ex); } return null; } public static byte[] getRandomNonce(int numBytes) { byte[] nonce = new byte[numBytes]; new SecureRandom().nextBytes(nonce); return nonce; } }
原Python代码的问题分析
- 密钥处理错误:Java中对密钥的处理是将字符串用UTF-16编码后截断前16字节作为AES密钥,原Python代码错误地使用
ljust(16, b'\0')补全,与Java逻辑不符。 - 密文结构拆分错误:Java的
cipher.doFinal()返回的是密文+GCM标签的组合字节数组,原Python代码手动拆分密文和标签的逻辑错误,cryptography库会自动处理标签的提取。 - 盐的冗余处理:Java代码中生成了盐但未用于密钥派生或加密过程,仅作为占位符,Python中无需对盐做额外处理,直接跳过即可。
修正后的Python解密代码
import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend def decrypt(cipher_text_base64, secret_key): # 解码Base64 cipher_text_with_iv_salt = base64.b64decode(cipher_text_base64) # 拆分IV、盐、密文(含标签) iv = cipher_text_with_iv_salt[:12] # 跳过盐(Java中未使用盐,仅占位) ciphertext_with_tag = cipher_text_with_iv_salt[12+16:] # 生成与Java一致的密钥:UTF-16编码后取前16字节 key_bytes = secret_key.encode('utf-16') key = key_bytes[:16] # AES-GCM解密 decryptor = Cipher( algorithms.AES(key), modes.GCM(iv), backend=default_backend() ).decryptor() # 解密时自动验证并提取标签 plaintext = decryptor.update(ciphertext_with_tag) + decryptor.finalize() return plaintext.decode('utf-8') if __name__ == "__main__": secret_key = "hellow world" # 替换为Java输出的加密字符串 encrypted_text = "这里替换成Java生成的加密Base64字符串" decrypted_text = decrypt(encrypted_text, secret_key) print(f"Decrypted (Python): {decrypted_text}")
关键修改说明
- 密钥生成:严格对齐Java逻辑,将密钥字符串用UTF-16编码后直接截取前16字节,不做补0处理。
- 密文处理:直接将IV和盐之后的所有字节作为
ciphertext_with_tag传入解密器,由cryptography库自动分离密文和标签并验证。 - 盐的处理:由于Java代码中盐未参与加密流程,仅需跳过该字节段即可。
内容的提问来源于stack exchange,提问作者chethankumar
相关产品推荐
相关产品推荐

