λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
[JAVA]

μžλ°”μ—μ„œ RSA ν‚€ 생성, SHA-256을 ν™œμš©ν•œ μ „μžμ„œλͺ… κ΅¬ν˜„μ„ κ°„λ‹¨ν•˜κ²Œ λ”°λΌν•˜κ³  깊이있게 μ•Œμ•„λ³΄μž.

by νŒ‘νŽ‘ν 2023. 10. 4.
728x90

πŸ“Œ RSA(Rivest-Shamir-Adleman)λž€?

  • μ•”ν˜Έν™”μ™€ λ³΄μ•ˆ λΆ„μ•Όμ—μ„œ 널리 μ‚¬μš©λ˜λŠ” κ³΅κ°œν‚€ μ•”ν˜Έν™” μ•Œκ³ λ¦¬μ¦˜μ΄λ‹€. 

 

πŸ“Œ μ•ˆμ „μ„±κ³Ό ν™œμš©

  • RSA의 μ•ˆμ „μ„±μ€ 큰 μ •μˆ˜ μΈμˆ˜λΆ„ν•΄ λ¬Έμ œμ— κΈ°λ°˜ν•œλ‹€.
  • 데이터 λ³΄μ•ˆλΏλ§Œ μ•„λ‹ˆλΌ 인증과 λ””μ§€ν„Έ μ„œλͺ… λ“± λ‹€μ–‘ν•œ λ³΄μ•ˆ μ‘μš© λΆ„μ•Όμ—μ„œ ν™œμš©λœλ‹€.
  • ν˜„μž¬κΉŒμ§€λ„ 널리 μ‚¬μš©λ˜λ©° μ‹€μƒν™œμ—μ„œ 많이 μ ‘ν•˜λŠ” λ³΄μ•ˆ ν”„λ‘œμ„ΈμŠ€ 쀑 ν•˜λ‚˜μ΄λ‹€.

 

πŸ“Œ κ³΅κ°œν‚€μ™€ κ°œμΈν‚€

  • RSAλŠ” κ³΅κ°œν‚€μ™€ κ°œμΈν‚€λΌλŠ” 두 κ°€μ§€ ν‚€λ₯Ό μ‚¬μš©ν•œλ‹€.
  • 두 ν‚€λ₯Ό μ΄μš©ν•΄ μ „μžμ„œλͺ…κ³Ό μ•”ν˜Έν™”λ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€.
  • 두 ν‚€λŠ” μ„œλ‘œ νŽ˜μ–΄(짝)λ₯Ό 이룬닀.
  • κ³΅κ°œν‚€λŠ” λˆ„κ΅¬μ—κ²Œλ‚˜ μ˜€ν”ˆν•˜λŠ” 킀이닀.
    • κ³΅κ°œν‚€λ‘œ μ•”ν˜Έν™”λœ λ°μ΄ν„°λŠ” κ°œμΈν‚€λ‘œλ§Œ 해독할 수 μžˆλ‹€.
  • κ°œμΈν‚€λŠ” ν‚€λ₯Ό μƒμ„±ν•œ 개인만이 κ°€μ§€κ³  μžˆλŠ” 킀이며 외뢀에 μ ˆλŒ€ λ…ΈμΆœλ˜μ–΄μ„œλŠ” μ•ˆ λœλ‹€.
    • κ°œμΈν‚€λ‘œ μ„œλͺ…λœ λ°μ΄ν„°λŠ” κ³΅κ°œν‚€λ‘œλ§Œ 해독할 수 μžˆλ‹€.

 

πŸ“Œ ν‚€ 생성

  • RSA ν‚€ νŽ˜μ–΄(κ³΅κ°œν‚€-κ°œμΈν‚€ 쌍)λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ†Œμˆ˜(p, q) 선택과 κ΄€λ ¨λœ 연산이 ν•„μš”ν•˜λ‹€.
  • p와 qλ₯Ό μ„ νƒν•˜κ³ , 이듀을 λ°”νƒ•μœΌλ‘œ κ³΅κ°œν‚€(e, n)와 κ°œμΈν‚€(d, n)λ₯Ό κ³„μ‚°ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ§Œλ“€μ–΄μ§„λ‹€.
  • μžμ„Έν•œ 건 ꡬ글링을 톡해 μ•Œμ•„λ³΄μž πŸ˜‰

 

πŸ“Œ RSA μ „μžμ„œλͺ…을 μ˜ˆμ‹œλ‘œ μ΄ν•΄ν•˜κΈ°

  • μ „μžμ„œλͺ…μ—μ„œ μ„œλͺ…μ΄λž€ μ•”ν˜Έν™”ν•œλ‹€λŠ” 말과 κ°™λ‹€.
  • μ² μˆ˜λŠ” RSA λ°©μ‹μ˜ κ³΅κ°œν‚€μ™€ κ°œμΈν‚€λ₯Ό λ§Œλ“€μ—ˆλ‹€.
  • μ² μˆ˜λŠ” 데이터λ₯Ό μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ„œλͺ…(μ•”ν˜Έν™”)ν–ˆλ‹€.
  • μ² μˆ˜λŠ” μ˜ν¬μ—κ²Œ <μžμ‹ μ˜ κ³΅κ°œν‚€>와, <μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”λœ 데이터>λ₯Ό μ£Όμ—ˆλ‹€.
  • μ˜ν¬λŠ” <철수의 κ³΅κ°œν‚€>와, <철수의 κ°œμΈν‚€λ‘œ μ•”ν˜Έν™” 된 데이터>λ₯Ό λ°›μ•˜λ‹€.
  • μ˜ν¬λŠ” <철수의 κ³΅κ°œν‚€>둜 <μ•”ν˜Έν™”λœ 데이터>λ₯Ό λ³΅ν˜Έν™”ν•¨μœΌλ‘œμ¨ μ² μˆ˜κ°€ ν•΄λ‹Ή 데이터λ₯Ό λ§Œλ“€μ—ˆμŒμ„ μ‹ λ’°ν•  수 μžˆλ‹€.
    • 철수의 κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”λ˜μ—ˆλ‹€λŠ” μ΄μ•ΌκΈ°λŠ” ν•΄λ‹Ή 데이터가 철수의 κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”λ˜μ—ˆλ‹€λŠ” 이야기이닀.
    • 철수만이 κ°€μ§€κ³  μžˆλŠ” κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”λ˜μ—ˆμœΌλ―€λ‘œ 철수의 λ°μ΄ν„°μž„μ„ μ‹ λ’°ν•  수 μžˆλŠ” 것이닀.

 

πŸ’‘ μ „μžμ„œλͺ…μ˜ 이점

  • 무결성: μ„œλͺ…λœ 데이터가 λ³€κ²½λ˜μ§€ μ•Šμ•˜μŒμ„ 검증할 수 μžˆλ‹€.
  • 인증: μ„œλͺ…이 μœ νš¨ν•œ 경우, ν•΄λ‹Ή 데이터λ₯Ό μƒμ„±ν•œ μ‚¬λžŒ(철수)μž„μ„ 확인할 수 μžˆλ‹€.
  • 뢀인 λ°©μ§€: 철수만이 μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ„œλͺ…ν•  수 μžˆμœΌλ―€λ‘œ, λ‚˜μ€‘μ— λΆ€μ •ν•˜κ±°λ‚˜ λΆ€μΈν•˜λŠ” 것이 μ–΄λ €μ›Œμ§„λ‹€.

 

