반응형
공개키 암호화 알고리즘이란? - RSA
- 메시지를 암호화할때는 공개키(public key), 메시지를 복호화할때는 비공개키(private key)를 사용하는 방식
- 두 개의 키는 한쌍으로 생성되어 관리된다.
- 암호화/복호화에 사용되는 키가 서로 다르므로 비대칭키 암호화 알고리즘이라고 부른다.
- 장점 : 보안성이 높다.
- 단점 : 대칭키 암호화 방식에 비해 속도가 느리다.
- 클라이언트 - 서버간의 공개키 암호화 방식 사용 방식
- 클라이언트가 서버에게 키 발급을 위해 요청
- 서버는 키 생성 후 private key를 서버에 저장, public key를 클라이언트에 전달
- 클라이언트에서는 public key를 활용해 암호화하여 서버에 전달 -> 서버는 private key로 메시지를 복호화하여 사용
RSA 예제
@Getter
@RequiredArgsConstructor
public class RsaKeyPair {
private final String publicKey;
private final String privateKey;
}
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RsaUtil {
private static final String RSA_ALGORITHM = "RSA";
public static RsaKeyPair generateKeyPair(String seed) {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
if (seed == null) {
generator.initialize(2048);
} else {
// seed가 동일하면 generateKeyPair() 호출시 이전과 동일한 publicKey, privateKey를 생성
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(seed.getBytes(StandardCharsets.UTF_8));
generator.initialize(2048, secureRandom);
}
KeyPair keyPair = generator.genKeyPair();
return new RsaKeyPair(
encodeBase64(keyPair.getPublic().getEncoded()),
encodeBase64(keyPair.getPrivate().getEncoded())
);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static String encode(String publicKey, String text) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, generatePublicKey(publicKey));
return encodeBase64(cipher.doFinal(text.getBytes()));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static String decode(String publicKey, String encodedText) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, generatePrivateKey(publicKey));
return new String(cipher.doFinal(Base64.getDecoder().decode(encodedText.getBytes())));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static PublicKey generatePublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey.getBytes());
return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
}
private static PrivateKey generatePrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey.getBytes());
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
}
private static String encodeBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
}
public class RsaDemo {
public static void main(String[] args) {
RsaKeyPair keyPair = RsaUtil.generateKeyPair(null);
String text = "RSA - 공개키(비대칭키) 알고리즘 테스트";
String encoded = RsaUtil.encode(keyPair.getPublicKey(), text);
String decoded = RsaUtil.decode(keyPair.getPrivateKey(), encoded);
System.out.println(encoded); // yIvPE0ooOEzFgMjh/MeyX/70HHUEgx1EhpkQmat....
System.out.println(decoded); // RSA - 공개키(비대칭키) 알고리즘 테스트
RsaKeyPair keyPair2 = RsaUtil.generateKeyPair("Hi");
// String decoded2 = RsaUtil.decode(keyPair2.getPrivateKey(), encoded); // A 공개키로 암호화한 데이터를 B 개인키로 복호화 시도할 경우 javax.crypto.BadPaddingException: Decryption error 발생
}
}
AES란?
- 암복호화시 동일한 키를 사용하는 대칭키 알고리즘
- 비대칭키 알고리즘보다 속도가 빠르다는 장점이 있고, 키 한개로 암복호화를 하기 때문에 키가 유출될 경우 문제가 발생할 수 있다는 단점이 있다.
- AES-128, AES-192, AES-256이 있고 뒤의 숫자는 키의 길이를 의미
- 높은 안정성과 빠른 속도로 전세계적으로 많이 사용
- SecretKey는 암복호화 하는데 사용하는 키이므로 외부에 노출되어서는 안되고, AES 종류에 따라 Key의 길이가 달라진다.(AES-256은 256비트(32바이트) 길이의 키를 사용)
- AES는 128비트(16바이트)의 고정된 블록 단위로 암호화를 수행. (암호화 키의 길이와는 무관함)
- Block Cipher Mode는 CBC, ECB 방식이 있음. CBC 방식을 권장함.
- CBC 방식
- CBC 방식은 이전에 암호화했던 블록과 XOR 연산을 하여 암호화를 수행.
- 첫 번째 블록은 이전 암호화 블록이 없기 때문에 이를 위해 IV(Initialization Vector)를 이용
- 암호화 블록 사이즈는 128비트(16바이트)이기 때문에 IV도 128비트(16바이트) 길이로 사용해야함
- IV가 변경되면 같은 평문이라도 다른 암호문을 생성할 수 있음
- 128비트(16바이트)보다 작은 블록이 생길 경우 부족한 부분을 특정 값을 채우는 패딩 작업을 수행
- 패딩은 PKCS5, PKCS7 방식이 있음
CipherWithAesTest
>import org.junit.jupiter.api.Test;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CipherWithAes256Test {
@Test
public void testAes256() throws Exception {
String key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; // 32bytes (AES-256 방식은 256비트(32바이트) 길이의 값 사용)
String iv = "1234567890ABCDEF"; // 16bytes (CBC 방식은 128비트(16바이트) 길이의 값 사용)
String encryptedText = encrypt(key, iv, "Hello World");
String decryptedText = decrypt(key, iv, encryptedText);
assertEquals("Hello World", decryptedText);
}
private static String encrypt(String key, String iv, String decryptedText) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(
Cipher.ENCRYPT_MODE,
new SecretKeySpec(key.getBytes(), "AES"),
new IvParameterSpec(iv.getBytes())
);
byte[] encryptedBytes = cipher.doFinal(decryptedText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
private static String decrypt(String key, String iv, String encryptedText) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(
Cipher.DECRYPT_MODE,
new SecretKeySpec(key.getBytes(), "AES"),
new IvParameterSpec(iv.getBytes())
);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
참고
반응형
'Development > Java' 카테고리의 다른 글
[Java] Mockito (0) | 2022.06.23 |
---|---|
[Java] Random (0) | 2022.06.21 |
[Java] Base64 (0) | 2022.04.26 |
[Java] Singleton Pattern (0) | 2022.01.07 |
[Java] JUnit5 (0) | 2021.12.08 |