JavaScript で RSA 暗合複合

OpenSSL で作成した RSA 暗合鍵、どのJSコードが良いのか探した。
JSEncrypt というのが一番使い易い。
電子署名で、SHA256 にするので、CryptoJS から、
sha256.js だけ使う。

<script src="jsencrypt.min.js" type="text/javascript"></script>
<script src="sha256.js" type="text/javascript"></script>


公開鍵で暗合化

var crypt = new JSEncrypt();
// 公開鍵 publickey は、Base64エンコード済
crypt.setPublicKey(publickey);
var encedtxt = crypt.encrypt(planetext);

秘密鍵で複合

// 秘密鍵 *.pem を読み込んでよけいなマーキングを除去
var secretkey = $('#pemkey').val()
    .replace("-----BEGIN RSA PRIVATE KEY-----", "")
    .replace("-----END RSA PRIVATE KEY-----", "")
    .replace(/\r?\n/g, '');

var crypt = new JSEncrypt();
crypt.setPrivateKey(secretkey);
var decrypted = crypt.decrypt(enctext);

署名作成 (秘密鍵で平文を署名)

var sign = new JSEncrypt();
sign.setPrivateKey(secretkey);
var signature = sign.sign(planetext, CryptoJS.SHA256, "sha256");

署名検証、元の平文、公開鍵で署名を検証する

var verify = new JSEncrypt();
verify.setPublicKey(publickey);
var verified = verify.verify(planetext, signature, CryptoJS.SHA256);
if (verified){
   alert('検証  成功');
}else{
   alert('不正な電子署名 !!');
}

Pattern Matcher の find()実行と group()実行、1つの処理文にする。

java.util.regex.Pattern
java.util.regex.Matcher
マッチした部分の抽出は、今まで良い考えが浮かばず、以下のように、
Matcher 変数宣言を強いられてきた。

String str = "HTTP   This request is Unauthorized    (401)";
Matcher m = Pattern.compile("\\([0-9]{3}\\)$").matcher(str);
String code = m.find() ? m.group(0) : null;

・Matcher 変数宣言すること。
・処理文が2つになること。
これが嫌で嫌でたまらなかった。
========================
下の方、取消線を書いたが、もっと良い綺麗に書く方法がある。
============
Optional で filter を使えば良い!

String code =	Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.filter(e->e.find()).map(e->e.group()).orElse(null);
Integer value =	Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.filter(e->e.find()).map(e->e.group())
.map(e->Integer.parseInt(e.substring(1, 4)))
.orElse(null);

以下は、たいして良くない。
========================
Optional を使えば、Matcher 変数宣言なし、AND 処理文は1つ。になる。

String code = Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.map(e->new Object[]{e.find(), e})
.map(e->Boolean.valueOf(e[0].toString()) ? ((Matcher)e[1]).group() : null)
.orElse(null);

"(401)" → 401 整数を抽出したければ、、

Integer val = Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.map(e->new Object[]{e.find(), e})
.map(e->Boolean.valueOf(e[0].toString()) ? ((Matcher)e[1]).group() : null)
.map(e->Integer.parseInt(e.substring(1, 4)))
.orElse(null);

これらは、Java Pythonのタプルが存在しないから、Matcher find() 結果 boolean find()後の
Matcher インスタンス
を Object[] に突っ込んでいるが、、
ペア構造のクラス、Map.Entry の実装に突っ込むようにすれば、
この Optional で実行する map も次のように綺麗なる。

ICU4J というのを使います。
International Components for Unicode の略で、Java文字コード変換ライブラリに
ある Pair<T, U> を使えば、この問題も綺麗になります。
site.icu-project.org
日本語の解説サイトは古いのしかないけど、決して開発が止まってるわけでもなさそうです。
ライブラリ最新は、去年10月に出ています。

JARを Maven で取得します。

<dependency>
  <groupId>com.ibm.icu</groupId>
  <artifactId>icu4j</artifactId>
  <version>63.1</version>
</dependency>

PDF帳票出力フレームワーク Jasperreports は、これを使っています。
取得したら、
com.ibm.icu.impl.Pair<F, S>
を使います。
総称型 F は、First で、Sは、Second ,1番目の要素と2番目の要素ということです。
インスタンス生成は、of メソッドで、要素へのアクセスは、getter ではなく public 変数を直接読込みです。
public final 宣言なので、インスタンス生成後は書き込めません。