πŸ“Œ RSA μ•”ν˜Έν™”λ₯Ό μ˜ˆμ‹œλ‘œ μ΄ν•΄ν•˜κΈ°

  • μ˜ν¬λŠ” λŒ€μΉ­ν‚€λ₯Ό μƒμ„±ν–ˆλ‹€.
  • μ˜ν¬λŠ” <철수의 κ³΅κ°œν‚€>둜 <λŒ€μΉ­ν‚€>λ₯Ό μ•”ν˜Έν™”ν•˜μ—¬ μ² μˆ˜μ—κ²Œ λ³΄λƒˆλ‹€.
  • ν•΄λ‹Ή λ°μ΄ν„°λŠ” 철수의 κ°œμΈν‚€λ‘œλ§Œ λ³΅ν˜Έν™”κ°€ κ°€λŠ₯ν•˜λ―€λ‘œ 철수만이 μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ—΄ 수 μžˆλ‹€.
  • μ² μˆ˜λŠ” μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ˜ν¬μ—κ²Œ 받은 데이터λ₯Ό λ³΅ν˜Έν™”ν–ˆλ‹€.
  • λ³΅ν˜Έν™”ν•œ λ°μ΄ν„°λŠ” μ˜ν¬κ°€ λ§Œλ“  λŒ€μΉ­ν‚€μ΄λ―€λ‘œ 이후 μ² μˆ˜μ™€ μ˜ν¬λŠ” ν•΄λ‹Ή λŒ€μΉ­ν‚€λ‘œ μ•ˆμ „ν•˜κ²Œ 데이터λ₯Ό μ•”λ³΅ν˜Έν™”ν•˜μ—¬ 주고받을 수 있게 λ˜μ—ˆλ‹€. 

 

πŸ“Œ μžλ°”μ—μ„œ RSA ν‚€ μƒμ„±ν•˜κΈ°

// RSA 생성
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class RsaGenerator {

    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        // 1. ν˜„μž¬ μ‹œκ°„μ„ μ΄μš©ν•˜μ—¬ seed 값을 μƒμ„±ν•©λ‹ˆλ‹€.
        String key = String.valueOf(System.currentTimeMillis());

        // 2. SecureRandom 객체λ₯Ό μƒμ„±ν•˜κ³  seed 값을 μ μš©ν•©λ‹ˆλ‹€.
        SecureRandom random = new SecureRandom(key.getBytes());

        // 3. KeyPairGenerator 객체λ₯Ό μƒμ„±ν•˜κ³  RSA μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜μ—¬ μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€.
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        
        // 4. ν‚€μŒμ˜ μ΄ˆκΈ°ν™” 섀정을 (keySize : 2048, μœ„μ—μ„œ μ„€μ •ν•œ 랜덀 seed κ°’)으둜 ν•©λ‹ˆλ‹€.
        keyGen.initialize(2048, random);

        // 5. KeyPairGeneratorλ₯Ό μ‚¬μš©ν•˜μ—¬ κ³΅κ°œν‚€μ™€ κ°œμΈν‚€μ˜ 쌍인 KeyPairλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
        return keyGen.generateKeyPair();
    }
}

// μ‚¬μš© μ˜ˆμ‹œ(Base64둜 인코딩)
public class test {

	public void testMethod() {
    	String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
    	String secretKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
    	
        ...
    }
}
  • Javaμ—μ„œλŠ” Rsa ν‚€ μŒμ„ λ§Œλ“€ 수 μžˆλŠ” 클래슀λ₯Ό μ§€μ›ν•œλ‹€.
  • 1 , 2λ²ˆμ€ ν‚€μŒμ„ λ§Œλ“€κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” λ‚œμˆ˜λ₯Ό μƒμ„±ν•˜λŠ” 과정이닀.
  • 3λ²ˆμ€ KeyPairGeneratorλΌλŠ” 객체λ₯Ό μƒμ„±ν•˜κ³  μ•Œκ³ λ¦¬μ¦˜μ„ RSA둜 μ‚¬μš©ν•˜μ—¬ μ΄ˆκΈ°ν™”ν•œλ‹€.
  • 4λ²ˆμ—μ„œ initialize λ©”μ„œλ“œμ˜ νŒŒλΌλ―Έν„° 값은 (int keysize, SecureRandom random)이닀.
    • μžμ„Έν•œ λ‚΄μš©μ„ μ•„λž˜μ˜ KeyPairGenerator ν΄λž˜μŠ€μ—μ„œ μ„€λͺ…ν•œλ‹€.
  • μ‚¬μš© μ˜ˆμ‹œμ—μ„œ μƒμ„±λœ ν‚€ νŽ˜μ–΄λ₯Ό Base64λ₯Ό μ΄μš©ν•΄ μΈμ½”λ”©ν•˜μ—¬ λ¬Έμžμ—΄ νƒ€μž…μœΌλ‘œ ν‚€λ₯Ό μƒμ„±ν•˜κ³  μžˆμŒμ„ 확인할 수 μžˆλ‹€.
  • λ¬Όλ‘  Hex λ“±μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ μ‚¬μš©ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.

 

