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
相关产品推荐
相关产品推荐