Pair を使用すると以下のように書けます。

import com.ibm.icu.impl.Pair;
String code = Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.map(e->Pair.of(e.find(), e))
.map(e->e.first ? e.second.group() : null)
.orElse(null);
Integer val = Optional.of(Pattern.compile("\\([0-9]{3}\\)$").matcher(str))
.map(e-> Pair.of(e.find(), e))
.map(e->e.first ? e.second.group() : null)
.map(e->Integer.parseInt(e.substring(1, 4)))
.orElse(null);

PostgreSQL の再帰SQL

MySQL は、8.x なら再帰SQL( WITH RECURSIVE target AS ...)が使えるらしいが、
今回はPostgreSQL 再帰SQL
再帰SQLクエリを実行するテーブル構造
テーブル名:divison

id プライマリーキー
parent_id 親を指すID
name 名称

このテーブル、指定のID(id=1)の配下ツリーになるレコード全てを求めるSQL

WITH RECURSIVE r AS (
  SELECT id, name, parent_id 
  FROM division WHERE id = 1
UNION ALL
  SELECT d.id, d.name, d.parent_id 
  FROM division d, r WHERE d.parent_id = r.id
)
SELECT r.id, r.name, r.parent_id FROM r

指定ID(id=13)の 祖先(親)を順に求めるSQLクエリ

WITH RECURSIVE ancestor (depth, id, parent_id) AS (
   SELECT 0, id, parent_id,
   name 
   FROM division WHERE id = 13
UNION ALL
   SELECT ancestor.depth + 1, d.id, d.parent_id,
   d.name
   FROM ancestor, division d
   WHERE ancestor.parent_id = d.id )
SELECT depth, id, name 
FROM ancestor ORDER BY depth ASC

祖先(親)を求めるクエリの結果、depth は、0始まり整数で順にインクリメントされた結果になる。
例)A→B→C→D と連結している場合、、

depth id name
0 13 D
1 21 C
2 19 B
3 3 A

Putty で作成したRSA鍵で暗合複合の実行(Python)

本来なら Python だけで PuTTYgen で作成したRSA鍵ファイル (*.ppk) を読み込んで
Python crypto が使用できるRSA公開鍵、RSA秘密鍵を取り出したかったのですが、
秘密鍵を抽出するもの puttykeys · PyPI しか見つからず、
公開鍵を抽出するものが欲しかったのですが、見つからない。
もちろん *.ppk を単純に読んで "Public-Lines: " から行数取得して公開鍵の行を読んだだけで
そのまま使用できるわけない。

そこで、前回投稿のとおり、Java で ppk ファイルを読込み、Python が公開鍵と秘密鍵を使用できるように
ファイルに、Base64エンコードして書き出しておいて使用する。

Base64エンコードさえ、気をつければ、OpenSSL RSA の暗合複合と同様に、
pycryptodome をインストールした環境で Python でも PuttyRSA 暗合複合が実現できる。
Putty 用と、OpenSSL 用を書き並べてみる。Base64エンコード結果のキーを使う点が差になる。
ppk Java → 公開鍵抽出 → Python 公開鍵で暗合化

def rsa_encrypt(plane):
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5
    from base64 import b64encode
    from base64 import b64decode
    
    with codecs.open('puttypub.txt', 'r', 'utf-8') as f:
        pbkey = f.read()
        key = RSA.importKey(b64decode(pbkey))
        cipher = PKCS1_v1_5.new(key)
        return b64encode(cipher.encrypt(bytes(plane, 'utf-8'))).decode('utf-8')

OpenSSL RSA Python 公開鍵で暗合化

def rsa_encrypt(plane):
    with open("public_key.der", "rb") as f:
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5
        from base64 import b64encode
        key = RSA.importKey(f.read())
        cipher = PKCS1_v1_5.new(key)
        return b64encode(cipher.encrypt(bytes(plane, 'utf-8'))).decode('utf-8')

OpenSSL の公開鍵は、元からバイナリで読めない。

ppk Java秘密鍵抽出 → Python 秘密鍵で複合

