๐Ÿšจ RSA ์•”ํ˜ธํ™” ๋ฐฉ์‹์˜ ์ดํ•ด์™€ ์ ์šฉ (feat.์ทจ์•ฝ์„ฑ์ ๊ฒ€)


RSA ์•”ํ˜ธํ™”๋Š” ๋น„๋Œ€์นญ ํ‚ค ์•”ํ˜ธํ™” ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.

1977๋…„์— Rivest, Shamir, Adleman ์ด๋ผ๋Š” ์„ธ ๋ช…์˜ ์ˆ˜ํ•™์ž๊ฐ€ ๊ณ ์•ˆํ•ด์„œ RSA๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
์ด ๋ฐฉ์‹์€ ์ง€๊ธˆ๋„ SSL/TLS, ์ด๋ฉ”์ผ, ๋””์ง€ํ„ธ ์„œ๋ช…, ์ธ์ฆ์„œ ๋“ฑ ๋ณด์•ˆ์˜ ํ•ต์‹ฌ ์ธํ”„๋ผ์—์„œ ๊ณ„์† ์“ฐ์ด๊ณ  ์žˆ๋Š” ๋ฐฉ์‹์ด๋‹ค.

์ •์˜

  • RSA๋Š” "๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”ํ•˜๊ณ , ๊ฐœ์ธํ‚ค๋กœ ๋ณตํ˜ธํ™”" ๋˜๋Š” ๊ทธ ๋ฐ˜๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š” ๋น„๋Œ€์นญ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‹ค.

ํ•ต์‹ฌ ๊ฐœ๋…

ํ•ญ๋ชฉ ์„ค๋ช…
ํ‚ค์Œ ๊ณต๊ฐœํ‚ค (Public Key), ๊ฐœ์ธํ‚ค (Private Key)
์•”ํ˜ธํ™” ์†ก์‹ ์ž๊ฐ€ ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”
๋ณตํ˜ธํ™” ์ˆ˜์‹ ์ž๋Š” ์ž์‹ ์˜ ๊ฐœ์ธํ‚ค๋กœ ๋ณตํ˜ธํ™”
๋ณด์•ˆ ๊ทผ๊ฑฐ ํฐ ์ˆ˜์˜ ์†Œ์ธ์ˆ˜๋ถ„ํ•ด๊ฐ€ ๋งค์šฐ ์–ด๋ ต๋‹ค๋Š” ์ˆ˜ํ•™์  ๋ฌธ์ œ์— ๊ธฐ๋ฐ˜
์šฉ๋„ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”, ๋””์ง€ํ„ธ ์„œ๋ช…, ์ธ์ฆ ๋“ฑ

๊ทธ๋ž˜์„œ ์™œ ๋ณด์•ˆ์— ํšจ๊ณผ์ ์ธ๊ฑธ๊นŒ?

  • ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”๋œ ๋ฐ์ดํ„ฐ๋Š” ๊ฐœ์ธํ‚ค๋ฅผ ์†Œ์œ ํ•œ ๊ณต๊ฐœํ‚ค ์ƒ์„ฑ์ž๋งŒ์ด ๋ณตํ˜ธํ™” ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์‚ฌ์šฉ์ž( Client )๋งˆ๋‹ค ๊ณต๊ฐœํ‚ค์™€ ๊ฐœ์ธํ‚ค๋ฅผ ์ ‘๊ทผ ์‹œ ์ƒˆ๋กœ ์ƒ์„ฑ/๋ฐœ๊ธ‰ํ•˜์—ฌ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ถ”์ ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค.
๊ฒฐ๋ก 

์ฆ‰, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์„œ๋ฒ„์—์„œ ๊ณต๊ฐœํ‚ค(ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ œ๊ณตํ•  ํ‚ค) ์™€ ๊ฐœ์ธํ‚ค(์„œ๋ฒ„์ธก์—์„œ ๊ฐ–๊ณ ์žˆ๋Š”, ๊ณต๊ฐœํ‚ค์™€ ํ•œ์Œ์ด ๋˜๋Š” ํ‚ค) ๋ฅผ ๋งค๋ฒˆ ์ƒˆ๋กœ ๋ฐœ๊ธ‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ถ”์ ์ด ์–ด๋ ต๊ณ  ์•”ํ˜ธํ™”์— ๋›ฐ์–ด๋‚˜๋‹ค.

๋‹จ, AES(๋Œ€์นญ ์•”ํ˜ธํ™”)์™€ ๋ฐฉ์‹๋ณด๋‹ค ์„ฑ๋Šฅ(์†๋„)์ธก๋ฉด์—์„œ ๋–จ์–ด์ง„๋‹ค.

  • ์„œ๋น„์Šค ํŠน์„ฑ์— ๋”ฐ๋ผ ์ ์ ˆํ•˜๊ฒŒ ์ ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

Java/JS ํ™˜๊ฒฝ์—์„œ RSA์•”ํ˜ธํ™” ์ ์šฉํ•˜๊ธฐ