πŸ“– KeyPairGenerator 클래슀

    /**
     * Initializes the key pair generator for a certain keysize using
     * a default parameter set and the {@code SecureRandom}
     * implementation of the highest-priority installed provider as the source
     * of randomness.
     * (If none of the installed providers supply an implementation of
     * {@code SecureRandom}, a system-provided source of randomness is
     * used.)
     *
     * @param keysize the keysize. This is an
     * algorithm-specific metric, such as modulus length, specified in
     * number of bits.
     *
     * @throws    InvalidParameterException if the {@code keysize} is not
     * supported by this KeyPairGenerator object.
     */
    public void initialize(int keysize) {
        initialize(keysize, JCAUtil.getDefSecureRandom());
    }

    /**
     * Initializes the key pair generator for a certain keysize with
     * the given source of randomness (and a default parameter set).
     *
     * @param keysize the keysize. This is an
     * algorithm-specific metric, such as modulus length, specified in
     * number of bits.
     * @param random the source of randomness.
     *
     * @throws    InvalidParameterException if the {@code keysize} is not
     * supported by this KeyPairGenerator object.
     *
     * @since 1.2
     */
    public void initialize(int keysize, SecureRandom random) {
        // This does nothing, because either
        // 1. the implementation object returned by getInstance() is an
        //    instance of KeyPairGenerator which has its own
        //    initialize(keysize, random) method, so the application would
        //    be calling that method directly, or
        // 2. the implementation returned by getInstance() is an instance
        //    of Delegate, in which case initialize(keysize, random) is
        //    overridden to call the corresponding SPI method.
        // (This is a special case, because the API and SPI method have the
        // same name.)
    }
  • initialize λ©”μ„œλ“œλŠ” μ˜€λ²„λ‘œλ”©λœ μ—¬λŸ¬ λ©”μ„œλ“œκ°€ μ‘΄μž¬ν•œλ‹€.
    • λ‚œμˆ˜λ₯Ό μ„€μ •ν•˜μ§€ μ•ŠλŠ” 경우 JCAUtil.getDefSecureRandom() 자체적으둜 λ‚œμˆ˜λ₯Ό 생성함을 확인할 수 μžˆλ‹€.
  • πŸ€– RSA의 ν‚€ μ‚¬μ΄μ¦ˆλŠ” μ–΄λŠ 정도가 적당 ν•œ 지에 λŒ€ν•œ 뀼튼의 λ‹΅λ³€
    • RSA ν‚€ ν¬κΈ°λŠ” μ•”ν˜Έν™”μ—μ„œ μ‚¬μš©λ˜λŠ” λͺ¨λ“ˆλŸ¬μŠ€(modulus)의 길이λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 일반적으둜 RSA ν‚€ ν¬κΈ°λŠ” λΉ„νŠΈ λ‹¨μœ„λ‘œ ν‘œν˜„λ©λ‹ˆλ‹€. 더 큰 ν‚€ ν¬κΈ°λŠ” 더 κ°•λ ₯ν•œ λ³΄μ•ˆμ„ μ œκ³΅ν•˜μ§€λ§Œ 계산 λΉ„μš©μ΄ μ¦κ°€ν•˜λ―€λ‘œ 상황에 맞게 μ μ ˆν•œ ν‚€ 크기λ₯Ό 선택해야 ν•©λ‹ˆλ‹€. 
  • 1024λΉ„νŠΈ μ΄ν•˜
    • 과거에 널리 μ‚¬μš©λ˜μ—ˆμœΌλ‚˜, ν˜„μž¬λŠ” ꢌμž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
    • μ•”ν˜Έ 뢄석 기술의 λ°œμ „μœΌλ‘œ 인해 μ•ˆμ „μ„±μ΄ μ €ν•˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    • ν˜„μž¬ λ³΄μ•ˆ μš”κ΅¬μ‚¬ν•­μ„ μΆ©μ‘±ν•˜μ§€ λͺ»ν•  수 μžˆμœΌλ―€λ‘œ ꢌμž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • 2048λΉ„νŠΈ
    • ν˜„μž¬ κ°€μž₯ 일반적으둜 μ‚¬μš©λ˜λŠ” RSA ν‚€ ν¬κΈ°μž…λ‹ˆλ‹€.
    • μΆ©λΆ„νžˆ μ•ˆμ „ν•˜λ©΄μ„œλ„ 계산 λΉ„μš©μ΄ μ λ‹Ήν•©λ‹ˆλ‹€.
    • λŒ€λΆ€λΆ„μ˜ λ³΄μ•ˆ μ‘μš© ν”„λ‘œκ·Έλž¨μ—μ„œ ꢌμž₯ν•˜λŠ” κΈ°λ³Έ κ°’μž…λ‹ˆλ‹€.
  • 3072λΉ„νŠΈ 이상
    • 더 κ°•λ ₯ν•œ λ³΄μ•ˆμ„ μš”κ΅¬ν•˜λŠ” 경우 선택할 수 μžˆλŠ” μ˜΅μ…˜μž…λ‹ˆλ‹€.
    • 예λ₯Ό λ“€μ–΄, μ •λΆ€ κΈ°κ΄€, 금육 κΈ°κ΄€ λ“±μ—μ„œ λ―Όκ°ν•œ 정보λ₯Ό μ²˜λ¦¬ν•˜λŠ” κ²½μš°μ— μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν•˜μ§€λ§Œ 계산 λΉ„μš©μ΄ μ¦κ°€ν•˜λ―€λ‘œ μ„±λŠ₯κ³Ό λ³΄μ•ˆ μ‚¬μ΄μ˜ κ· ν˜•μ„ κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • 4096λΉ„νŠΈ 이상
    • 맀우 κ°•λ ₯ν•œ λ³΄μ•ˆμ„ μ œκ³΅ν•˜μ§€λ§Œ, 계산 λΉ„μš©μ΄ 많이 λ“­λ‹ˆλ‹€.
    • 주둜 졜고 μˆ˜μ€€μ˜ λ³΄μ•ˆ μš”κ΅¬μ‚¬ν•­μ„ μΆ©μ‘±ν•΄μ•Ό ν•˜λŠ” ν™˜κ²½μ—μ„œ μ„ νƒλ©λ‹ˆλ‹€.

 

πŸ“Œ RSA μ „μžμ„œλͺ… μ•”λ³΅ν˜Έν™” ν…ŒμŠ€νŠΈ

πŸ“– μ•”ν˜Έν™” 클래슀

public class RsaGenerator {
    public static String encrypt(String data, String privateKeyStr) {
        try {
            // [1] Base64둜 μΈμ½”λ”©λœ 개인 ν‚€λ₯Ό PrivateKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            // [2] RSA μ•”ν˜Έν™” 방식을 μ„€μ •ν•œλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            // [3]
            byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedData);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }
}
  • μœ„ μ•”ν˜Έν™” ν΄λž˜μŠ€μ—μ„œλŠ” λ¬Έμžμ—΄ 데이터와, κ°œμΈν‚€λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›μ•„ μ•”ν˜Έν™”ν•œλ‹€.
    • κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”(μ„œλͺ…)ν•˜κ³  κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”ν•˜λŠ” μ „μžμ„œλͺ… ν…ŒμŠ€νŠΈμ΄λ‹€.
  • [1]μ—μ„œλŠ” Base64둜 인코딩 된 κ°œμΈν‚€λ₯Ό PrivateKey 객체둜 λ³€ν™˜ν•œλ‹€.
    • μœ„μ˜ ν‚€ 생성 μ˜ˆμ œμ—μ„œ κ°œμΈν‚€λ₯Ό Base64둜 μΈμ½”λ”©ν•˜μ—¬ String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν–ˆκΈ° λ•Œλ¬Έμ— μ—­μœΌλ‘œ Base64둜 λ””μ½”λ”©ν•˜μ—¬ PrivateKey νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜λŠ” 것이닀.
    • λ§Œμ•½ Hex둜 μΈμ½”λ”©ν•œ String νƒ€μž…μ˜ ν‚€λ₯Ό κ°€μ§€κ³  μžˆλ‹€λ©΄ Hex둜 λ””μ½”λ”©ν•˜μ—¬μ•Ό ν•œλ‹€.
    • 두 차이λ₯Ό λͺ¨λ₯΄κ² λ‹€λ©΄ 이 글을 μ°Έκ³ ν•˜μž.
    • String νƒ€μž…μ˜ κ°œμΈν‚€λ₯Ό PrivaveKey νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜λŠ” 경우 PKCS8EncodedKeySpec 객체λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
    • KeyFactory 객체λ₯Ό 톡해 RSA λ°©μ‹μž„μ„ μ„ μ–Έν•˜κ³  privateKey둜 μ΅œμ’… λ³€ν™˜ν•œλ‹€.
  • [2]μ—μ„œλŠ” RSA μ•”ν˜Έν™” 방식을 μ΄ˆκΈ°ν™”ν•˜λŠ”λ° Cipher.getInstance()의 νŒŒλΌλ―Έν„°λ₯Ό 톡해 μ»€μŠ€ν…€ν•œ 방식을 μ„€μ •ν•  수 μžˆλ‹€.
    • 기본적으둜 "RSA"만 μž…λ ₯ν•˜λ©΄ μœ„μ˜ μ½”λ“œμ™€ 같이 "RSA/ECB/PKCS1Padding"둜 μ„€μ •λœλ‹€.
      • μ•„λž˜μ— μ„€λͺ…ν•˜κ² μ§€λ§Œ μ „μžμ„œλͺ…μ˜ 경우 PKCS1Padding을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
    • μ™Όμͺ½λΆ€ν„° "μ•”ν˜Έν™” 방식/블둝 μ•”ν˜Έ 운용 λͺ¨λ“œ/νŒ¨λ”© μŠ€ν‚€λ§ˆ"λ₯Ό μ„€μ •ν•  수 μžˆλ‹€.
    • μ•”ν˜Έν™” 방식은 RSA이닀.
    • 블둝 μ•”ν˜Έ 운용 λͺ¨λ“œλŠ” λŒ€μΉ­ν‚€μ—μ„œ μ‚¬μš©ν•˜λŠ” μ‹œμŠ€ν…œμ΄κΈ° λ•Œλ¬Έμ— RSAμ—μ„œλŠ” ν•„μš”λ‘œ ν•˜μ§€ μ•Šμ§€λ§Œ 관둀상 μ λŠ”λ‹€. μ‹€μ œλ‘œ Cipher 클래슀의 상단 주석에도 "RSA/ECB/PKCS1Padding"라고 μ ν˜€μžˆλ‹€. 
      • AES와 같은 λŒ€μΉ­ν‚€λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 ECB (Electronic Codebook) λͺ¨λ“œ 외에도 CBC (Cipher Block Chaining), CTR (Counter), CFB (Cipher Feedback), OFB (Output Feedback) 및 GCM (Galois/Counter Mode)κ³Ό 같은 λ‹€μ–‘ν•œ λͺ¨λ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
    • νŒ¨λ”© μŠ€ν‚€λ§ˆμ˜ 경우 PKCS1Padding은 RSA μ•”ν˜Έν™”μ—μ„œ μ‚¬μš©λ˜λŠ” νŒ¨λ”© μŠ€ν‚€λ§ˆμ˜ 였래된 ν‘œμ€€μ΄λ©°, λ°μ΄ν„°μ˜ μ•ˆμ „μ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•΄ μΆ”κ°€λœλ‹€. 
      • μ—¬κΈ°μ„œ νŒ¨λ”©μ΄λž€ 원본 데이터에 λ‚œμˆ˜μ™€ 같은 λ³„λ„μ˜ 좔가적인 데이터λ₯Ό 더해 원본 데이터λ₯Ό μ•”ν˜Έν™”ν•˜λŠ” 데에 κ·œμΉ™μ„±μ„ μ €ν•˜μ‹œμΌœ μ•ˆμ „μ„±μ„ κ°•ν™”ν•˜λŠ” 역할을 ν•œλ‹€. 
      • PKCS1Padding은 RSA μ•”ν˜Έν™”μ—μ„œ 블둝 크기에 맞좰 데이터λ₯Ό νŒ¨λ”© ν•˜λŠ” 방식이닀. 이 νŒ¨λ”©μ€ κ³΅κ°œν‚€ μ•”ν˜Έν™” μ‹œ λ°œμƒν•  수 μžˆλŠ” 일뢀 취약점을 λ³΄μ™„ν•˜κ³  λ°μ΄ν„°μ˜ 무결성과 λ³΄μ•ˆμ„±μ„ κ°•ν™”ν•œλ‹€. κ·ΈλŸ¬λ‚˜ PKCS1Padding에도 일뢀 μ œν•œμ‚¬ν•­κ³Ό 취약점이 μžˆμ„ 수 μžˆλ‹€. 예λ₯Ό λ“€μ–΄, νŒ¨λ”© 였라클(oracle) 곡격과 같은 곡격 벑터가 μ‘΄μž¬ν•  수 있으며, 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ 좔가적인 μ‘°μΉ˜κ°€ ν•„μš”ν•  μˆ˜λ„ μžˆλ‹€.
      • 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 더 κ°•λ ₯ν•œ λ³΄μ•ˆμ„ μ œκ³΅ν•˜λŠ” OAEP (Optimal Asymmetric Encryption Padding) νŒ¨λ”© μŠ€ν‚€λ§ˆκ°€ κ°œλ°œλ˜μ—ˆλ‹€. OAEPλŠ” PKCS1Padding보닀 μ•ˆμ „ν•˜λ©°, SHA-1 λ˜λŠ” SHA-256κ³Ό MGF1(Mask Generation Function 1)을 μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό νŒ¨λ”© ν•œλ‹€. λ”°λΌμ„œ OAEP νŒ¨λ”© (RSA/ECB/OAEPWithSHA-1AndMGF1Padding λ˜λŠ” RSA/ECB/OAEPWithSHA-256AndMGF1Padding)을 μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.
      • μ•„λž˜λŠ” Cipher 클래슀의 주석 쀑 일뢀이닀.
  • [3]μ—μ„œλŠ” μ•”ν˜Έν™”ν•  데이터λ₯Ό λ°”μ΄νŠΈλ°°μ—΄λ‘œ λ³€ν™˜ν•˜λŠ”λ° μ΄λ•Œ UTF-8 인코딩 λ°©μ‹μž„μ„ λͺ…μ‹œν•œλ‹€.
  • 이후 μ΅œμ’…μ μœΌλ‘œ Base64 인코딩을 톡해 String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜κ³  λ¦¬ν„΄ν•œλ‹€.
    • 이 λΆ€λΆ„ μ—­μ‹œ Hex(16μ§„μˆ˜)둜 λ³€ν™˜ν•˜μ—¬ λ¦¬ν„΄ν•˜λŠ” 방법도 μžˆλ‹€. 

 

