Java と Python の AES暗合復号

実運用を考えて、書き直しました。
AES のモードは、CBC ,パディングは、PKCS5Padding
Java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
 * AES 256 暗合複合.  mode : CBC     padding : PKCS5Padding
 * iv ← SHA-256 SecretKeySpec byte[] の先頭16byte
 */
public final class AESCipher{
   private SecretKeySpec key;
   private IvParameterSpec iv;
   private String mode;

   private AESCipher(String password){
      try{
         byte[] keydata = password.getBytes();
         MessageDigest sha = MessageDigest.getInstance("SHA-256");
         keydata = sha.digest(keydata);
         keydata = Arrays.copyOf(keydata, 32);
         key = new SecretKeySpec(keydata, "AES");
         mode = "AES/CBC/PKCS5Padding";
         iv = new IvParameterSpec(Arrays.copyOf(key.getEncoded(), 16));
      }catch(NoSuchAlgorithmException e){
         throw new RuntimeException(e.getMessage());
      }
   }
   /**
    * AES インスタンス生成
    * @param keyword 共通鍵
    * @return AESCipher
    */
   public static AESCipher of(String keyword){
      return new AESCipher(keyword);
   }
   /**
    * Initilize Vector
    * @return 16byte byte[]
    */
   public byte[] getIV(){
      return iv.getIV();
   }
   /**
    * 暗合化.
    * @param message 平文
    * @return 暗合文byte[]
    */
   public byte[] encrypt(String message){
      try{
         Cipher cipher = Cipher.getInstance(mode);
         cipher.init(Cipher.ENCRYPT_MODE, key, iv);
         return cipher.doFinal(message.getBytes());
      }catch(Exception e){
         throw new RuntimeException(e);
      }
   }
   /**
    * 複合
    * @param encbytes 暗合文byte[]
    * @return 平文
    */
   public byte[] decrypt(byte[] encbytes){
      try{
         Cipher cipher = Cipher.getInstance(mode);
         cipher.init(Cipher.DECRYPT_MODE, key, iv);
         return cipher.doFinal(encbytes);
      }catch(Exception e){
         throw new RuntimeException(e);
      }
   }
   /**
    * ランダム文字列生成
    * @param len 長さ
    * @return a-zA-Z0-9 の文字列
    */
   public static String randomKey(int len){
      SecureRandom secureRandom = new SecureRandom();
      String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      StringBuilder sb = new StringBuilder();
      for(int i=0; i < len; i++){
         sb.append(CHARACTERS.charAt(secureRandom.nextInt(CHARACTERS.length())));
      }
      return sb.toString();
   }
}

iv は、キーから先頭16byte にしています。
Java 暗合化実行

AESCipher cipher = AESCipher.of(keyword);
byte[] encbytes = cipher.encrypt(planetext);
String enctxt = Base64.getEncoder().encodeToString(encbytes);
// enctxt = Base64 エンコードした暗合文

Java 復号実行

byte[] encbytes = Base64.getDecoder().decode(enctxt);
AESCipher cipher = AESCipher.of(keyword);
String dectxt = new String(cipher.decrypt(encbytes), StandardCharsets.UTF_8);
while(B64Util.isBase64(dectxt)){
   dectxt = new String(Base64.getDecoder().decode(dectxt), StandardCharsets.UTF_8);
}
// dectxt = 復号

B64Util.isBase64 は、
Base64 かどうか判定する - Oboe吹きプログラマの黙示録
のメソッドです。Python で暗合化した暗合文を解くときは、
再度、Base64デコードが必要です。
Python
cryptoaes.py

# -*- coding: UTF-8 -*-
# AES 256 暗合複合  CBCモード
#   使い方:
#     from cryptoaes import AESCipher
#     インスタンス
#           aes = AESCipher(password)
#     暗合化 → Base64 エンコード
#            enctxt = b64encode(aes.encrypt(planetxt)).decode('utf-8')
#     CBCモード 複合 ← Base64エンコード済の暗号文
#            dectxt = aes.decrypt(encted_b64_string)
#
import re
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
from base64 import b64encode, b64decode

BLOCK_SIZE = AES.block_size
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

class AESCipher:
    def __init__(self, password):
        self.key = hashlib.sha256(password.encode("utf-8")).digest()
        self.iv = self.key[:16]
        self.reb64 = re.compile(r'^[a-z0-9A-Z/\+]+={1,2}$')
    # AES 256キー参照
    def getKey(self):
        return self.key
    # ベクトル値取得
    def getIV(self):
        return self.iv
    # password & ベクトル値セット
    def setPasswdAndIV(self, passandiv):
        self.key = hashlib.sha256(passandiv[0].encode("utf-8")).digest()
        self.iv = passandiv[1]

    # 暗号化  plane → byte
    def encryptCBC(self, message):
        if message is None or len(message) == 0:
            raise NameError("No value given to encrypt")
        raw = b64encode(message.encode('utf-8')).decode()
        raw = pad(raw)
        raw = raw.encode('utf-8')
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        return cipher.encrypt(raw)

    # 復号  
    def decryptCBC(self, enctext, type='b64'):
        if enctext is None or len(enctext) == 0:
            raise NameError("No value given to decrypt")
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        #data = re.sub(b"([\x00-\x08\x0b\x0c\x0e-\x1f])*$", b'', cipher.decrypt(b64decode(enctext))).decode()
        data = unpad(cipher.decrypt(b64decode(enctext))).decode()
        if len(data) % 4 == 0 and self.reb64.match(data):
            data =  b64decode(data).decode()
        return data

復号時、unpad を使うか正規表現で末尾のパディングされている制御文字を除去するか
迷いましたが、unpad を使ってます。

Python暗合実行 暗号文→Base64エンコード

from cryptoaes import AESCipher
from base64 import b64encode

aes = AESCipher(key)
edata = aes.encryptCBC(planetxt)
enctxt = b64encode(edata).decode()

Python復号実行

aes = AESCipher(key)
dectxt = aes.decryptCBC(enctext)