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

Java AES-256加密转Python后,无法解密Java加密字符串求助

AES跨语言加解密问题:Python无法解密Java加密字符串

我将基于AES算法的Java加密代码改写为Python版本后,Python内部的加解密功能正常,但无法解密Java生成的加密字符串。

Java加密代码

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;


@Slf4j
public class EncryptUsingAES {

    /**
     * This function takes a String as input and encrypts it using AES Algorithm
     * @param stringToEncrypt
     * @param publicKey
     * @return
     */
    public String returnEncryptedString(String stringToEncrypt, String publicKey) {

        Security.addProvider(new BouncyCastleProvider());
        try{
            SecureRandom sr = new SecureRandom();
            byte[] salt = new byte[8];
            sr.nextBytes(salt);
            final byte[][] keyAndIV = generateKeyAndIV(salt, publicKey.getBytes(StandardCharsets.UTF_8), MessageDigest.getInstance("MD5"));
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyCastleProvider.PROVIDER_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIV[0], "AES"), new IvParameterSpec(keyAndIV[1]));
            byte[] encryptedData = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
            byte[] prefixAndSaltAndEncryptedData = new byte[16 + encryptedData.length];

            System.arraycopy("Salted__".getBytes(StandardCharsets.UTF_8), 0, prefixAndSaltAndEncryptedData, 0, 8);

            System.arraycopy(salt, 0, prefixAndSaltAndEncryptedData, 8, 8);

            System.arraycopy(encryptedData, 0, prefixAndSaltAndEncryptedData, 16, encryptedData.length);
            String encryptedString = URLEncoder.encode(Base64.getEncoder().encodeToString(prefixAndSaltAndEncryptedData),"UTF-8");

            return encryptedString;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * this function creates key Initialisation Vector which is required for AES Encryption
     * @param salt
     * @param password
     * @param md
     * @return
     */
    protected byte[][] generateKeyAndIV(byte[] salt, byte[] password, MessageDigest md) {

        int keyLength = 32;
        int ivLength = 16;
        int iterations = 1;
        int digestLength = md.getDigestLength();
        int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
        byte[] generatedData = new byte[requiredLength];
        int generatedLength = 0;

        try {
            md.reset();

            while (generatedLength < keyLength + ivLength) {

                if (generatedLength > 0)
                    md.update(generatedData, generatedLength - digestLength, digestLength);
                md.update(password);
                if (salt != null)
                    md.update(salt, 0, 8);
                md.digest(generatedData, generatedLength, digestLength);

                for (int i = 1; i < iterations; i++) {
                    md.update(generatedData, generatedLength, digestLength);
                    md.digest(generatedData, generatedLength, digestLength);
                }

                generatedLength += digestLength;
            }

            byte[][] result = new byte[2][];
            result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
            if (ivLength > 0)
                result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);

            return result;

        } catch (DigestException e) {
            log.error(OrderStatusSmsTrackerConstants.ORDER_STATUS_SMS_TRACKER_SERVICE);
            return null;
        } finally {
            Arrays.fill(generatedData, (byte)0);
        }
    }
}

原Python加解密代码

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64
import urllib.parse
import hashlib
import logging

