본문 바로가기

문제 노트/백준

RSA( BOJ 13618 )

문제 : https://www.acmicpc.net/problem/13618

 

13618번: RSA

A única linha da entrada contém três inteiros N, E, e C, onde 15 ≤ N ≤ 109 , 1 ≤ E < N e 1 ≤ C < N, de forma que N e E constituem a chave pública do algoritmo RSA descrita acima e C é uma mensagem criptografada com essa chave pública.

www.acmicpc.net

 

문제 파악하기

포르투갈 언어로 작성되어 있는 문제라 조금 읽기 어려웠지만, 단순히 RSA 알고리즘을 기반으로 만든 문제이기 때문에 문제를 푸는 데에는 큰 무리가 없습니다. 이 문제는 RSA 알고리즘으로 암호화된 값(C)을 복호화하는 문제입니다. 만약 RSA 알고리즘을 잘 모르겠다면 위키피디아의 설명을 먼저 읽어보면 쉽게 문제를 풀 수 있습니다.

 

우선 RSA 알고리즘에서 키를 생성하는 과정은 다음과 같습니다.

 

  • 두 수를 곱하여 N = p*q( p와 q는 서로 다른 소수)를 찾는다.
  • φ( N ) = ( p − 1 ) ( q − 1 ) 를 구한다.
  • φ( N ) 보단 작고, φ( N ) 와 서로소인 정수 E (1 < E < φ( N ))를 찾는다.
  • E φ( N ) 으로 나누었을 때 나머지가 1인 정수 d를 구한다. ( E*d ≡ 1 ( mod φ( N ) )

이렇게 생성한 값을 가지고 공개키(N, e)와 비밀키(N, d)를 가지고 암호화/복호화를 할 수 있습니다. 우선 암호화는 공개키를 가지고 다음과 같이 진행합니다. (M은 원문, C는 암호화된 원문을 의미한다)

 

C = Me (mod N)

 

암호화된 메세지를 복호화하기 위해서는 다음 과정을 거치면 됩니다.

 

M = Cd (mod N)

 

따라서, 원문(M)을 구하기 위해서는 N을 통해 d를 구한 다음, Cd를 구하면 됩니다. 하나씩 단계를 거치면서 원문(M)을 구해봅시다.

 

문제 해결하기

우선, 정수 d를 구하기 위해서는 φ( N )를 구해야 합니다. φ( N )을 구하는 방법은 우선 N의 약수를 살펴본 다음, 짝을 이루는 두 약수(p, q)가 모두 소수이며, φ( N )의 값((p-1)*(q-1))과 입력된 값 E가 서로소인지 확인하면 됩니다. 빠른 계산을 위해 약수를 구할 때에는 O(√N) 알고리즘을 사용하면 됩니다.

 

φ( N )을 구했으면 다음으로는 E*d ≡ 1 ( mod φ( N ) )를 만족하는 d를 구하면 됩니다. 이는 확장 유클리드 호제법을 활용하면 쉽게 구할 수 있습니다. 다만, d만큼 거듭제곱 해야하기 때문에 d의 값을 0 이상으로 구해주어야 나중에 계산할 때 오류가 발생하지 않습니다. d를 구했다면 마지막으로 거듭제곱을 빠르게 계산해야 합니다. 이 때, 우리는 분할정복 기법을 사용할 수 있습니다. 분할정복 기법으로 거듭제곱을 빠르게 구하면 우리가 원하는 원문(M)을 구할 수 있습니다.

 

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <utility>
using namespace std;
typedef long long int lint;
typedef pair<intint> PAIR;
 
lint N, E, C, D, M;
lint p, q, phi;
 
lint pow(lint val, lint k) {
    if(k == 1return val;
    else {
        if(k%2 == 0return pow((val*val)%N, k/2);
        else return ( pow((val*val)%N, k/2)*val )%N;
    }
}
 
lint gcd(lint a, lint b) {
    if(a == 0return b;
    else if(b == 0return a;
    else return gcd(b, a%b);
}
 
// ax + by = 1
PAIR EEA(lint a, lint b) {
    if(a == 0return make_pair(01);
    else if(b == 0return make_pair(10);
    else {
        PAIR prev = EEA(b, a%b);
        lint x = (prev.second%phi + phi)%phi;
        lint y = ((prev.first - (a/b)*x)%phi + phi)%phi;
 
        return make_pair(x, y);
    }
}
 
bool isPrime(lint k) {
    if(k%2 == 0return 0;
 
    for(lint i=3;i*i<=k;i+=2) {
        if(k%i == 0return 0;
    }
 
    return 1;
}
 
int main() {
    // 입력
    scanf("%lld %lld %lld"&N, &E, &C);
 
    // p, q 구하기 >> 오일러 피함수 구하기
    for(lint i=2;i*i<N;i++) {
        if(N%i > 0continue;
 
        p = i;
        q = N/i;
        phi = (p-1)*(q-1);
        if(isPrime(p) and isPrime(q) and gcd(phi, E) == 1break;
    }
 
    // D 구하기
    D = EEA(E, phi).first;
 
    // C^D (mod N) 구하기
    M = pow(C, D);
 
    // M 출력하기(복호화)
    printf("%lld", M);
}
cs

후기

소수 판별, 유클리드 호제법, 확장 유클리드 호제법, 그리고 분할정복 기법을 활용한 거듭제곱 계산까지 수학적 알고리즘을 한 번에 실습할 수 있는 문제입니다. 수학 알고리즘을 연습하고 싶은 사람에게 추천하고 싶은 문제입니다.

'문제 노트 > 백준' 카테고리의 다른 글

홍준이의 친위대( BOJ 3948 )  (0) 2022.07.17
스승님 찾기( BOJ 15979 )  (0) 2022.07.14
추첨상 사수 대작전! (Hard) ( BOJ 20412 )  (0) 2022.07.12
천상용섬( BOJ 12758 )  (0) 2022.06.29
주식( BOJ 11501 )  (0) 2022.06.26