Java
RSA
RSA暗号

java 8 での RSA 暗号化・復号化

環境

  • Java 8
  • RSA暗号

背景

java8 で RSAの暗号化・復号化をしたい!
後ほど Wicket で暗号化されたリクエストの復号化をする想定です。

2048bit 鍵準備

  • 秘密鍵生成
console
openssl genrsa -out rsa_2048_priv.pem 2048
  • 公開鍵生成
console
openssl rsa -pubout -in rsa_2048_priv.pem -out rsa_2048_pub.pem

生成したファイルはリソースフォルダに保存します

暗号化・復号化用クラス

RSAUtil.java
public final class RSAUtil {

    public static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
    public static ConcurrentHashMap<String, PublicKey> pubCache = new ConcurrentHashMap<String, PublicKey>();
    public static ConcurrentHashMap<Object, PrivateKey> priCache = new ConcurrentHashMap<Object, PrivateKey>();

    /**
     * 公開鍵によるRSA暗号化
     * @param plain
     * @param key
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    public static String encryptWithPublicKey(String plain, PublicKey key) throws IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException {

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte[] plainBytes = plain.getBytes("UTF-8");
        String result = Base64.encodeBase64String(cipher.doFinal(plainBytes));
        return result;
    }

    /**
     * 秘密鍵によるRSA復号化
     * @param encrypted
     * @param key
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    public static String decryptWithPrivateKey(String encrypted, PrivateKey key) throws IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException {

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, key);

        byte[] encryptedBytes = Base64.decodeBase64(encrypted);
        String result = new String(cipher.doFinal(encryptedBytes), "UTF-8");
        return result;
    }

    /**
     * 公開鍵を pem ファイルから読み込む
     * @param filename
     * @return
     */
    public static PublicKey readPublicKeyFromPem(String filename) {
        PublicKey key = null;
        InputStream is = null;

        // check cache
        if (pubCache.containsKey(filename)) {
            key = (PublicKey) pubCache.get(filename);
            if (key != null) {
                return key;
            }
        }

        try {
            is = RSAUtil.class.getClassLoader().getResourceAsStream(filename);
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            StringBuilder sb = new StringBuilder();
            boolean inKey = false;

            for (String line = br.readLine(); line != null; line = br.readLine()) {
                if (!inKey) {
                    if (line.startsWith("-----BEGIN ") && line.endsWith(" PUBLIC KEY-----")) {
                        inKey = true;
                    }
                    continue;
                }
                else {
                    if (line.startsWith("-----BEGIN ") && line.endsWith(" PUBLIC KEY-----")) {
                        inKey = false;
                        break;
                    }
                    else {
                        sb.append(line);
                    }
                }
            }
            byte[] decoded = DatatypeConverter.parseBase64Binary(sb.toString());
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            key = kf.generatePublic(keySpec);

            pubCache.put(filename, key);
        }
        catch (Exception e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            logger.error("ERROR:\n"+sw.toString());
        }
        finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {}
            }
        }
        return key;
    }

    /**
     * 秘密鍵を pem ファイルから読み込む
     * @param filename
     * @return
     */
    public static PrivateKey readPrvateKeyFromPem(String filename) {
        PrivateKey key = null;
        InputStream is = null;
        boolean isRSAKey = false;

        if (priCache.containsKey(filename)) {
            key = (PrivateKey) priCache.get(filename);
            if (key != null) {
                return key;
            }
        }

        try {
            is = RSAUtil.class.getClassLoader().getResourceAsStream(filename);
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            StringBuilder sb = new StringBuilder();
            boolean inKey = false;

            for (String line = br.readLine(); line != null; line = br.readLine()) {
                if (!inKey) {
                    if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
                        inKey = true;
                        isRSAKey = line.contains("RSA");
                    }
                    continue;
                }
                else {
                    if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
                        inKey = false;
                        isRSAKey = line.contains("RSA");
                        break;
                    }
                    else {
                        sb.append(line);
                    }
                }
            }
            byte[] decoded = DatatypeConverter.parseBase64Binary(sb.toString());
            KeySpec keySpec = null;
            if (isRSAKey) {
                DerInputStream derReader = new DerInputStream(decoded);
                DerValue[] seq = derReader.getSequence(0);
                if (seq.length < 9) {
                    throw new GeneralSecurityException("Could not parse a PKCS1 private key.");
                }
                // skip version seq[0]
                BigInteger modulus = seq[1].getBigInteger();
                BigInteger publicExp = seq[2].getBigInteger();
                BigInteger privateExp = seq[3].getBigInteger();
                BigInteger prime1 = seq[4].getBigInteger();
                BigInteger prime2 = seq[5].getBigInteger();
                BigInteger exponent1 = seq[6].getBigInteger();
                BigInteger exponent2 = seq[7].getBigInteger();
                BigInteger crtCoef = seq[8].getBigInteger();

                keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2,
                        exponent1, exponent2, crtCoef);
            } else {
                keySpec = new PKCS8EncodedKeySpec(decoded);
            }
            KeyFactory kf = KeyFactory.getInstance("RSA");
            key = kf.generatePrivate(keySpec);

            priCache.put(filename, key);
        }
        catch (Exception e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            logger.error("ERROR:\n"+sw.toString());
        }
        finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {}
            }
        }
        return key;
    }
}

使ってみる

TestRSAEncryptDecrypt.java
public class TestRSAEncryptDecrypt {

    public static void main(String[] args) {

        final String prvPemfile = "rsa_2048_priv.pem";
        final String pubPemFile = "rsa_2048_pub.pem";

        final String plain = "THIS is a test.";

        PublicKey pubKey = RSAUtil.readPublicKeyFromPem(pubPemFile);
        PrivateKey prvKey = RSAUtil.readPrvateKeyFromPem(prvPemfile);

        String encrypted = "";
        try {
            encrypted = RSAUtil.encryptWithPublicKey(plain, pubKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Encrypted:" + encrypted);

        String decrypted = "";
        try {
            decrypted = RSAUtil.decryptWithPrivateKey(encrypted, prvKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Decrypted:" + decrypted);
    }

}

結果

console
Encrypted:P3X8B1A+N+MEt7+oSLZ+FsbpS1ddkXIz1zpiDAuTf6/EDEPK9ozPZ+VGeaKvDucj3tkVX5YLvXU4psApuyt/NGHlzbAQryaArUGxK7Ol8Nfpp+l5yUwwi2EG6J85QZWtigH8ITmjvIf+lO8676kyg7snF2MXsIx2fNoen4A8bO8gDJ5/Dh4RkPFT8AUZFz4chVQ4whl1vdegHNO2qWj8x3+2UegqGO7QIE6P57BHGbsyF/3Dpept7Gr1NFUpN43DYA/h+IgRu0OeLCx6sss0MWokK1C+1OOam59pEpNhefIV8wzCaag7N+SfSBXGKViGN25vBfOUhC7dZfYheX7BUQ==
Decrypted:THIS is a test.