반응형

공개키 암호화 알고리즘이란? - 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

+ Recent posts