環境
- 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.