πŸ–‹οΈ Cipher 클래슀 μƒλ‹¨μ˜ 주석

* Every implementation of the Java platform is required to support
* the following standard {@code Cipher} transformations with the keysizes
* in parentheses:
* <ul>
* <li>{@code AES/CBC/NoPadding} (128)</li>
* <li>{@code AES/CBC/PKCS5Padding} (128)</li>
* <li>{@code AES/ECB/NoPadding} (128)</li>
* <li>{@code AES/ECB/PKCS5Padding} (128)</li>
* <li>{@code AES/GCM/NoPadding} (128)</li>
* <li>{@code DESede/CBC/NoPadding} (168)</li>
* <li>{@code DESede/CBC/PKCS5Padding} (168)</li>
* <li>{@code DESede/ECB/NoPadding} (168)</li>
* <li>{@code DESede/ECB/PKCS5Padding} (168)</li>
* <li>{@code RSA/ECB/PKCS1Padding} (1024, 2048)</li>
* <li>{@code RSA/ECB/OAEPWithSHA-1AndMGF1Padding} (1024, 2048)</li>
* <li>{@code RSA/ECB/OAEPWithSHA-256AndMGF1Padding} (1024, 2048)</li>
  • AES, RSAλ₯Ό μ‚¬μš©ν•  λ•Œμ˜ 기본적으둜 μ§€μ›ν•˜λŠ” λΈ”λ‘μ•”ν˜Έ 운용 λͺ¨λ“œ(AES만 ν•΄λ‹Ή)와 νŒ¨λ”© μŠ€ν‚€λ§ˆκ°€ λͺ…μ‹œλ˜μ–΄ μžˆλ‹€.
  • 이λ₯Ό μ‘μš©ν•˜λ©΄ AES도 μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμ„ κ²ƒμœΌλ‘œ 보인닀.

 

🧹 RSA νŒ¨λ”© μŠ€ν‚€λ§ˆ 정리 

RSA/ECB/PKCS1Padding

  • 이 νŒ¨λ”©μ€ 였래된 ν‘œμ€€μ΄μ§€λ§Œ μ—¬μ „νžˆ 널리 μ‚¬μš©λœλ‹€.
  • 주둜 λ°μ΄ν„°μ˜ 블둝 λ‹¨μœ„κ°€ μ•„λ‹Œ 짧은 λ©”μ‹œμ§€λ₯Ό μ•”ν˜Έν™”ν•˜λŠ” κ²½μš°μ— μ ν•©ν•˜λ‹€.
  • 예λ₯Ό λ“€μ–΄, μ „μž μ„œλͺ… 및 μΈμ¦μ„œμ— μ‚¬μš©λ  수 μžˆλ‹€.

RSA/ECB/OAEPWithSHA-1AndMGF1Padding

  • PKCS#1 v1.5보닀 더 μ΅œμ‹ μ΄κ³  μ•ˆμ „ν•œ μ˜΅μ…˜μ΄λ‹€.
  • SHA-1 ν•΄μ‹œ ν•¨μˆ˜μ™€ MGF1을 μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό νŒ¨λ”© 처리λ₯Ό ν•œλ‹€.
  • κΈ°λ°€μ„±κ³Ό μ•ˆμ •μ„±μ΄ μ€‘μš”ν•œ μƒν™©μ—μ„œ μ‚¬μš©ν•  수 μžˆλ‹€.

