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

## 環境
- Java 8
- RSA暗号

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

## 2048bit 鍵準備
- 秘密鍵生成

```console:console
openssl genrsa -out rsa_2048_priv.pem 2048
```
- 公開鍵生成

```console:console
openssl rsa -pubout -in rsa_2048_priv.pem -out rsa_2048_pub.pem
```
生成したファイルはリソースフォルダに保存します

## 暗号化・復号化用クラス
```java: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;
	}
}
```
## 使ってみる
```java: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:console
Encrypted:P3X8B1A+N+MEt7+oSLZ+FsbpS1ddkXIz1zpiDAuTf6/EDEPK9ozPZ+VGeaKvDucj3tkVX5YLvXU4psApuyt/NGHlzbAQryaArUGxK7Ol8Nfpp+l5yUwwi2EG6J85QZWtigH8ITmjvIf+lO8676kyg7snF2MXsIx2fNoen4A8bO8gDJ5/Dh4RkPFT8AUZFz4chVQ4whl1vdegHNO2qWj8x3+2UegqGO7QIE6P57BHGbsyF/3Dpept7Gr1NFUpN43DYA/h+IgRu0OeLCx6sss0MWokK1C+1OOam59pEpNhefIV8wzCaag7N+SfSBXGKViGN25vBfOUhC7dZfYheX7BUQ==
Decrypted:THIS is a test.
```