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());
}

を用意します。