実運用を考えて、書き直しました。
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 を使ってます。
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)