def rsa_decrypt(enctxt):
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5
    from base64 import b64encode
    from base64 import b64decode

    with codecs.open('privatekey.txt', 'r', 'utf-8') as f:
        key = RSA.importKey(b64decode(f.read()))
        cipher = PKCS1_v1_5.new(key)
        text = cipher.decrypt(b64decode(enctxt), "Error while decrypt")
        return text.decode('utf-8')

OpenSSL RSA Python 秘密鍵で複合

def rsa_decrypt(enctxt):
    with open('private_key.pem','r') as f:
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5
        from base64 import b64decode
        pemkey = b64decode(f.read().replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "").replace("\n", ""))
        rsakey = RSA.importKey(pemkey)
        cipher = PKCS1_v1_5.new(rsakey)
        text = cipher.decrypt(b64decode(enctxt), "Error while decrypt")
    return text.decode('utf-8')

Java ー Pthon 間の RSA 暗合複合は、Base64 エンコードが使えるのは、
OpenSSL RSA でも、Putty ssh-2 - RSA でも上記に従えば、可能だ。

Putty で作成したRSA鍵で暗合複合の実行(Java)

putty key generator (PuTTYgen)SSH-2 RSA 鍵を作成、PuTTYgen 画面の [ Save private key ] ボタンで
作成する ppk ファイルを使用します。
SSH 通信やFSCP の為に、PuTTYgen で鍵を作って利用するのがほとんどで、業務開発などのプログラム開発の
データの暗号化でこのRSA鍵を利用することは、聞いたことがありませんでした。
でも、興味本位でやってみたくなり、Java で暗合複合、Python で暗合複合、Java-Python 間で暗合複合
を試しました。

(長いので、今回の投稿は、Java で暗合複合のみ)

ppkファイルの中身、Putty 独自のフォーマットで公開鍵と秘密鍵が書かれていますが
単純に読み取ってデコードしただけでは、RSA鍵として使えません。
計算してRSA鍵として読取りが必要です。Putty 独自のフォーマットだからどうしようもないです。

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.crypto.Cipher;
/**
 * PuttyRSA.java
 */
public class PuttyRSA{
   private PublicKey publicKey;
   private PrivateKey privateKey;