class EncryptUsingAES:
    def return_encrypted_string(self, string_to_encrypt, public_key):
        try:
            salt = get_random_bytes(8)
            key_and_iv = self.generate_key_and_iv(salt, public_key.encode('utf-8'), hashlib.md5)

            cipher = AES.new(key_and_iv[0], AES.MODE_CBC, iv=key_and_iv[1])
            encrypted_data = cipher.encrypt(pad(string_to_encrypt.encode('utf-8'), AES.block_size))

            prefix_and_salt_and_encrypted_data = b"Salted__" + salt + encrypted_data
            encrypted_string = urllib.parse.quote(base64.b64encode(prefix_and_salt_and_encrypted_data))

            return encrypted_string
        except Exception as e:
            logging.error("Error when encrypting: " + str(e))
            return None
        
    def return_decrypted_string(self, encrypted_string, public_key):
        try:
            encrypted_data = base64.b64decode(urllib.parse.unquote(encrypted_string))
            salt = encrypted_data[8:16]
            encrypted_data = encrypted_data[16:]

            key_and_iv = self.generate_key_and_iv(salt, public_key.encode('utf-8'), hashlib.md5)

            cipher = AES.new(key_and_iv[0], AES.MODE_CBC, iv=key_and_iv[1])
            decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)

            return decrypted_data.decode('utf-8')
        except Exception as e:
            logging.error("Error when decrypting: " + str(e))
            return None

    def generate_key_and_iv(self, salt, password, md):
        key_length = 32
        iv_length = 16
        iterations = 1
        digest_length = md().digest_size
        required_length = (key_length + iv_length + digest_length - 1) // digest_length * digest_length
        generated_data = bytearray([0]) * required_length
        generated_length = 0

        try:
            md = md()
            while generated_length < key_length + iv_length:
                if generated_length > 0:
                    md.update(generated_data[generated_length - digest_length:generated_length])
                md.update(password)
                if salt is not None:
                    md.update(salt[:8])
                generated_data[generated_length:generated_length + digest_length] = md.digest()

                for i in range(1, iterations):
                    md.update(generated_data[generated_length:generated_length + digest_length])
                    generated_data[generated_length:generated_length + digest_length] = md.digest()

                generated_length += digest_length

            key = generated_data[:key_length]
            iv = generated_data[key_length:key_length + iv_length]
            return key, iv

        except Exception as e:
            logging.error("Error when generating key and IV: " + str(e))
            return None
        finally:
            for i in range(len(generated_data)):
                generated_data[i] = 0

问题原因及修复方案

核心问题是Java与Python的哈希计算实例行为不一致:

  • Java中MessageDigest.digest()方法在完成哈希计算后会自动重置实例状态,回到初始干净的状态。
  • Python的hashlib实例调用digest()后不会自动重置,若循环复用同一个实例,后续哈希计算会基于之前的状态累积,导致生成的Key和IV与Java完全不匹配。

修复后的Python代码(仅修改generate_key_and_iv方法,确保每次哈希计算都使用全新的实例):

def generate_key_and_iv(self, salt, password, md):
        key_length = 32
        iv_length = 16
        iterations = 1
        digest_length = md().digest_size
        required_length = (key_length + iv_length + digest_length - 1) // digest_length * digest_length
        generated_data = bytearray([0]) * required_length
        generated_length = 0

        try:
            while generated_length < key_length + iv_length:
                # 每次循环创建全新的哈希实例,匹配Java的自动重置逻辑
                md_instance = md()
                if generated_length > 0:
                    md_instance.update(generated_data[generated_length - digest_length:generated_length])
                md_instance.update(password)
                if salt is not None:
                    md_instance.update(salt[:8])
                generated_data[generated_length:generated_length + digest_length] = md_instance.digest()

                for i in range(1, iterations):
                    md_instance = md()
                    md_instance.update(generated_data[generated_length:generated_length + digest_length])
                    generated_data[generated_length:generated_length + digest_length] = md_instance.digest()

                generated_length += digest_length

            key = generated_data[:key_length]
            iv = generated_data[key_length:key_length + iv_length]
            return key, iv

        except Exception as e:
            logging.error("Error when generating key and IV: " + str(e))
            return None
        finally:
            for i in range(len(generated_data)):
                generated_data[i] = 0

额外验证点:

  • 填充方式:Java使用PKCS7Padding,Python的pad()函数默认采用pkcs7风格,无需调整。
  • URL编码:Java的URLEncoder.encode与Python的urllib.parse.quote对Base64特殊字符的编码逻辑一致,解密时用urllib.parse.unquote即可正确解码。

内容的提问来源于stack exchange,提问作者Abhishek Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 18:15:55