RSA/ECB/OAEPWithSHA-256AndMGF1Padding

  • 보닀 κ°•λ ₯ν•œ λ³΄μ•ˆμ΄ ν•„μš”ν•œ κ²½μš°μ— μ„ νƒλ˜λŠ” μ˜΅μ…˜μ΄λ‹€.
  • SHA-256 ν•΄μ‹œ ν•¨μˆ˜μ™€ MGF1을 μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό νŒ¨λ”© 처리λ₯Ό ν•œλ‹€.
  • λ³΄μ•ˆ μš”κ΅¬μ‚¬ν•­μ΄ μ—„κ²©ν•˜κ±°λ‚˜ κ³ λ„μ˜ λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•œ μƒν™©μ—μ„œ μ‚¬μš©ν•  수 μžˆλ‹€.

 

🚨 주의

  • μœ„μ™€ 같이 κ°œμΈν‚€λ‘œ μ„œλͺ…(μ•”ν˜Έν™”)ν•˜λŠ” μ „μžμ„œλͺ… λ°©μ‹μ˜ κ²½μš°μ— RSA/ECB/OAEPWithSHA-1AndMGF1Padding λ˜λŠ” RSA/ECB/OAEPWithSHA-256AndMGF1Padding)을 μ‚¬μš©ν•˜κ²Œ 되면 Caused by: java.security.InvalidKeyException: OAEP cannot be used to sign or verify signatures와 같은 μ—λŸ¬λ₯Ό λ§Œλ‚˜κ²Œ λœλ‹€.
  • λ”°λΌμ„œ μ „μžμ„œλͺ…μ˜ 경우 μœ„μ²˜λŸΌ "RSA/ECB/PKCS1PADDING"λ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€.
  • λ°˜λŒ€λ‘œ RSA둜 데이터λ₯Ό μ•”ν˜Έν™”ν•˜λŠ” κ²½μš°μ—λŠ” OAEP νŒ¨λ”© μŠ€ν‚€λ§ˆλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 보닀 μ•ˆμ „ν•œ 데이터 μ•”ν˜Έν™”κ°€ κ°€λŠ₯ν•˜λ‹€.
    • 데이터 μ•”ν˜Έν™”μ—μ„œλŠ” PKCS1Paddingλ₯Ό ꢌμž₯ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • μ΄μœ λŠ” 잘 λͺ¨λ₯΄κ² μœΌλ‚˜, μ „μžμ„œλͺ…μ˜ 경우 λ°μ΄ν„°μ˜ μ•ˆμ „ν•œ μ•”ν˜Έν™” λͺ©μ λ³΄λ‹€λŠ” 인증, 증λͺ…에 λŒ€ν•œ λͺ©μ μ΄ 크기 λ•Œλ¬Έμ— κ°•λ ₯ν•œ μ•”ν˜Έν™”κ°€ λΆˆν•„μš”ν•˜κΈ° λ•Œλ¬Έμ΄ μ•„λ‹κΉŒ μ‹Άλ‹€.

 

πŸ“– λ³΅ν˜Έν™” 클래슀

public class RsaVerifier {

    // String 객체둜 λ³€ν™˜ ν›„ 리턴
    public static String decryptReturnNewString(String encryptedValue, String publicKeyStr) {
        try {

            // [1] Base64둜 μΈμ½”λ”©λœ 곡개 ν‚€λ₯Ό PublicKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            // [2] RSA λ³΅ν˜Έν™” 방식을 μ„€μ •ν•œλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.DECRYPT_MODE, publicKey);

            // [3]
            byte[] encryptedValueBytes = Base64.getDecoder().decode(encryptedValue);
            byte[] decryptedValueBytes = cipher.doFinal(encryptedValueBytes);

            return new String(decryptedValueBytes);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }
}
  • μœ„μ˜ λ©”μ„œλ“œλŠ” κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”λœ 데이터λ₯Ό κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”ν•œλ‹€.
  • [1]μ—μ„œλŠ” Base64둜 인코딩 된 κ³΅κ°œν‚€λ₯Ό PublicKey 객체둜 λ³€ν™˜ν•œλ‹€.
  • μœ„μ˜ ν‚€ 생성 λ‘œμ§μ—μ„œ Base64둜 μΈμ½”λ”©ν•˜μ—¬ String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν–ˆκΈ° λ•Œλ¬Έμ— μ—­μœΌλ‘œ Base64둜 λ””μ½”λ”©ν•˜μ—¬ PublicKey νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•΄ μ£ΌλŠ” 것이닀. 
    • μ—¬κΈ°μ„œλ„ λ§ˆμ°¬κ°€μ§€μ΄λ‹€. λ§Œμ•½ Hex둜 μΈμ½”λ”©ν•œ String νƒ€μž…μ˜ ν‚€λ₯Ό κ°€μ§€κ³  μžˆλ‹€λ©΄ Hex둜 λ””μ½”λ”©ν•˜μ—¬μ•Ό ν•œλ‹€.
  • String νƒ€μž…μ˜ κ³΅κ°œν‚€λ₯Ό PublicKey νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜λŠ” 경우 X509EncodedKeySpec 객체λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
  • KeyFactory 객체λ₯Ό 톡해 RSA λ°©μ‹μž„μ„ μ„ μ–Έν•˜κ³  publicKey ν‚€λ‘œ μ΅œμ’… λ³€ν™˜ν•œλ‹€.
  • [2]μ—μ„œλŠ” μ•”ν˜Έν™” 방식과 λ˜‘κ°™μ΄ λ³΅ν˜Έν™” 방식을 μ„€μ •ν•œλ‹€.
  • μ•”ν˜Έν™” λ°©μ‹μ—μ„œ μ„€μ •ν•œ μ„ΈνŒ…κ°’μ„ κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜κ³ 
  • DECRYPT_MODEμž„μ„ λͺ…μ‹œν•˜κ³ , κ³΅κ°œν‚€λ‘œ μ΄ˆκΈ°ν™”ν•œλ‹€.
  • [3]μ—μ„œλŠ” λ³΅ν˜Έν™” 데이터λ₯Ό Base64둜 λ””μ½”λ”©ν•˜μ—¬ λ°”μ΄νŠΈλ°°μ—΄λ‘œ λ³€ν™˜ν•˜κ³ 
  • λ³΅ν˜Έν™”λ₯Ό μ§„ν–‰ν•˜μ—¬ 원본 λ°μ΄ν„°μ˜ λ°”μ΄νŠΈ 배열값을 μ–»λŠ”λ‹€.
  • 이후 μ΅œμ’…μ μœΌλ‘œ new String 객체λ₯Ό μƒμ„±ν•˜μ—¬ String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜κ³  λ¦¬ν„΄ν•œλ‹€.
    • new String은 λ°”μ΄νŠΈ 배열을 λ¬Έμžμ—΄λ‘œ λ³€ν™˜ν•˜λŠ” 것이고, toString()은 λ°”μ΄νŠΈ λ°°μ—΄ 자체의 λ¬Έμžμ—΄μ„ 값을 μ˜λ―Έν•˜λ―€λ‘œ λ°”μ΄νŠΈ 배열이 μ˜λ―Έν•˜λŠ” 문자λ₯Ό μ–»κΈ° μœ„ν•΄μ„œ new String 객체λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
  • μ΄λ•Œ μ£Όμ˜ν•΄μ•Ό ν•  점이 λ§Œμ•½ Base64.getEncoder().encodeToString()을 μ‚¬μš©ν•˜μ—¬ λ¦¬ν„΄ν•˜λ©΄ Base64둜 μΈμ½”λ”©ν•œ 값을 String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•œ 값이 λ¦¬ν„΄λ˜κΈ° λ•Œλ¬Έμ— 원본 λ°μ΄ν„°μ˜ κ°’κ³Ό μ „ν˜€ λ‹€λ₯Έ 값이 λ‚˜μ˜¬ 수 μžˆλ‹€.

 

πŸ“– ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ 및 κ²°κ³Ό

public static void rsaEncryptDecryptTest() {
	
    String text = "μ•ˆλ…•ν•˜μ„Έμš”.";
    
    String publicKeyByJava   = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlK5ioVvjb1CHE+wl0Od9tekm7tMLB06ApYKuwEVEFQNRRdcVToNnHMf80Hy17LHAv4R83UTf6ZOy3IeLWbjFMp43s3K4SCS1e94T9bojztWLuwrSDIrGMXfKOsnaEdJyAEAqocL+HSZTOGpM20UGKIHzKK+VDwFYsP1d2qoRBhy6bLE/hbFYodc5iIFeB+U8vpHwcSROYXdZ+hceo9oBxMZM0Kv+SqOFIcguVEoIteMH7VjSXR1vtfN5yCDRrLKwj6FOPTO9EXHsgbc1+PwOntSLGxm7AbgS90BvEmHDSXlUt7hA2WHeP7PCsN1cYYpuca+nRAP3jwfVMLSxsziq8wIDAQAB";
    String privateKeyByJava  = "미리 μƒμ„±ν•œ privateKey";
	
    // κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”
    String encrypted = RsaGenerator.encrypt(text, privateKeyByJava);

    // κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”
    String decryptReturnBase64 = RsaVerifier.decryptReturnBase64(encrypted, publicKeyByJava);
    String decryptNewString = RsaVerifier.decryptReturnNewString(encrypted, publicKeyByJava);

    System.out.println("### decryptReturnBase64 : " + decryptReturnBase64); // Base64 return
    // ### decryptReturnBase64 : 7JWI64WV7ZWY7IS47JqULg==

    System.out.println("### decryptNewString : " + decryptNewString); // string return
    // ### decryptNewString : μ•ˆλ…•ν•˜μ„Έμš”.
}
  • μœ„μ˜ 결과처럼 λ§Œμ•½ Base64둜 μΈμ½”λ”©ν•œ 후에 λ¦¬ν„΄ν•˜λ©΄ 원본 λ°μ΄ν„°μ˜ "μ•ˆλ…•ν•˜μ„Έμš”"κ°€ μ•„λ‹Œ Base64둜 인코딩 κ°’μ˜ String λ³€ν™˜ 값이 λ‚˜μ˜€λ‹ˆ μ£Όμ˜ν•΄μ•Ό ν•œλ‹€.
    • λ³΅ν˜Έν™” λ©”μ„œλ“œλŠ” 원본값을 μ–»μ–΄μ•Ό ν•˜λ―€λ‘œ Hexλ‚˜ Base64둜 μΈμ½”λ”©ν•˜μ—¬ λ¦¬ν„΄ν•˜μ§€ 말고 new String 객체둜 리턴해야 ν•œλ‹€.
  • κ³΅κ°œν‚€λ‘œ μ•”ν˜Έν™”ν•˜κ³  κ°œμΈν‚€λ‘œ λ³΅ν˜Έν™”ν•˜λŠ” 방식은 μœ„μ˜ 방식을 보고 μ‘μš©ν•˜λ©΄ μ‰½κ²Œ λ§Œλ“€ 수 μžˆμ„ 것 κ°™λ‹€.

 

πŸ“Œ SHA-256을 ν™œμš©ν•œ μ „μžμ„œλͺ… κ΅¬ν˜„