   private PuttyRSA(String filepath){
      try{
         StringBuilder sbpublic = new StringBuilder();
         StringBuilder sbprivate = new StringBuilder();
         AtomicInteger pc = new AtomicInteger(0);
         AtomicInteger pv = new AtomicInteger(0);
         List<String> lines
 = Files.readAllLines(Path.of(new File(filepath).toURI()), StandardCharsets.UTF_8);
         lines.stream().forEach(e->{
            if (pc.decrementAndGet() >= 0){
               sbpublic.append(e);
            }
            if (pv.decrementAndGet() >= 0){
               sbprivate.append(e);
            }
            if (e.startsWith("Public-Lines: ")){
               pc.set(Integer.parseInt(e.replace("Public-Lines: ", "")));
            }
            if (e.startsWith("Private-Lines: ")){
               pv.set(Integer.parseInt(e.replace("Private-Lines: ", "")));
            }
         });
         String publicstring = sbpublic.toString();
         String privatestring = sbprivate.toString();

         KeyFactory keyfactory = KeyFactory.getInstance("RSA");
         BigInteger publicExponent;
         BigInteger modulus;
         try(DataInputStream dis = new DataInputStream(
            new ByteArrayInputStream(Base64.getDecoder().decode(publicstring)))
         ){
            dis.readFully(new byte[dis.readInt()]);
            publicExponent = readInt(dis);
            modulus = readInt(dis);
            RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
            // 公開鍵
            publicKey = keyfactory.generatePublic(rsaPublicKeySpec);
         }
         try(DataInputStream dis = new DataInputStream(
             new ByteArrayInputStream(Base64.getDecoder().decode(privatestring)))
         ){
            BigInteger privateExponent = readInt(dis);
            BigInteger P = readInt(dis);
            BigInteger Q = readInt(dis);
            BigInteger iqmp = readInt(dis);
            BigInteger primeExponentP = privateExponent.mod(P.subtract(BigInteger.ONE));
            BigInteger primeExponentQ = privateExponent.mod(Q.subtract(BigInteger.ONE));
            RSAPrivateCrtKeySpec rsaPrivateKeySpec
 = new RSAPrivateCrtKeySpec(modulus,
                            publicExponent,
                            privateExponent,
                            P,
                            Q,
                            primeExponentP,
                            primeExponentQ,
                            iqmp);
            // 秘密鍵
            privateKey = keyfactory.generatePrivate(rsaPrivateKeySpec);
         }
      }catch(Exception ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
   }
   private BigInteger readInt(DataInputStream dis) throws IOException{
      int leng = dis.readInt();
      byte[] tmpBytes = new byte[leng];
      dis.readFully(tmpBytes);
      return new BigInteger(1, tmpBytes);
   }
   /**
    * Putty ppkファイルPATH → インスタンス生成
    * @param filepath Putty ppkファイルPATH
    * @return PuttyFile
    */
   public static PuttyRSA of(String filepath){
      return new PuttyRSA(filepath);
   }
   /**
    * 暗号化. (Base64 エンコードして返す)
    * @param planetxt 対象文字列
    * @return 暗号化されたBase64エンコード済文字列
    * @throws Exception
    */
   public String encrypt(String planetxt) throws Exception{
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder()
      .encodeToString(cipher.doFinal(planetxt.getBytes()));
   }
   /**
    * 秘密鍵による暗号化. (Base64 エンコードして返す)
    * @param planetxt 対象文字列
    * @return 暗号化されたBase64エンコード済文字列
    * @throws Exception
    */
   public String encryptPrivate(String planetxt) throws Exception{
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.ENCRYPT_MODE, privateKey);
      return Base64.getEncoder()
      .encodeToString(cipher.doFinal(planetxt.getBytes()));
   }
   /**
    * 複合化. (暗号化→Base64 エンコード済を指定)
    * @param encstring Base64 エンコード済の暗号テキスト
    * @return 複合文字列
    * @throws Exception
    */
   public String decrpt(String encstring) throws Exception{
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.DECRYPT_MODE, privateKey);
      return new String(cipher.doFinal(Base64.getDecoder().decode(encstring)));
   }
   /**
    * 公開鍵による複合化. (暗号化→Base64 エンコード済を指定)
    * @param encstring Base64 エンコード済の暗号テキスト
    * @return 複合文字列
    * @throws Exception
    */
   public String decrptPublic(String encstring) throws Exception{
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.DECRYPT_MODE, publicKey);
      return new String(cipher.doFinal(Base64.getDecoder().decode(encstring)));
   }
   public byte[] decrptPublicByte(String encstring) throws Exception{
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.DECRYPT_MODE, publicKey);
      return cipher.doFinal(Base64.getDecoder().decode(encstring));
   }
   /**
    * RSA公開鍵 Base64エンコード文字列の取得.
    * @return RSA公開鍵 Base64エンコード文字列
    */
   public String getPublickeyBase64(){
      return Base64.getEncoder().encodeToString(publicKey.getEncoded());
   }
   /**
    * RSA秘密鍵 Base64エンコード文字列の取得.
    * @return RSA秘密鍵 Base64エンコード文字列
    */
   public String getPrivatekeyBase64(){
      return Base64.getEncoder().encodeToString(privateKey.getEncoded());
   }
   /**
    * 署名の作成.
    * @param str メッセージ
    * @return Base64 エンコードした署名データ
    * @throws Exception
    */
   public String createSignature(String str) throws Exception{
      Signature signer = Signature.getInstance("SHA256withRSA");
      signer.initSign((PrivateKey)privateKey);
      signer.update(str.getBytes("UTF-8"));
      byte[] b = signer.sign();
      return Base64.getEncoder().encodeToString(b);
   }
   /**
    * 署名検証 (秘密鍵で暗号化→Base64エンコード文字列 を検証)
    * @param signstring 秘密鍵で暗号化したBase64エンコード済のデータ
    * @param origin 署名元のデータ
    * @return true = OK
    */
   public boolean verifySignature(String signstring, String origin){
      try{
         byte[] sigdata = Base64.getDecoder().decode(signstring);
         Signature signature = Signature.getInstance("SHA256withRSA");
         signature.initVerify((PublicKey)publicKey);
         signature.update(origin.getBytes());
         return signature.verify(sigdata);
      }catch(Exception e){
         return false;
      }
   }
}

