๐จ RSA ์ํธํ ๋ฐฉ์์ ์ดํด์ ์ ์ฉ (feat.์ทจ์ฝ์ฑ์ ๊ฒ)
1977๋
์ Rivest, Shamir, Adleman ์ด๋ผ๋ ์ธ ๋ช
์ ์ํ์๊ฐ ๊ณ ์ํด์ RSA๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ด ๋ฐฉ์์ ์ง๊ธ๋ SSL/TLS, ์ด๋ฉ์ผ, ๋์งํธ ์๋ช
, ์ธ์ฆ์ ๋ฑ ๋ณด์์ ํต์ฌ ์ธํ๋ผ์์ ๊ณ์ ์ฐ์ด๊ณ ์๋ ๋ฐฉ์์ด๋ค.
ํญ๋ชฉ | ์ค๋ช |
---|---|
ํค์ | ๊ณต๊ฐํค (Public Key), ๊ฐ์ธํค (Private Key) |
์ํธํ | ์ก์ ์๊ฐ ์์ ์์ ๊ณต๊ฐํค๋ก ์ํธํ |
๋ณตํธํ | ์์ ์๋ ์์ ์ ๊ฐ์ธํค๋ก ๋ณตํธํ |
๋ณด์ ๊ทผ๊ฑฐ | ํฐ ์์ ์์ธ์๋ถํด๊ฐ ๋งค์ฐ ์ด๋ ต๋ค๋ ์ํ์ ๋ฌธ์ ์ ๊ธฐ๋ฐ |
์ฉ๋ | ๋ฐ์ดํฐ ์ํธํ, ๋์งํธ ์๋ช , ์ธ์ฆ ๋ฑ |
์ฆ, ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์๋ฒ์์ ๊ณต๊ฐํค(ํด๋ผ์ด์ธํธ์๊ฒ ์ ๊ณตํ ํค) ์ ๊ฐ์ธํค(์๋ฒ์ธก์์ ๊ฐ๊ณ ์๋, ๊ณต๊ฐํค์ ํ์์ด ๋๋ ํค) ๋ฅผ ๋งค๋ฒ ์๋ก ๋ฐ๊ธํ๊ธฐ ๋๋ฌธ์, ์ถ์ ์ด ์ด๋ ต๊ณ ์ํธํ์ ๋ฐ์ด๋๋ค.
PublicKey
, PrivateKey
) ๋ ๋ฉ๋ชจ๋ฆฌ ์์์๋ง ๋์ํ ์ ์๋ค.[๋ก๊ทธ์ธ / ํ์๊ฐ์ ํ์ด์ง]
ํด๋ผ์ด์ธํธ
์์ ์๋ฒ
์ ๊ณต๊ฐํค ์์ฒญ์๋ฒ
์์ ํค์ ๊ณผ KeyUUID ์์ฑํด๋ผ์ด์ธํธ
์ ์ ๋ฌํด๋ผ์ด์ธํธ
๋ ์๋ฒ
๋ก๋ถํฐ ๋ฐ์ ๊ณต๊ฐํค๋ก ํ๋ฌธ ์ํธํ, ์๋ฒ
์ ์ ๋ฌjava.security
ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ค. Java์ ๋ณด์ ํ๋ ์์ํฌ์ ํต์ฌ 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();
}
NoSuchAlgorithmException
: ์ง์ ํ ์๊ณ ๋ฆฌ์ฆ ์ด๋ฆ์ด ํ์ฌ JVM ํ๊ฒฝ์์ ์ง์๋์ง ์๊ฑฐ๋ ์๋ชป๋ ๊ฒฝ์ฐ ๋ฐ์ํ๋ ์ฒดํฌ ์์ธ๋ค.KeyPair
ํ์
: PrivateKey์ PublicKey๋ก ์ด๋ฃจ์ด์ ธ์๋ ๋ฐ์ดํฐ ํ์
SecureRandom
์ ์๋๋ก ์ฌ์ฉํด ๋ณด์ ์์ค ํฅ์2048bit๋ก RSA์ํธํ ๋ฐฉ์์ ์ฌ์ฉํ์ฌ keyPair๋ฅผ ์์ฑํ๋ ์ฝ๋์ด๋ค.
private static final String INSTANCE_TYPE = "RSA";
// ํ๋ฌธ + ๊ณต๊ฐํค Base64๋ก ์ํธ๋ฌธ ์์ฑ
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);
}
//Base64 ๊ณต๊ฐํค -> ๊ณต๊ฐํค๋ก ๋์ฝ๋ฉ
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));
}
Cipher
: Java ๋ณด์ API์์ ์ค์ ์ํธํ/๋ณตํธํ๋ฅผ ์ํํ๋ ํต์ฌ ํด๋์ค
AES, RAS, DES ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ ์คํํ๋ ์ํธ ๋ชจ๋ , ์ํธํ ์์ง ์ด๋ค.
Cipher cipher = Cipher.getInstance(INSTANCE_TYPE);
: ํ์
์ ๋ฐ๋ผ, ์ํธํ ๋ชจ๋์ ํจ๋ฉ ๋ฐฉ์์ด ๊ฒฐ์ ๋๋ค.
convertPublicKey
: base64๊ธฐ๋ฐ ์ฝ๋๋ฅผ ์ค์ ํค ๊ฐ์ฒด๋ก ๋ณํ
KeyFactory
: ํค ๋ณต์์ฉ ํํ ๋ฆฌ ๊ฐ์ฒด
keyFactory.generate...
: ์ค์ ํค ๊ฐ์ฒด ์์ฑ
X509EncodedKeySpec
โ ๊ณต๊ฐํค ํ์ค ํฌ๋งท ์คํ
base64, encode๋ฑ TLSํ๋กํ ์ฝ(์ํธํ ๋๊ตฌ)๋ฅผ ๊ตฌํํ ํจํค์ง ์ด๋ค.
<script src="https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js"></script>
<script>
/**
* ์๋ฒ์์ ๊ณต๊ฐํค๋ฅผ ๋ฐ์์ RSA๋ก ์ํธํํ๋ ํจ์
* @param {string} plainText - ์ํธํํ ํ๋ฌธ
* @param {string} publicKeyBase64 - ์๋ฒ๋ก๋ถํฐ ๋ฐ์ Base64 ์ธ์ฝ๋ฉ๋ ๊ณต๊ฐํค
* @returns {string} ์ํธํ๋ Base64 ๋ฌธ์์ด
*/
function rsaEncryptWithBase64PublicKey(plainText, publicKeyBase64) {
const forge = window.forge;
// 1. Base64 ๋์ฝ๋ฉ โ DER ๋ฐ์ด๋๋ฆฌ
const der = forge.util.decode64(publicKeyBase64);
// 2. DER โ ASN.1 ํ์ฑ โ PublicKey ๊ฐ์ฒด
const asn1 = forge.asn1.fromDer(der);
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
// 3. RSA ์ํธํ
const encryptedBytes = publicKey.encrypt(plainText, 'RSAES-PKCS1-V1_5');
// 4. ์ํธ๋ฌธ์ Base64 ์ธ์ฝ๋ฉํด์ ๋ฐํ
return forge.util.encode64(encryptedBytes);
}
</script>
2.1
๊ณผ ๋์ผ ํ๋ค private static final String INSTANCE_TYPE = "RSA";
// ์ํธ๋ฌธ + ๊ฐ์ธํค Base64๋ก ํ๋ฌธ ์์ฑ
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));
}
//Base64 ๊ฐ์ธํค -> ๊ฐ์ธํค๋ก ๋์ฝ๋ฉ
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));
}
Cipher
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์์ ๋์ผํ๊ฒ ๋์.PKCS8EncodedKeySpec
โ ๊ฐ์ธํค ํ์ค ํฌ๋งท ์คํ public static String base64EncodeToString(byte[] byteData) {
return Base64.getEncoder().encodeToString(byteData);
}
JUnit
โ ํ
์คํธ ํ๋ ์์ํฌ AssertJ
โ ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์ฆ(assert)ํ ๋ ์ฐ๋ ๊ฐ๋ ฅํ assertion ๋๊ตฌJS์ฝ๋๋ ์ ์ธ
private static final String PLAIN_TEXT = "ํค ์/๋ณตํธํ ํ
์คํธ 123 abc !@#";
@Test
@DisplayName("RSA ํค์ ์์ฑ ๋ฐ ์/๋ณตํธํ ํตํฉ ํ
์คํธ")
public void testGenerateKeypairAndEncryptDecrypt() throws Exception {
// ํค์ ์์ฑ
KeyPair keyPair = rsaService.generateKeypair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// ๊ณต๊ฐํค, ๊ฐ์ธํค โ Base64 ์ธ์ฝ๋ฉ
String publicKeyBase64 = rsaService.base64EncodeToString(publicKey.getEncoded());
String privateKeyBase64 = rsaService.base64EncodeToString(privateKey.getEncoded());
System.out.println("๊ณต๊ฐํคBase64 : " + publicKeyBase64);
System.out.println("๊ฐ์ธํคBase64 : " + privateKeyBase64);
// ์ํธํ
String encryptedText = rsaService.rsaEncode(PLAIN_TEXT, publicKeyBase64);
System.out.println("RSA์ํธํ ํ
์คํธ : " + encryptedText);
// ๋ณตํธํ (๊ฐ์ธํค ์ฌ์ฉํด์ผ ํจ)
String decryptedText = rsaService.rsaDecode(encryptedText, privateKeyBase64);
System.out.println("RSA๋ณตํธํ ํ
์คํธ : " + decryptedText);
// ๊ฒ์ฆ
Assertions.assertThat(decryptedText).isEqualTo(PLAIN_TEXT);
}
๊ฒฐ๊ณผ