[Java] RSA 공개키 암호화/복호화 예시( RSA 비대칭키 암호화
[Java] RSA 공개키 암호화/복호화 예시( RSA 비대칭키 암호화 )
RSA
-
공개키 암호화 방식 중 하나로 전자서명이 가능한 최초의 알고리즘 RSA
- RSA 공개키 방식은
AES
암호화 방식과 함께 실무에서 가장 많이 사용되고 있는 대표적인 양방향 데이터 암호화 기법 이다. - 양방향 데이터 암호화 기법은 데이터 암호화 이후 원본 데이터로 복호화가 가능 하다는 뜻이다.
- RSA 공개키 방식은
RSA 비대칭키 (공개키) 암호화 방식
-
RSA 공개키 암호화 방식은 메세지를 암호화할 때 사용되는 공개키(Public Key), 암호화된 메세지를 복호화하기 위한 개인키(Private Key)가 존재하며, 두 개의 키는 한쌍으로 생성되어 관리된다.
-
이러한 방식은 데이터 암호화/복호화에 사용되는 키가 서로 다르므로 ‘비대칭키 암호화 알고리즘‘이라고도 많이 부른다.
-
RSA 공개키 암호화 방식은 AES 대칭키 암호화 방식에 비해 속도가 느린 단점을 가지고 있으나 서비스 특성에 따라 성능보다 보안에 강점을 갖는 RSA 공개키 암호화 방식을 사용하게 된다.
-
예를 들면 웹 서비스 접근을 위해 사용되는 SSL/TLS handshake 과정에서 비밀키 (Scret Key) 교환을 위해 RSA 공개키 암호화 방식이 사용된다.
Java로 RSA 공개키 암호화/복호화 구현하기
RSA 공개키 암호화/복호화 Util 생성
package com.work.util;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class RsaUtil {
private static final String INSTANCE_TYPE = "RSA";
// 2048bit RSA KeyPair 생성.
public static KeyPair generateKeypair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(INSTANCE_TYPE);
keyPairGen.initialize(2048, new SecureRandom());
return keyPairGen.genKeyPair();
}
public static String rsaEncode(String plainText, String publicKey)
throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(INSTANCE_TYPE);
cipher.init(Cipher.ENCRYPT_MODE, convertPublicKey(publicKey));
byte[] plainTextByte = cipher.doFinal(plainText.getBytes());
return base64EncodeToString(plainTextByte);
}
public static String rsaDecode(String encryptedPlainText, String privateKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
byte[] encryptedPlainTextByte = Base64.getDecoder().decode(encryptedPlainText.getBytes());
Cipher cipher = Cipher.getInstance(INSTANCE_TYPE);
cipher.init(Cipher.DECRYPT_MODE, convertPrivateKey(privateKey));
return new String(cipher.doFinal(encryptedPlainTextByte));
}
public static PublicKey convertPublicKey(String publicKey)
throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance(INSTANCE_TYPE);
byte[] publicKeyByte = Base64.getDecoder().decode(publicKey.getBytes());
return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyByte));
}
public static PrivateKey convertPrivateKey(String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance(INSTANCE_TYPE);
byte[] privateKeyByte = Base64.getDecoder().decode(privateKey.getBytes());
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));
}
public static String base64EncodeToString(byte[] byteData) {
return Base64.getEncoder().encodeToString(byteData);
}
}
RSA 2048bit로 KeyPair를 생성 1024bit에 비해 생성 속도는 조금 느리지만 보안을 생각하면 2048bit로 선택하는게 좋다.
convertPublicKey, convertPrivateKey 기능은?
public static PublicKey convertPublicKey(String publicKey)
throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance(INSTANCE_TYPE);
byte[] publicKeyByte = Base64.getDecoder().decode(publicKey.getBytes());
return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyByte));
}
public static PrivateKey convertPrivateKey(String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance(INSTANCE_TYPE);
byte[] privateKeyByte = Base64.getDecoder().decode(privateKey.getBytes());
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));
}
비대칭키 PublicKey와 PrivateKey 생성 후 Client에게 공개키를 전달하거나 Server가 개인키를 보관하기 위한 목적으로 보통 String 변환하여 관리한다.
-
물론, File로 만들어 관리하기도 하지만 편의성을 생각하면 String 문자열 형태가 용이하다.
- 한가지 예로 **모바일 웹 서비스에서 E2E 암호화로 RSA 공개키 암호화 방식을 사용한다면?
- 사용자 서비스 접근 시 서버에게 Public Key 발급을 위한 REST API 요청
- 서버는 Key 생성 후 Public Key 사용자에게 응답 처리, Private Key 서버 Session에 보관
- 이후 프로세스는 Client 암호화, Server 복호화 과정이 반복된다.
RSA 암호화/복호화 결과 확인
package com.wor.util;
import java.security.KeyPair;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hyo.test.util.AesUtil;
import com.hyo.test.util.RsaUtil;
class RsaUtilTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static String publicKey;
private static String privateKey;
@Test
@DisplayName("2048bit RSA KeyPair 생성")
void generateKeypair() throws Exception {
KeyPair keyPair = RsaUtil.generateKeypair();
publicKey = RsaUtil.base64EncodeToString(keyPair.getPublic().getEncoded());
privateKey = RsaUtil.base64EncodeToString(keyPair.getPrivate().getEncoded());
}
@Test
@DisplayName("RSA-2048 공개키(비대칭키) 암호화/복호화")
void testRsa2048() throws Exception {
String plainText = "암호화 테스트";
String encryptedPlainText = RsaUtil.rsaEncode(plainText, publicKey);
logger.info("# rsaEncode : {}", encryptedPlainText);
String decryptedPlainText = RsaUtil.rsaDecode(encryptedPlainText, privateKey);
logger.info("# rsaDecode : {}", decryptedPlainText);
}
@Test
@DisplayName("AES-256 대칭키 암호화/복호화")
void testAse256() throws Exception {
String plainText = "암호화 테스트";
String encryptedPlainText = AesUtil.aesCBCEncode(plainText);
logger.info("# aseEncode : {}", encryptedPlainText);
String decryptedPlainText = AesUtil.aesCBCDecode(encryptedPlainText);
logger.info("# aseDecode : {}", decryptedPlainText);
}
}
rsaEncode : R2ZpoGI1ATzi9KEgjZmD5a2Vi1a2EZZh3504jRqiWe5zLDXNVzRCQkkHMlhdihe8BfmoE
wTmjIZNgm6LsMj+CK/kMCZyJ14OpdPo+ZpRPR6ZevB+gKszOaSfH39rWVdAL+g7SJ6b8j ynA0+3sbdvx6Gb6q8tiQEBkseXvx20heEIeiPzMOdsSXOr21e7Mtvpif9ESDdFoe3VCVc nPI3xUwhFA0jtx4zuummnpOW/ZLFE94Yl5D1C5rJcnbTjWgvY/ELJAKmuE82/KDfz+aTg 4EKwgsqwmmJsFaIx2K02jdoUwnhxW+0dQznlb1lUDk3Q+2uP6zm6S+kJ3C/uCT/91Q== # rsaDecode : 암호화 테스트 # aseEncode : 66efb73ba12b7434a696dae62d1d10b26ca92cd2173f6689da43c593ae05cc24 # aseDecode : 암호화 테스트 ```