-
[JAVA] RSA 알고리즘에 대해 쉽게 알아보고 구현해보기Algorithm 2024. 12. 29. 01:31728x90반응형
RSA 알고리즘은 공개키와 비밀키라는 두 개의 키를 사용하여 데이터를 암호화하고 복호화하는 방식입니다.
이 알고리즘의 핵심은 두 개의 소수를 사용하여 공개키와 비밀키를 생성하고 이를 통해 안전하게 정보를 전달하는 것입니다.- 공개키 : 공개키는 공유되며 암호화에 사용됩니다.
- 비밀키 : 공유되면 안되며 암호화된 데이터를 복호화하는 데 사용됩니다.
RSA 알고리즘
RSA 알고리즘을 구현하기 위한 몇가지 과정은 아래와 같습니다.
1. 2개의 소수 선택 및 N 추출
소수 P와 Q를 선택한 후 N을 계산합니다.
예를 들어 P = 5, Q = 11를 선택한다면 N = P * Q = 55가 됩니다.
중요한 점은 P와 Q가 노출되면 공개키와 비밀키를 만들 수 있기 때문에 꼭 제거해야합니다.2. 오일러 피 함수( φ(N) ) 계산
오일러 피 함수( φ(N))는 N과 서로소인 숫자의 개수를 구하는 함수입니다.
φ(N) = (p - 1) * (q - 1)
위 공식에 대입하면 φ(N) = (5 - 1) * (11 - 1) = 4 * 10 = 40이 됩니다.
3. 공개키 E
공개키 E는 1보다 크며 φ(N)보다 작아야합니다.
그리고 E는 φ(N)과 서로소여야 합니다.1 < E < φ(N)
여기서 1 ~ φ(N) 중에 φ(N)를 소인수 분해한 결과의 배수를 모두 지운 후 일반적으로 가장 작은 수를 선택하게 됩니다.
해당 예시에서는 3이 됩니다.4. 비밀키 D
비밀키 D는 ED = (K * φ(N)) + 1에 부합하는 수를 의미합니다.
ED = 1(mod φ(N))
E와 D의 곱을 φ(N)으로 나누었을 때 나머지가 1이 되어야 합니다.
이를 풀어서 만들면 ED = (K * φ(N)) + 1 이렇게 만들 수 있습니다.
여기서 K는 배수를 의미하며 현재 예시의 결과로 보면 3d = 41, 81, 121이 되며 d는 27입니다.5. 암호화
현재 공개키는 N, E가 되며 예를 들어 2를 암호화 하나면 2^E를 N으로 나눈 나머지 즉 8이 됩니다.
6. 복호화
현재 비밀키는 N,D가 되며 암호화된 8을 복호화 한다면 8^D를 N으로 나눈 나머지 즉 2로 복호화됩니다.
Java 직접 RSA 구현
@Slf4j public class RsaTest { @Test public void rsa() { // 공개되면 큰일나는 소수 두개 BigInteger p = BigInteger.valueOf(1000000007); BigInteger q = BigInteger.valueOf(1000000009); // n = p * q ( 공개 ) // n이 충분히 커야 한글과 같이 유니코드 값이 큰 문자도 암호화/복호화 가능 BigInteger n = p.multiply(q); // φ(N) 계산: φ(N) = (p-1) * (q-1) BigInteger eulerN = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE)); // 공개키 (public key) BigInteger e = chooseE(eulerN); // 비밀키 (private key) BigInteger d = calculateD(e, eulerN); log.info("Public Key = ({}, {})", n, e); log.info("private Key = ({}, {})", n, d); String message = "Hello! RSA!\n안녕 알에스에이\n123456"; log.info("메시지 : {}", message); // 암호화 StringBuilder encryptedMessage = new StringBuilder(); for (char c : message.toCharArray()) { // 문자 c를 유니코드 값(BigInteger로 변환) BigInteger m = BigInteger.valueOf(c); // 암호화 BigInteger encryptedChar = encryptionOrDecryption(m, e, n); // 16진수로 변환 후 추가 encryptedMessage.append(encryptedChar.toString(16)).append(" "); } log.info("[Hex] 암호화 된 메시지 : {}", encryptedMessage.toString()); // 복호화 String[] encryptedMessageArray = encryptedMessage.toString().split(" "); StringBuilder decryptedMessage = new StringBuilder(); for (String em : encryptedMessageArray) { // 16진수 문자열을 BigInteger로 변환 BigInteger encryptedChar = new BigInteger(em, 16); // 복호화 BigInteger decryptedChar = encryptionOrDecryption(encryptedChar, d, n); // 복호화된 숫자를 문자로 변환하여 메시지에 추가 decryptedMessage.append((char) decryptedChar.intValue()); } log.info("복호화 된 메시지 : {}", decryptedMessage.toString()); } private static BigInteger encryptionOrDecryption(BigInteger m, BigInteger key, BigInteger n) { return m.modPow(key, n); } private static BigInteger calculateD(BigInteger e, BigInteger phiN) { return e.modInverse(phiN); } private BigInteger chooseE(BigInteger phiN) { BigInteger e = BigInteger.valueOf(2); while (e.compareTo(phiN) < 0) { if (e.gcd(phiN).equals(BigInteger.ONE)) { return e; } e = e.add(BigInteger.ONE); } return null; } }
1. 소수 생성
예제에서는 소수를 직접 입력했지만 실제로 소수를 만들때는 BigInteger.probablePrime(bitLength, random) 메서드를 사용하여 생성할 수 있으며 해당 메소드는 내부적으로 밀러-라빈 알고리즘을 통해 소수 검증을 하며 주어진 비트 길이에 맞는 소수를 반환합니다.
// 1024 : 비트 길이 // new SecureRandom() : 난수 생성기 BigInteger test = BigInteger.probablePrime(1024, new SecureRandom());
2. 공개키와 비밀키의 일치 문제
간혹 공개키와 비밀키가 서로 같아지는 문제가 발생하는데 이를 해결하기 위해서 실제 사용시 e와 d가 같다면 p와 q를 다시 생성하도록 만들어줘야 합니다.
Java 라이브러리로 RSA 구현
@Test void rsaTest() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048); KeyPair keyPair = keyPairGen.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String message = "안녕하세요!\nHello!\nThis is RSA"; System.out.println("origin message = " + message); // 암호화 Cipher encryptCipher = Cipher.getInstance("RSA"); encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey); // 공개키로 암호화 byte[] encryptedMessage = encryptCipher.doFinal(message.getBytes(StandardCharsets.UTF_8)); System.out.println("encrypted message = " + Base64.getEncoder().encodeToString(encryptedMessage)); // 복호화 Cipher decryptCipher = Cipher.getInstance("RSA"); decryptCipher.init(Cipher.DECRYPT_MODE, privateKey); // 비밀키로 복호화 byte[] decryptedMessage = decryptCipher.doFinal(encryptedMessage); String decryptedText = new String(decryptedMessage, StandardCharsets.UTF_8); System.out.println("decrypted message = " + decryptedText); assertThat(decryptedText).isEqualTo(message); }
결론
RSA는 매우 큰 수의 연산을 기반으로 하기 때문에 짧은 데이터를 암호화하고 복호화하는 데 적합합니다.
긴 데이터를 처리하려면 효율성이 떨어질 수 있으므로 일반적으로 RSA는 대칭 키 암호화를 위한 키 교환이나 디지털 서명 같은 용도로 사용된다고 합니다.
긴 데이터의 경우 다른 암호화 알고리즘을 사용하는 것이 적합합니다.728x90반응형'Algorithm' 카테고리의 다른 글
[Greedy Algorithm] 백준 2217번 : 로프(Java) (1) 2023.08.04 [Greedy Algorithm] 백준 5585번 : 거스름돈 (Java) (1) 2023.08.04 [Greedy Algorithm] 백준 1541번 : 잃어버린 괄호 (Java) (0) 2023.08.04 [Greedy Algorithm] 백준 1931번 : 회의실 배정 (Java) (0) 2023.08.04 [Greedy Algorithm] 백준 11047번 : 동전 0(Java) (0) 2023.08.04