์ •๋ณด
  • RSA ํ‚ค ๊ฐ์ฒด (PublicKey, PrivateKey) ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ƒ์—์„œ๋งŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์šฐ๋ฆฌ๊ฐ€ ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ, DB๋กœ ํ‚ค๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ๋•Œ๋Š” Base64 ๋ฌธ์ž์—ด ๋กœ ์ง๋ ฌํ™”ํ•ด์•ผ ํ•ด์•ผํ•œ๋‹ค.

์ „๋ฐ˜์ ์ธ ๋กœ์ง ํ๋ฆ„

[๋กœ๊ทธ์ธ / ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€]

  1. ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„์— ๊ณต๊ฐœํ‚ค ์š”์ฒญ
  2. ์„œ๋ฒ„์—์„œ ํ‚ค์Œ ๊ณผ KeyUUID ์ƒ์„ฑ
  3. ๊ฐœ์ธํ‚ค๋Š” KeyUUID์™€ ํ•จ๊ป˜ ์บ์‰ฌ์— ์ €์žฅ / ๊ณต๊ฐœํ‚ค๋Š” KeyUUID์™€ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ
  4. ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๊ณต๊ฐœํ‚ค๋กœ ํ‰๋ฌธ ์•”ํ˜ธํ™”, ์„œ๋ฒ„์— ์ „๋‹ฌ
  5. ์•”ํ˜ธํ™”๋œ ํ‰๋ฌธ์„ KeyUUID๋กœ ์ฐพ์€ ๊ณต๊ฐœํ‚ค๋กœ ๋ณตํ˜ธํ™” -> ํ‰๋ฌธ ์™„์„ฑ

1. ํ‚ค ์Œ ๋ฐœ๊ธ‰ ๋กœ์ง

ํ•˜๋‚˜์˜ ์Œ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๊ณต๊ฐœํ‚ค(public key) ์™€ ๊ฐœ์ธํ‚ค(private key) ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋กœ์ง

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();  
    }  
  • KeyPairGenerator (๊ณต๊ฐœํ‚ค/๊ฐœ์ธํ‚ค ์Œ ์ƒ์„ฑ) ์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • NoSuchAlgorithmException : ์ง€์ •ํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ด๋ฆ„์ด ํ˜„์žฌ JVM ํ™˜๊ฒฝ์—์„œ ์ง€์›๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋Š” ์ฒดํฌ ์˜ˆ์™ธ๋‹ค.
  • KeyPairํƒ€์ž… : PrivateKey์™€ PublicKey๋กœ ์ด๋ฃจ์–ด์ ธ์žˆ๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…
  • SecureRandom์„ ์‹œ๋“œ๋กœ ์‚ฌ์šฉํ•ด ๋ณด์•ˆ ์ˆ˜์ค€ ํ–ฅ์ƒ

2048bit๋กœ RSA์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ keyPair๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.


2.1 ํ‰๋ฌธ + base64๊ณต๊ฐœํ‚ค -> base64์•”ํ˜ธ๋ฌธ ์ƒ์„ฑ ๋กœ์ง .Java

	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 โ†’ ๊ณต๊ฐœํ‚ค ํ‘œ์ค€ ํฌ๋งท ์ŠคํŽ™


2.2 ํ‰๋ฌธ + base64๊ณต๊ฐœํ‚ค -> base64์•”ํ˜ธ๋ฌธ ์ƒ์„ฑ ๋กœ์ง .JS

node-forge ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

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>
  • ํด๋ผ์ด์–ธํŠธ ๋‹จ์—์„œ request์ „๋‹ฌ ์ „, ์•”ํ˜ธํ™” ํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ / ๋™์ž‘ ๊ตฌ์„ฑ์€ 2.1๊ณผ ๋™์ผ ํ•˜๋‹ค

3. base64์•”ํ˜ธ๋ฌธ + base64๊ฐœ์ธํ‚ค -> ํ‰๋ฌธ ์ƒ์„ฑ ๋กœ์ง

	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 โ†’ ๊ฐœ์ธํ‚ค ํ‘œ์ค€ ํฌ๋งท ์ŠคํŽ™

+ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ Base64๋กœ ์ธ์ฝ”๋”ฉ .java

  public static String base64EncodeToString(byte[] byteData) {
    return Base64.getEncoder().encodeToString(byteData);
  }
  • cipher.doFinal(...) ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋กœ ๋ฆฌํ„ด๊ฐ’์„ ๋ณด๋‚ธ๋‹ค.

๋ฐฑ๋กœ์ง ๋ฐ ํ”„๋ก ํŠธ ๊ฒ€์ฆ ์™„๋ฃŒ

junit๊ณผ assertj๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ฆ

  • 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);  
}

๊ฒฐ๊ณผ
Pasted image 20250325102427.png

  • Base64๊ธฐ๋ฐ˜ String ๋ณ€ํ™˜ ๋ฐ ์•”/๋ณตํ˜ธํ™” ํ…Œ์ŠคํŠธ ์™„๋ฃŒ
  • js์•”ํ˜ธํ™”๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ ํ™•์ธ

+ ๋กœ๊ทธ์ธ / ํšŒ์›๊ฐ€์ž… ์„œ๋น„์Šค ์ ์šฉ ์™„๋ฃŒ

Pasted image 20250325165845.png
Pasted image 20250325172818.png

Key ๊ด€๋ฆฌ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