  • μ „μžμ„œλͺ…은 νƒ€μΈμ—κ²Œ λ‚˜λ₯Ό ν˜Ήμ€ λ‚΄κ°€ λ§Œλ“  κ²ƒμž„μ„ μž…μ¦ν•˜λŠ” μš©λ„λ‘œ μ‚¬μš©ν•œλ‹€.
  • μœ„μ˜ κ°œμΈν‚€λ‘œ μ•”ν˜Έν™” κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”ν•˜λŠ” λ°©μ‹μ—μ„œ SHA-256 ν•΄μ‹œν•¨μˆ˜λ₯Ό ν™œμš©ν•˜μ—¬ μ „μžμ„œλͺ…을 κ΅¬ν˜„ν•  수 μžˆλ‹€.
  • μ—¬κΈ°μ—μ„œλŠ” λŒ€ν‘œμ μΈ 인코딩 방식인 16μ§„μˆ˜ λ³€ν™˜κ³Ό Base64 인코딩 λ³€ν™˜ 두 κ°€μ§€ λͺ¨λ‘ 보여주겠닀.
  • SHA-256κ³Ό μžλ°”μ—μ„œμ˜ κ΅¬ν˜„μ— λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ 이 글을 μ°Έκ³ ν•˜λ„λ‘ ν•˜μž.

 

πŸ’‘ SHA-256을 ν™œμš©ν•œ μ „μžμ„œλͺ…μ˜ μ˜ˆμ‹œ

  • μ² μˆ˜λŠ” RSA λ°©μ‹μ˜ κ³΅κ°œν‚€μ™€ κ°œμΈν‚€λ₯Ό λ§Œλ“€μ—ˆλ‹€.
  • μ² μˆ˜λŠ” μ˜ν¬μ—κ²Œ 쀄 데이터λ₯Ό SHA-256으둜 ν•΄μ‹œν•˜κ³ , κ·Έ ν•΄μ‹œκ°’μ„ μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ„œλͺ…(μ•”ν˜Έν™”)ν–ˆλ‹€.
  • μ² μˆ˜λŠ” μ˜ν¬μ—κ²Œ <μžμ‹ μ˜ κ³΅κ°œν‚€>와, <μžμ‹ μ˜ κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”λœ ν•΄μ‹œ 데이터>, <원본 데이터>λ₯Ό μ£Όμ—ˆλ‹€.
  • μ˜ν¬λŠ” <철수의 κ³΅κ°œν‚€>와, <철수의 κ°œμΈν‚€λ‘œ μ•”ν˜Έν™” 된 ν•΄μ‹œ 데이터>, <원본 데이터>λ₯Ό λ°›μ•˜λ‹€.
  • μ˜ν¬λŠ” <철수의 κ³΅κ°œν‚€>둜 <μ•”ν˜Έν™”λœ ν•΄μ‹œ 데이터>λ₯Ό λ³΅ν˜Έν™”ν•˜μ—¬ λ‚˜μ˜¨ ν•΄μ‹œκ°’κ³Ό, 원본 데이터λ₯Ό 직접 ν•΄μ‹œν•˜μ—¬ λ‚˜μ˜¨ ν•΄μ‹œκ°’μ„ λΉ„κ΅ν•˜μ—¬ μ² μˆ˜κ°€ ν•΄λ‹Ή 데이터λ₯Ό λ§Œλ“€μ—ˆμœΌλ©°, 원본 데이터가 λ³€μ‘°λ˜μ§€ μ•Šμ•˜μŒμ„ μ‹ λ’°ν•  수 μžˆλ‹€.

 

πŸ“– SHA256 클래슀(Return Hex)

public class Sha256ToHex {

    /**
     * μž…λ ₯ 받은 λ¬Έμžμ—΄μ„ SHA-256으둜 ν•΄μ‹±ν•˜μ—¬ ν•΄μ‹œ 값을 λ°˜ν™˜ν•¨
     * @param text
     * @return ν•΄μ‹œκ°’
     * @throws NoSuchAlgorithmException
     */
    public String encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(text.getBytes());

        return bytesToHex(md.digest());
    }

