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()); }
を用意します。