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

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代码的问题分析

  1. 密钥处理错误:Java中对密钥的处理是将字符串用UTF-16编码后截断前16字节作为AES密钥,原Python代码错误地使用ljust(16, b'\0')补全,与Java逻辑不符。
  2. 密文结构拆分错误:Java的cipher.doFinal()返回的是密文+GCM标签的组合字节数组,原Python代码手动拆分密文和标签的逻辑错误,cryptography库会自动处理标签的提取。
  3. 盐的冗余处理: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 20:05:55