Java 暗合複合のプログラム。。。単純です。

PuttyRSA puttyRSA = PuttyRSA.of("putty_test.ppk");
String enctxt = puttyRSA.encrypt("テスト ABCDE_12345");

String dectxt = puttyRSA.decrpt(enctxt);

ppk ファイルの読込みで、こんな複雑なことをしなくてはならず、
Python でこれを書く気になれません。
だから、PuttyRSA に、ppk解析後の

/**
 * RSA公開鍵 Base64エンコード文字列の取得.
 * @return RSA公開鍵 Base64エンコード文字列
 */
public String getPublickeyBase64(){
   return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
/**
 * RSA秘密鍵 Base64エンコード文字列の取得.
 * @return RSA秘密鍵 Base64エンコード文字列
 */
public String getPrivatekeyBase64(){
   return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}

を用意します。

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

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 で暗合化したものも、両方このまま
複合する処理として走らせることができます。

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

先日書いた、
Java と Python 相互で AES 256 暗号/複合を実行する - Oboe吹きプログラマの黙示録
やはり、Python のコードを改める。
複合メソッド、Base64 受け取りとHex文字列受け取りが分かれるより引数で指定することにする。

名称も、どうせ 256 bit 長しか今後も使わないので、cryptoaes256.py でなくて、cryptoaes.py にする。
複合メソッドで渡す形式は文字列型だけにする

# -*- coding: UTF-8 -*-
# AES 256 暗合複合
#   使い方:
#     from cryptoaes import AESCipher
#     CBCモードインスタンス
#           aes = AESCipher(password, bytes.fromhex(iv_hex_string))
#     ECBモードインスタンス
#           aes = AESCipher(password)
#
#     CBCモード 暗合化 → Base64 エンコード
#            enctxt = b64encode(aes.encryptCBC(planetxt)).decode('utf-8')
#     CBCモード 複合 ← Base64エンコード済の暗号文
#            dectxt = aes.decryptCBC(encted_b64_string)
#     CBCモード 複合 ← 16進 Hex表現 の暗号文
#            dectxt = aes.decryptCBC(encted_hexstring, type='hex')
#
#     ECBモード 暗合化 → Base64 エンコード
#            enctxt = b64encode(aes.encryptECB(planetxt)).decode('utf-8')
#     ECBモード 複合 ← Base64エンコード済の暗号文
#            dectxt = aes.decryptECB(encted_b64_string)
#     CBCモード 複合 ← 16進 Hex表現 の暗号文
#            dectxt = aes.decryptECB(encted_hexstring, type='hex')
#
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, iv=Random.new().read(AES.block_size)):
        self.key = hashlib.sha256(password.encode("utf-8")).digest()
        self.iv = iv
    # 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]

    # CBC モード暗号化  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)

    # CBC モード複合  encrypted  base64 or HEX → plane
    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)
        if type=='b64':
            return b64decode(cipher.decrypt(b64decode(enctext))).decode()
        elif type=='hex':
            return re.sub(b'\x08*$', b'', cipher.decrypt(bytes.fromhex(enctext))).decode()
        else:
            raise TypeError("type Error must be 'b64' or 'hex'")

    # ECB モード暗号化  plane → byte
    def encryptECB(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_ECB)
        return cipher.encrypt(raw)

    # ECB モード複合  encrypted  base64 or HEX → plane
    def decryptECB(self, enctxt, type='b64'):
        if enctxt is None or len(enctxt) == 0:
            raise NameError("No value given to decrypt")
        cipher = AES.new(self.key, AES.MODE_ECB)
        if type == 'b64':
            return b64decode(cipher.decrypt(b64decode(enctxt)).decode()).decode()
        elif type == 'hex':
            return re.sub(b'\x08*$', b'', cipher.decrypt(bytes.fromhex(enctxt))).decode()
        else:
            raise TypeError("type Error must be 'b64' or 'hex'")

# password と iv を再セットするための タプルを生成
def createPasswordAndIV():
    import random
    import string
    return  (''.join(random.choices(string.ascii_letters + string.digits, k=16)), Random.new().read(AES.block_size) )

次は、Java の方の見直しだ。