他言語と相互間の暗合複合を考慮の AES 暗合複合 Java のコード

本記事よりも、2019-3-2 に書き改めた方を参照すべし。。
oboe2uran.hatenablog.com



Java と Pyrhon 間の AES 256 暗合複合ができるようにするものです。
他言語と相互間の暗合複合を考慮の AES 暗合複合 Python のコード - Oboe吹きプログラマの黙示録
の続きです。

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 : ECB or CBC     padding : PKCS5Padding
 */
public final class AESCipher{
   private SecretKeySpec key;
   private IvParameterSpec iv;
   private byte[] ivary;
   private String mode;

   private AESCipher(String password, byte[] ivector){
      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";
         ivary = Arrays.copyOf(ivector, 16);
         iv = new IvParameterSpec(ivary);
      }catch(NoSuchAlgorithmException e){
         throw new RuntimeException(e.getMessage());
      }
   }
   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/ECB/PKCS5Padding";
      }catch(NoSuchAlgorithmException e){
         throw new RuntimeException(e.getMessage());
      }
   }
   /**
    * AES CBC モードインスタンス生成
    * @param keyword 共通鍵
    * @param ivector 初期ベクトル
    * @return AESCipher
    */
   public static AESCipher of(String keyword, byte[] ivector){
      return new AESCipher(keyword, ivector);
   }
   /**
    * AES ECB モードインスタンス生成
    * @param keyword 共通鍵
    * @param ivector 初期ベクトル
    * @return AESCipher
    */
   public static AESCipher of(String keyword){
      return new AESCipher(keyword);
   }
   /**
    * CBC モードで使用する Initilize Vector
    * @return 16byte byte[]
    */
   public byte[] getIV(){
      return ivary;
   }
   /**
    * 暗合化.
    * @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);
      }
   }
   /**
    * ランダムキー生成(長さ=16 を指定して IV 作成する時に利用する)
    * @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();
   }
}

これと、先日の Base64 かどうかをチェックするクラス
Base64 かどうか判定する - Oboe吹きプログラマの黙示録
→ B64Util クラス
テスト用にファイルを読み書きするツール、・・・今回サンプル用のクラス
サンプルは、暗号文、キーをファイルに書いて受け渡す為。
ファイルの読み書き→FileTool

public final class FileTool{
   private FileTool(){}

   public static byte[] readBinary(String path) throws IOException{
      try(InputStream in = new FileInputStream(path)){
         byte[] data = new byte[in.available()];
         in.read(data);
         in.close();
         return data;
      }
   }
   public static String readText(String path) throws IOException{
      try(InputStream in = new FileInputStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream()){
         in.transferTo(out);
         return out.toString();
      }
   }
   public static void write(String text, String path) throws IOException{
      try(OutputStream out = new FileOutputStream(path)){
         out.write(text.getBytes());
         out.flush();
      }
   }
   public static void write(byte[] data, String path) throws IOException{
      try(OutputStream out = new FileOutputStream(path)){
         out.write(data, 0, data.length);
         out.flush();
      }
   }
}

JavaPython
JavaPython の為の暗合作成

String planetext = FileTool.readText("origin/plane.txt");

String keyword = AESCipher.randomKey(7);
byte[] iv = AESCipher.randomKey(16).getBytes();
String ivhex = IntStream.range(0, iv.length)
.mapToObj(i->String.format("%02x", iv[i]))
.collect(Collectors.joining(""));

// 鍵とIVの保存
FileTool.write(keyword, "out/aeskey.txt");
FileTool.write(ivhex, "out/ivhex.txt");
// 暗合化
AESCipher cipher = AESCipher.of(keyword, iv);
byte[] encbytes = cipher.encrypt(planetext);

/* Python に渡す為に、16進数 HEX表現文字列にしてファイル書き出す */

String hexstr = IntStream.range(0, encbytes.length)
.mapToObj(i->String.format("%02x", encbytes[i])).collect(Collectors.joining(""));
FileTool.write(hexstr, "out/aes_encted.hex");

Python での複合

import codecs
from cryptoaes import AESCipher

with codecs.open('../../../../out/aeskey.txt', "r", 'utf-8') as f:
    key = f.read()
with codecs.open('../../../../out/ivhex.txt', 'r', 'utf-8') as f:
    ivhex = f.read()
    iv = bytes.fromhex(ivhex)
with codecs.open('../../../../out/aes_encted.hex', 'r', 'utf-8') as f:
    enctext = f.read()
    print(enctext)

    aes = AESCipher(key, bytes.fromhex(ivhex))
    dectxt = aes.decryptCBC(enctext, type='hex')

    print(dectxt)

PythonJava
PythonJava の為の暗合作成

# -*- coding: UTF-8 -*-
import codecs
from cryptoaes import AESCipher
from base64 import b64encode

with codecs.open('../../../../out/aeskey.txt', "r", 'utf-8') as f:
    key = f.read()
with codecs.open('../../../../out/ivhex.txt', 'r', 'utf-8') as f:
    ivhex = f.read()
    iv = bytes.fromhex(ivhex)
with codecs.open('../../../../origin/plane.txt', 'r', 'utf-8') as f:
    planetxt = f.read()

    aes = AESCipher(key, bytes.fromhex(ivhex))
    enctxt = b64encode(aes.encryptCBC(planetxt)).decode('utf-8')

    with codecs.open('../../../../out/aes_encted.txt', 'w', 'utf-8') as f:
        f.write(enctxt)

Java 複合

String keyword = FileTool.readText("out/aeskey.txt");
String ivhex = FileTool.readText("out/ivhex.txt");
String enctxt = FileTool.readText("out/aes_encted.txt");

byte[] encbytes = Base64.getDecoder().decode(enctxt);


// ベクトル16進文字列 → byte[]
List<Byte> ivlist = Pattern.compile("[\\w]{1,2}").matcher(ivhex).results().
map(r->(byte)Integer.parseInt(r.group(), 16)).collect(Collectors.toList());
byte[] iv = Bytes.toArray(ivlist);

// 複合
AESCipher cipher = AESCipher.of(keyword, iv);
String dectxt = new String(cipher.decrypt(encbytes), StandardCharsets.UTF_8);
// Base64 チェック→ デコード繰り返す。
while(B64Util.isBase64(dectxt)){
	dectxt = new String(Base64.getDecoder().decode(dectxt), StandardCharsets.UTF_8);
}

System.out.println(dectxt);

decrypt 実行後、B64Util.isBase64() でBase64エンコードされたものかどうかをチェックして
Base64デコードを繰り返すようにすることで、
Javaで暗合化したものも、Python で暗合化したものも、両方このまま
複合する処理として走らせることができます。