    /**
     * λ°”μ΄νŠΈ 배열을 16μ§„μˆ˜ λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•¨
     * @param bytes
     * @return 16μ§„μˆ˜ λ¬Έμžμ—΄
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder builder = new StringBuilder();
        for (byte b : bytes) {
            builder.append(String.format("%02x", b));
        }
        return builder.toString();
    }
}
  • 리턴을 16μ§„μˆ˜λ‘œ ν•œλ‹€.

 

πŸ“– SHA256 클래슀(Return Base64)

public class Sha256ToBase64 {

    /**
     * μž…λ ₯ 받은 λ¬Έμžμ—΄μ„ SHA-256으둜 ν•΄μ‹±ν•˜μ—¬ ν•΄μ‹œ 값을 λ°˜ν™˜ν•¨
     * @param text
     * @return ν•΄μ‹œκ°’
     * @throws NoSuchAlgorithmException
     */
    public String encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(text.getBytes());

        return Base64.getEncoder().encodeToString(md.digest());
    }
}
  • 리턴을 Base64둜 인코딩 λ¬Έμžμ—΄λ‘œ ν•œλ‹€.

 

πŸ“– RsaGenerator(μ „μžμ„œλͺ… 생성) 클래슀

public class RsaGenerator {
    public static String encryptWithHexHash(String data, String privateKeyStr) {
        try {
            // Base64둜 μΈμ½”λ”©λœ 개인 ν‚€λ₯Ό PrivateKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            // λ°μ΄ν„°λ‘œλΆ€ν„° SHA-256 ν•΄μ‹œ 값을 μƒμ„±ν•˜κ³  16μ§„μˆ˜λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hashedData = md.digest(data.getBytes(StandardCharsets.UTF_8));
            String sha256hex = bytesToHex(hashedData);

            // RSA 개인 ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄μ‹œ 값을 μ•”ν˜Έν™”ν•©λ‹ˆλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            byte[] encryptedHashValue = cipher.doFinal(sha256hex.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedHashValue);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }
    
    public static String encryptWithBase64(String data, String privateKeyStr) {
        try {
            // Base64둜 μΈμ½”λ”©λœ 개인 ν‚€λ₯Ό PrivateKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            // λ°μ΄ν„°λ‘œλΆ€ν„° SHA-256 ν•΄μ‹œ 값을 μƒμ„±ν•˜κ³  Base64둜 μΈμ½”λ”©ν•©λ‹ˆλ‹€.
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hashedData = md.digest(data.getBytes(StandardCharsets.UTF_8));
            String sha256Base64 = Base64.getEncoder().encodeToString(hashedData);

            // RSA 개인 ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄μ‹œ 값을 μ•”ν˜Έν™”ν•©λ‹ˆλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            byte[] encryptedHashValue = cipher.doFinal(sha256Base64.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedHashValue);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }

    public static String encryptNoHash(String data, String privateKeyStr) {
        try {
            // Base64둜 μΈμ½”λ”©λœ 개인 ν‚€λ₯Ό PrivateKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            // RSA 개인 ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄μ‹œ 값을 μ•”ν˜Έν™”ν•©λ‹ˆλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedData);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }

    private static String bytesToHex(byte[] hash) {
        StringBuffer hexString = new StringBuffer();
        for (int i = 0; i < hash.length; i++) {
            String hex = Integer.toHexString(0xff & hash[i]);
            if(hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
  • μ„Έ 개의 λ©”μ„œλ“œ 쀑 λ§ˆμ§€λ§‰ encryptNoHashλŠ” ν•΄μ‹œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ€ λ©”μ„œλ“œμ΄λ‹€.
  • λ‚˜λ¨Έμ§€ 두 개의 λ©”μ„œλ“œμ˜ 차이점은 λ°”μ΄νŠΈ λ°°μ—΄μ˜ ν•΄μ‹œκ°’μ„ 16μ§„μˆ˜λ‘œ λ³€ν™˜ν•˜κ³  μ•”ν˜Έν™”ν•˜λƒ, Bas64둜 λ³€ν™˜ν•˜κ³  μ•”ν˜Έν™”ν•˜λƒμ΄λ‹€.

 

πŸ“– RsaVerifier 클래슀(μ „μžμ„œλͺ… λ³΅ν˜Έν™”) 클래슀

public class RsaVerifier {

    public static String decryptReturnNewString(String encryptedValue, String publicKeyStr) {
        try {

            // Base64둜 μΈμ½”λ”©λœ 곡개 ν‚€λ₯Ό PublicKey 객체둜 λ³€ν™˜ν•©λ‹ˆλ‹€.
            byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            // μ•”ν˜Έν™”λœ ν•΄μ‹œ 값을 λ³΅ν˜Έν™”ν•©λ‹ˆλ‹€.
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(Cipher.DECRYPT_MODE, publicKey);

            byte[] encryptedValueBytes = Base64.getDecoder().decode(encryptedValue);
            byte[] decryptedValueBytes = cipher.doFinal(encryptedValueBytes);

            return new String(decryptedValueBytes);
        } catch (Exception e) {
            throw new RuntimeException(e);  // ν•„μš”μ— 따라 μ˜ˆμ™Έ 처리λ₯Ό ν•©λ‹ˆλ‹€
        }
    }
}
  • μœ„μ˜ λ³΅ν˜Έν™” λ©”μ„œλ“œ κ·ΈλŒ€λ‘œμ΄λ‹€.
  • λ‹€μ‹œ ν•œ 번 κ°•μ‘°ν•˜μ§€λ§Œ λ³΅ν˜Έν™” λ©”μ„œλ“œμ˜ 리턴에 인코딩을 λ„£μœΌλ©΄ 리턴 값이 원본값과 λ‹€λ₯΄κ²Œ λ‚˜μ˜¨λ‹€. μ£Όμ˜ν•˜μž.

 

πŸ“– ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ 및 κ²°κ³Ό

public static void rsaTest(String text) throws NoSuchAlgorithmException {

    Sha256ToBase64 sha256ToBase64 = new Sha256ToBase64();
    Sha256ToHex sha256ToHex = new Sha256ToHex();

    // textλ₯Ό SHA-256으둜 ν•΄μ‹œν•˜μ—¬ 두가지 방식(Base64, Hex)으둜 리턴
    System.out.println("SHA-256 ENCRYPT");
    System.out.println("### 원본 text : " + text);
    System.out.println("### sha256ToHex : " + sha256ToHex.encrypt(text));
    System.out.println("### sha256ToBase64 : " + sha256ToBase64.encrypt(text));

    // μžλ°”μ—μ„œ λ§Œλ“  κ³΅κ°œν‚€μ™€ κ°œμΈν‚€
    String publicKeyByJava   = "미리 μƒμ„±ν•œ κ³΅κ°œν‚€";
    String privateKeyByJava  = "미리 μƒμ„±ν•œ κ°œμΈν‚€":

    String encryptNoHash = RsaGenerator.encryptNoHash(text, privateKeyByJava);
    String encryptedTextWithHex = RsaGenerator.encryptWithHexHash(text, privateKeyByJava);
    String encryptedTextWithBase64 = RsaGenerator.encryptWithBase64(text, privateKeyByJava);

    String decryptedNoHash = RsaVerifier.decryptReturnNewString(encryptNoHash, publicKeyByJava);
    String decryptedTextWithHex = RsaVerifier.decryptReturnNewString(encryptedTextWithHex, publicKeyByJava);
    String decryptedTextWithBase64 = RsaVerifier.decryptReturnNewString(encryptedTextWithBase64, publicKeyByJava);
    System.out.println("RSA DECRYPT");
    System.out.println("### decryptedNoHash : " + decryptedNoHash);
    System.out.println("### decryptedTextWithHex : " + decryptedTextWithHex);
    System.out.println("### decryptedTextWithBase64 : " + decryptedTextWithBase64);
}

/**
SHA-256 ENCRYPT
### (1) 원본 text : μ•ˆλ…•ν•˜μ„Έμš”.
### (2) sha256ToHex : 8b118d6741f7cfa1a7ee246d0dda39f2f00bf9fd207b4e6c7fad87a15434a513
### (3) sha256ToBase64 : ixGNZ0H3z6Gn7iRtDdo58vAL+f0ge05sf62HoVQ0pRM=
RSA DECRYPT
### (1) decryptedNoHash : μ•ˆλ…•ν•˜μ„Έμš”.
### (2) decryptedTextWithHex : 8b118d6741f7cfa1a7ee246d0dda39f2f00bf9fd207b4e6c7fad87a15434a513
### (3) decryptedTextWithBase64 : ixGNZ0H3z6Gn7iRtDdo58vAL+f0ge05sf62HoVQ0pRM=
*/
  • κ²°κ³Όλ₯Ό μ°¨λ‘€λŒ€λ‘œ μ‚΄νŽ΄λ³΄μž.
  1. ν•΄μ‹œλ₯Ό ν•˜μ§€ μ•Šμ€ μƒνƒœμ—μ„œ κ°œμΈν‚€λ‘œ μ•”ν˜Έν™”ν•˜κ³  κ³΅κ°œν‚€λ‘œ λ³΅ν˜Έν™”ν•˜λ©΄ 원본 κ·ΈλŒ€λ‘œμ˜ 값이 λ‚˜μ˜¨λ‹€.
  2. 데이터λ₯Ό Sha256으둜 ν•΄μ‹œν•˜μ—¬ λ‚˜μ˜¨ λ°”μ΄νŠΈ 배열을 16μ§„μˆ˜λ‘œ λ³€ν™˜ν•˜κ³  λ°”μ΄νŠΈ λ°°μ—΄λ‘œ λ°”κΏ” μ•”ν˜Έν™”ν•œ 경우(ν•΄μ‹œκ°’μ€ Sha256ToHex), 이λ₯Ό λ³΅ν˜Έν™”ν•  λ•Œ λ¦¬ν„΄μœΌλ‘œ λ‚˜μ˜¨ ν•΄μ‹œκ°’(λ°”μ΄νŠΈ λ°°μ—΄)을 new String 객체둜 λ³€ν™˜ν•˜λ©΄ 16μ§„μˆ˜λ‘œ μΈμ½”λ”©λœ String ν•΄μ‹œκ°’μ„ 얻을 수 μžˆλ‹€.
  3. 데이터λ₯Ό Sha256으둜 ν•΄μ‹œν•˜μ—¬ λ‚˜μ˜¨ λ°”μ΄νŠΈ 배열을 Base64둜 λ³€ν™˜ν•˜κ³  λ°”μ΄νŠΈ λ°°μ—΄λ‘œ λ°”κΏ” μ•”ν˜Έν™”ν•œ 경우(ν•΄μ‹œκ°’μ€ Sha256ToBase64), 이λ₯Ό λ³΅ν˜Έν™”ν•  λ•Œ λ¦¬ν„΄μœΌλ‘œ λ‚˜μ˜¨ ν•΄μ‹œκ°’(λ°”μ΄νŠΈ λ°°μ—΄)을 new String 객체둜 λ³€ν™˜ν•˜λ©΄ Base64둜 μΈμ½”λ”©λœ String ν•΄μ‹œκ°’μ„ 얻을 수 μžˆλ‹€.

πŸ“œ 정리

 μ„€λͺ…이 맀우 μ–΄λ ΅κΈ° λ•Œλ¬Έμ— κ°„λ‹¨νžˆ μ •λ¦¬ν•˜λ©΄ SHA-256이든, RSA μ•”ν˜Έν™”λ“  κ²°κ΅­ λ°”μ΄νŠΈ λ°°μ—΄λ‘œ encrypt되기 λ•Œλ¬Έμ— μ•”ν˜Έν™” λ©”μ„œλ“œμ—μ„œλŠ” κ°œλ°œμžκ°€ 읽고 λ°μ΄ν„°λ² μ΄μŠ€μ— μ›ν™œνžˆ μ €μž₯ν•˜κΈ° μœ„ν•΄ 리턴할 λ•Œ λ°˜λ“œμ‹œ String νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•΄μ€˜μ•Ό ν•œλ‹€. μ΄λ•Œ Hex둜 λ³€ν™˜ν•  것인지, Base64둜 λ³€ν™˜ν•  것인지λ₯Ό μ„ νƒν•˜μ—¬ ν•œ κ°€μ§€ λ°©λ²•μœΌλ‘œ ν†΅μΌν•΄μ•Όλ§Œμ΄ λ³΅ν˜Έν™”ν–ˆμ„ λ•Œ μ˜¬λ°”λ₯Έ ν•΄μ‹œκ°’μ„ 얻을 수 μžˆλ‹€.

 

 λ³΅ν˜Έν™”(decrypt) λ©”μ„œλ“œμ˜ 경우 원본인 λ¬Έμžμ—΄ νƒ€μž…μ„ κ·ΈλŒ€λ‘œ 리턴해야 ν•˜λŠ”λ° 리턴 값을 Hexλ‚˜ Base64둜 μΈμ½”λ”©ν•˜κ²Œ 되면 λΆˆν•„μš”ν•œ 인코딩 κ³Όμ • λ•Œλ¬Έμ— 원본 데이터가 인코딩 λ˜μ–΄ μ „ν˜€ λ‹€λ₯Έ 값이 λ‚˜μ˜¨λ‹€. λ”°λΌμ„œ λ³΅ν˜Έν™” λ©”μ„œλ“œμ˜ κ²½μš°λŠ” λ°˜λ“œμ‹œ new String 객체둜 λ¦¬ν„΄ν•˜μž.

 

 μΆ”κ°€μ μœΌλ‘œ SHA-256 ν•΄μ‹œκ°€ λ“€μ–΄κ°„ 경우 λ³΅ν˜Έν™” λ©”μ„œλ“œλŠ” new String으둜 λ¦¬ν„΄ν•˜κΈ° λ•Œλ¬Έμ— μ•”ν˜Έν™” λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ SHA-256으둜 ν•΄μ‹œκ°’μ„ κ΅¬ν•œ ν›„ Hexλ‚˜ Base64 λ“±μœΌλ‘œ ν•œ 번 인코딩을 ν•˜κ³  κ·Έ 인코딩 된 String 값을 λ°”μ΄νŠΈ λ°°μ—΄λ‘œ λ°”κΏ” μ•”ν˜Έν™” 처리λ₯Ό ν•΄μ•Ό ν•œλ‹€. μ΄λ ‡κ²Œ ν•˜μ§€ μ•Šκ³  λ°”μ΄νŠΈ λ°°μ—΄μ˜ SHA-256 ν•΄μ‹œκ°’μ„ κ·ΈλŒ€λ‘œ μ•”ν˜Έν™”ν•˜κ²Œ 되면 λ³΅ν˜Έν™” λ©”μ„œλ“œμ—μ„œ new String으둜 리턴할 경우 μ—‰λš±ν•œ 값이 λ‚˜μ˜€κ²Œ λœλ‹€. μΈμ½”λ”©ν•˜μ§€ μ•Šμ€ ν•΄μ‹œκ°’μ˜ λ°”μ΄νŠΈ λ°°μ—΄ κ·ΈλŒ€λ‘œ λ¬Έμžμ—΄ νƒ€μž…μœΌλ‘œ λ°”λ€Œμ–΄ λ¦¬ν„΄λ˜κΈ° λ•Œλ¬Έμ΄λ‹€.

 

ν˜Ήμ—¬λ‚˜ 이 글을 보고 κ΅¬ν˜„ν•˜λŠ” μ‚¬λžŒμ΄ μžˆλ‹€λ©΄ μ–Έμ œλ“ μ§€ λŒ“κΈ€μ„ 남기면 λ³΄λŠ” μ¦‰μ‹œ λ‹΅λ³€ν•˜κ² λ‹€. 끗.

 

 

 

μ°Έκ³ 

뀼튼

 

728x90