솔직히 아쉽긴 한데 그래도 완전 망한 성적은 아니어서 만족하는 중. C를 못 푼건 그렇다치고 B는 100 맞았어야 했다.

중간에 바빠서 정신 없었던 것도 있지만 어쨌든 B에 대한 접근이 정해와 조금 많이 떨어져 있어서 100은 못 먹었을 듯.


A. 벽 칠하기


각 길이 $m$ 구간이 "칠하는 것이 가능한 구간"임을 판별할 수 있다면, 그 후의 계산은 간단한 그리디 알고리즘으로 할 수 있다.

이제 이걸 판단하는 것이 중요한데, 이제부터 색깔 $i$를 좋아하는 사람의 집합을 $S_i$라 하자.

그럼 구간 $[i, i+m)$이 색칠 가능한 것은, $t \in S_{C[i]}$, $t+1 \in S_{C[i+1]}$, $\cdots$, $t+m-1 \in S_{C[i+m-1]}$을 만족하는 정수 $t$가 존재하는 것이다. 

모든 인덱스는 $\pmod{m}$으로 본다. 물론, 조건 판별을 이대로하면 고통스러울 것이 뻔하다. 그러니 접근을 바꿔보자.

생각해보면, 저 인덱스들의 차이는 $i-t$로 동일하다는 것을 알 수 있다. 그러니 이를 활용하면 뭔가 보일 것 같다.

즉, $u \in S_{C[v]}$에 대하여 $v-u$의 카운트를 $1$씩 증가시키면, 카운트가 $m$인 정수가 존재하는지 판별하면 된다.

이제 이걸 효율적으로 관리할 방법을 생각해보자. $[0, m)$에서 시작되는 구간을 오른쪽으로 움직여보자.

$[i. i+m)$에서 $[i+1, i+m+1)$로 가려면, $S_{C[i]}$에 있는 정보를 제거하고 $S_{C[i+m]}$의 정보를 추가해야 한다.

우리가 지원해야 하는 연산은 값 하나를 바꾸고, 전체의 최댓값을 뽑아내는 것이다. 이는 세그먼트 트리로 할 수 있다.

또한, 각 $i$에 대하여 $S_{C[i]}$는 $\sqrt{400000}$ 이하이므로, 업데이트 횟수가 $N \sqrt{400000}$ 이하가 된다.

그런데 내 경험상 세그먼트 트리를 붙이면 TLE가 나고, 로그를 떼어내야 AC를 받을 수 있다.

로그를 떼는 방법은 간단한데, 필요한 것이 최댓값이 $m$인지만을 확인하는 것임을 사용하면 된다.

그냥 $c[x]$를 카운트가 $x$인 정수의 개수, $cnt[x]$를 $x$의 카운트 값이라고 정의하면 업데이트가 상수 시간에 된다. 


B. 자매 도시


Subtask 1은 각 연결컴포넌트가 path 아니면 cycle이다. path이면 당연히 실패하며, cycle이면 그 중 간선 길이 최댓값이다.


Subtask 2는 그래프가 매우 특수한 형태를 갖는다. 경우를 나눠서 생각하면 간선 길이 중 최소 3~4개 정도만 생각하면 된다는 사실을 알 수 있다. 

이는 원하는 방식으로 적당히 구현하면 된다. 나는 멀티셋 썼다. 근데 은근 케이스 생각하기 헷갈리긴 했다 ㅋㅋ


Subtask 3, 4를 위해서는 간단한 관찰이 필요한데, 바로 필요충분조건이 연결성 및 (사이클 존재 또는 차수 3 이상 정점 존재)라는 것이다.

물론, 사이클이 단순 사이클이 아니면 차수 3 이상 정점은 무조건 존재하게 되어있다. 이제 생각하기 편하다.

이제 문제를 각 쿼리당 $\mathcal{O}(N+M)$ 시간에 해결할 수 있다. 간선을 작은 것부터 순서대로 이으면서, disjoint set으로 위 사실들을 관리하면 된다. 


Subtask 5를 위해서 나는 트리 DP를 활용했다. 오프라인 풀이가 너무 자명해서 small-to-large 같은 게 먹히나 생각을 했었는데, 뭔가 아닌 것 같아서 트리를 긁기로 했다. 근데 저 방향이 정해여서 너무 아쉽다 ㅋㅋㅋ 트리라도 맞은 걸 다행이라고 생각해야겠다.


여기서 좋은 점은 사이클 존재의 경우를 생각하지 않아도 된다는 것이다. 연결성과 차수 3 이상 정점만 다루면 된다.

연결성을 위한 "최대 간선 길이의 최솟값"과, 차수 3 이상 정점을 위한 "최대 간선 길이의 최솟값"을 구한 뒤 최댓값을 취하자.


첫 번째 문제는 어렵지 않고, 잘 알려진 편이다. MST를 찾아주고, 거기서 경로 최댓값을 찾으면 충분하기 때문이다.

그러니까 단순하게 크루스칼 + Sparse Table을 열심히 구현하면 이 부분을 큰 문제 없이 해결할 수 있다.


두 번째 문제는 엄청 어려운 건 아닌 것 같지만 트리 DP 근본이 없는 수준인 나에게는 힘들었다.

우선 차수 3 이상 정점이 어디서 나올 것인가에 대해서 논의를 할 필요가 있다. 쿼리로 들어온 정점이 $X, Y$라고 하자. 

 

우선 공통된 것이 있다. $X$와 $Y$ 사이의 경로에서 차수 3 이상 정점이 나올 수 있다.

이 정점들은 (단, $X, Y$ 제외) $X, Y$가 연결된 시점에서 이미 차수가 2 이상인 것이 보장되므로, 간선 하나만 더 그으면 끝이다.

여기서 생각을 해보면 중요한 것은 각 정점에서 뻗어나오는 "3번째로 길이가 작은 간선의 길이"가 된다.

그러니 첫 번째 문제와 같은 방법으로 Sparse Table을 적용하면 이 경우를 해결할 수 있다. 


그런데 $X, Y$는 조금 다른 점이 있다. 밑으로 내려갈 수도 있기 때문이다! 잠시 $Z = \text{lca}(X, Y)$라고 하자.

$Z \neq X$라면, 차수 3 정점을 찾기 위해서 $X$에서 트리를 내려가 정점 $W$에 도착한 뒤, $W$에서 가지를 2개 치는 방법이 있다.

이 경우 필요한 최대 길이는 $X$에서 $W$로 내려가는 길이 중 최대, 그리고 $W$의 가지 중 두 번째로 작은 것이 되겠다.

이러한 경로로 가능한 것 중 필요한 최대 길이의 최솟값을 구해야 하는데, 이는 트리 DP로 미리 전처리를 할 수 있다.

$Z \neq Y$인 경우에도 이러한 계산을 하고, 이를 쿼리를 답할 때에 고려해야 한다. 


마지막으로, $Z=X$거나 $Z=Y$인 경우를 생각할 필요가 있다. 이 경우에는 위로 올라갈 수 있다!

앞선 경우와 큰 차이는 없지만, 개인적으로는 조금 헷갈렸다. 비슷한 스타일의 DP를 한 번 더 해주면 된다.


매우 고통스러운 풀이였는데, 이걸 어쨌든 짜서 서브태스크를 긁었다는 게 다행이다. 솔직히 틀릴 것 같았다.


이 풀이를 일반적인 경우로 확장하려고 했는데, 단순 사이클을 다루는 것이 어마어마하게 힘들더라 ㅠㅠ   


C. 즐거운 행로


Subtask 1, 2는 우선 트리 전체의 구조를 $N^2$번의 쿼리로 구한다. (모든 거리를 알면 복원은 자명)

그 후, 트리의 지름을 잡고 가능한 정점 중 가장 먼 곳을 반복적으로 사용하면 된다. 


Subtask 3은 이 과정을 전형적인 이진트리에서 하면 된다. 위 과정을 그대로 적용하되, 트리의 높이가 작다는 점을 이용한다.

그냥 각 정점에 대해서 "내 왼쪽에 있는 정점" / "내 오른쪽에 있는 정점"의 집합을 관리하면 된다. 

'PS > 대회 후기' 카테고리의 다른 글

SCPC 2020 2차 예선 풀이  (1) 2020.09.05
SCPC 2020 1차 예선 풀이  (0) 2020.08.22
ACM-ICPC Seoul Regional 2019  (0) 2019.11.11
나는 코더다 2018 송년대회  (5) 2018.12.22
NYPC 2018 본선 복기 및 후기  (8) 2018.10.28


CryptoHack에서 활동하는 사람들과 같이 대규모로 팀을 만들어서 CTF 대회에 나갔다.

1등한 팀은 1인팀이라는데, 실력이 어마어마하다고 유명한 사람이라고 한다. 2등팀에서 1인분은 한 것 같으니 만족.

개인적인 경험과 풀이는 시간이 조금 나면 쓸 것 같고, 대신 팀에서 write-up을 곧 올릴 것 같다. 


이런 재능있는 사람들이랑 토론하면서 공부할 수 있다는 것 자체가 축복같다.


2등도 잘한거야 쿠쿠루쿠루루쿠쿠


UPD : 여기에서 볼 수 있다.

'CTF' 카테고리의 다른 글

CryptoHack All Solve  (3) 2020.09.30
TokyoWesternCTF 2020 Crypto Write-Ups  (2) 2020.09.20
DownUnderCTF Crypto Write-Ups  (2) 2020.09.19
InterKosenCTF 2020 Crypto 분야 Write-Ups (?)  (0) 2020.09.06
CryptoHack Write-Ups  (0) 2020.06.27

BOJ 19245 XOR

BOJ 19078 New Divide

BOJ 19273 XorTree

BOJ 19458 Expected LCP

BOJ 18956 Little Q and Big Integers

BOJ 19570 삼각 분할

BOJ 19424 Power of Power Partition Function


'PS > 수학 계열 PS' 카테고리의 다른 글

Project Euler 550+  (0) 2021.04.09
8월의 PS 일지 - Part 3  (0) 2020.08.10
8월의 PS 일지 - Part 1  (0) 2020.08.04
Brief Introduction to Hackenbush Games  (1) 2020.05.17
5월의 PS 일지 - Part 1  (0) 2020.05.16

추가적으로 더 해결한 수학 문제들과, 최근에 친 AGC 문제들을 다룬다.


BOJ 15983 순간이동 발판

BOJ 19302 XOR Transformation

BOJ 19390 Coprime Queries

BOJ 19138 GCD vs LCM

BOJ 19495 Square Function

AGC 047

출처는 잘 모르는 문제. 

입력으로는 $a, N$이 주어지며, 이때 $1 \le x \le y \le N$, $\text{gcd}(x, y)=1$, $x|y^2+a$, $y|x^2+a$를 만족하는 $(x, y)$의 개수를 구해야 한다. 

$1 \le a \le 10^5$, $1 \le N \le 10^{18}$. 테케는 총 $10^6$개가 있으며, 전부 5초 내에 해결해야 한다. 메모리 제한은 256MB.


'PS > 수학 계열 PS' 카테고리의 다른 글

Project Euler 550+  (0) 2021.04.09
8월의 PS 일지 - Part 5  (0) 2020.08.12
8월의 PS 일지 - Part 1  (0) 2020.08.04
Brief Introduction to Hackenbush Games  (1) 2020.05.17
5월의 PS 일지 - Part 1  (0) 2020.05.16

BOJ 19549 레이저 연구소

BOJ 18975 Jaw-Dropping Set

BOJ 19069 Rock-Paper-Scissors

BOJ 19003 Rikka with Equation

BOJ 19000 Rikka with Proper Fractions

BOJ 18764 LCM Sum


'PS > 수학 계열 PS' 카테고리의 다른 글

8월의 PS 일지 - Part 5  (0) 2020.08.12
8월의 PS 일지 - Part 3  (0) 2020.08.10
Brief Introduction to Hackenbush Games  (1) 2020.05.17
5월의 PS 일지 - Part 1  (0) 2020.05.16
4월의 PS 일지 - Part 5  (0) 2020.05.02

하라는 공부는 안하고 단간론파 시리즈를 단간, 슈단, 뉴단 V3까지 정주행을 했다. 


몰입도 하고 느낀 것도 많고 재미도 있고 여운도 강하게 남아서 딱히 후회되지는 않는다.


빠르게 계절학기 잘 마무리하고 제대로 공부에 복귀해야겠다 ㅋㅋ

'취미' 카테고리의 다른 글

EVERYTHING WE NEED IS ALREADY HERE  (0) 2024.12.16
CALL ME IF YOU GET LOST  (0) 2021.07.29
Squarepusher - Beep Street  (0) 2019.11.27
2019/11/16 ~ 2019/11/17 CHUNITHM 성과 정리  (0) 2019.11.17
2019/06/17~ 2019/06/21 CHUNITHM 성과 정리  (0) 2019.06.23

Matus Nemec, Marek Sys, Petr Svenda, Dusan Klinec, Vashek Matyas - 2017


1. Introduction


이 논문은 2017년의 논문으로, 제목처럼 많이 사용되는 RSA 공개키를 소인수분해하는 방법을 다룬다. 

앞선 논문 리딩에서 언급했듯이 RSA 공개키를 소인수분해하면 RSA 기반 암호는 깨진다. 이는 Textbook RSA든 RSA-OAEP든 마찬가지다.

그러니 이 논문은 당연하게도 많은 사람들에게 충격을 주었고, 실제로 이 논문 때문에 시스템을 교체한 곳도 많다고 한다.

논문의 제목처럼, 이 공격은 저번에도 언급된 Coppersmith's Attack과 관련이 깊다. 그러니 이에 대한 이해가 필수적이다.

이번 논문 리딩에서는 Lattice에 대한 기본적인 내용을 훑고, Coppersmith's Attack에 대한 내용을 증명과 함께 살펴본다.

그 후, 본격적으로 논문을 읽으면서 많이 사용되는 RSA 공개키들이 어떻게 생성되고, 이들이 어떠한 약점을 가지고 있는지 살펴본다. 


2. Brief Introduction to Lattices


사실 나도 잘 모르는 분야지만, 이 논문을 읽는데 필요한 지식들을 간단하게 나열해본다. 자세한 건 나도 빨리 배워야한다.

우리가 다룰 Lattice란, 쉽게 말해서 $\mathbb{R}^n$의 선형독립인 벡터 $v_1, v_2, \cdots, v_d$가 있을 때, $\{ \sum_{i=1}^d a_i v_i  | a_i \in \mathbb{Z} \}$에 해당한다. 

즉, $\mathbb{R}^n$의 subspace를 생각하되 가능한 계수를 $\mathbb{Z}$로 한정시켜서 생각하는 것이다.

이러한 Lattice가 주어졌을 때, 생각할 수 있는 어려운 문제들이 있다. 이들을 간단하게 소개한다.

Shortest Vector Problem: Lattice에 속하는 nonzero vector 중 가장 norm이 작은 것을 구한다.

Closest Vector Problem: Lattice에 속하는 vector 중 주어진 벡터에 가장 가까운 것을 구한다.

이 문제들은 상당히 어려운 것으로 알려져 있고, 이는 곧 Lattice가 암호학에 쓰이는 이유가 된다. (양자내성암호)


Shortest Vector Problem에 대해서 조금 더 논의를 해보자. 

Geometry of Numbers에서 가장 중요한 정리 중 하나인 Minkowski's Theorem을 생각해보자. 이를 활용하면, invertible한 $n \times n$ 행렬 $A$의 column으로 생성되는 Lattice에서 가장 짧은 nonzero vector의 norm이 최대 $\sqrt{n} \cdot \text{det}(A)^{1/n}$임을 어렵지 않게 증명할 수 있다.

그런데 이건 단순히 존재성의 여부이고, 실제로 계산하기에는 매우 어렵다. 

생각해보면, Lattice의 기저가 orthogonal인 경우에는 Shortest Vector Problem이 매우 쉽다는 것을 알 수 있다.

그러니 Lattice 자체는 그대로 유지하면서, 기저를 orthogonal하게 바꾸고 싶다는 전략이 나온다.

단순한 $\mathbb{R}^n$ 공간이었다면 Gram-Schmidt가 가능하지만, 여기서는 정수 계수를 유지해야 하므로 쉽지 않다.

이를 대신하는 알고리즘이 LLL 알고리즘이다. 자세한 디테일은 나도 모르므로 생략하지만, 다음 결과는 중요하다.

이 알고리즘을 통해서, 크기가 $2^{(n-1)/4} \cdot \text{det}(A)^{1/n}$ 이하인 nonzero vector를 찾을 수 있다는 것이다.

또한, LLL 알고리즘은 $n$에 대한 다항시간에 작동하므로, 효율적인 알고리즘이다.


3. Coppersmith's Attack


여기서는 Coppersmith's Attack이 무엇인지를 다룬다. 기본적으로 변수 1개인 경우를 다룬다.

다항식 $h(x) = \sum_{i=0}^n a_i x^i$에 대하여, $||h||^2 = \sum_{i=0}^n |a_i|^2$이라고 하자.


Theorem. (Coppersmith) $N$은 자연수고, $f \in \mathbb{Z}[x]$는 최고차항의 계수가 $1$인 $d$차 다항식이다. $\epsilon \ge 0$을 잡고, $X = N^{1/d - \epsilon}$이라 하자. $N, f$가 주어졌을 때, $|x_0| < X$와 $f(x_0) \equiv 0 \pmod{N}$을 만족하는 $x_0$를 모두 다항식 시간에 찾을 수 있다. 


이 정리를 증명하고, 실제로 $x_0$를 어떻게 찾는지까지 생각해보자.

그 전에 필요한 사전지식이 하나 더 있는데, 바로 다항식을 $\mathbb{Z}[x]$의 원소로 봤을 때 정수근을 찾는 것은 쉽다는 것이다.

이에 대한 내용을 알기 위해서는, 먼저 $\mathbb{F}_p$ 위에서 다항식의 인수분해가 쉽다는 것을 알아야 한다. 자료 참고.

그러니 (아마) 충분히 큰 $p$를 잡고 $\mathbb{F}_p$ 위에서의 다항식의 근을 찾은 다음 실제로 근이 되는지를 보면 될 것이다.

이제 본격적으로 Coppersmith's Theorem을 증명한다. 우선 핵심적인 Lemma를 소개한다.


Lemma. $h(x) \in \mathbb{Z}[x]$는 $d$차 다항식이고, $X$는 자연수다. 

$||h(xX)|| < N/\sqrt{d}$이며, $|x_0| < X$가 $h(x_0) \equiv 0 \pmod{N}$을 만족한다면, $h(x_0) = 0$이다.


Proof of Lemma. 접근의 방식 자체는 어렵지 않다. 목표는 $|h(x_0)|<N$을 보이는 것이다.

$|h(x_0)| = \left| \sum_{i=0}^d a_i x_0^i \right| = \left| \sum_{i=0}^d a_i X^i \left( \frac{x_0}{X} \right)^i \right| \le \sum_{i=0}^d \left| a_i X^i \left( \frac{x_0}{X} \right)^i \right| \le \sum_{i=0}^d |a_i X^i|$이다.

위 식의 우변은 Cauchy-Schwarz 부등식에의하여 $\sqrt{d} ||h(xX)||$ 이하가 되고, 다시 조건에 의하여 $N$ 미만이 된다. 증명 끝.


이제 새로운 전략이 나온다. $f(x) \equiv 0 \pmod{N}$의 근을 모두 찾고 싶다면, $f$의 배수인 다항식 중 norm이 작은 것을 하나 찾자.

만약 norm이 충분히 작은 다항식이 나온다면, 그 다항식의 정수근을 찾는 것으로 위 과정을 대체할 수 있다.

$\pmod{N}$을 생각하면서 근을 찾는 것보다 그냥 정수다항식의 정수근을 찾는 것이 훨씬 쉬우므로, 이는 괜찮은 전략이다.


이는 결국 $f(x), xf(x), x^2f(x), \cdots$를 잘 조합해서 작은 norm을 만드는 문제가 되고, Shortest Vector Problem의 냄새가 난다.

하지만 이렇게 단순한 방법으로는 좋은 다항식을 찾기 어렵다는 것을 알 수 있고, 약간의 트릭이 필요하다.

트릭은 $N$ 대신 $N$의 거듭제곱을 생각하는 것이다. $g_{u, v} (x) = N^{m-v} f(x)^v x^u$라는 다항식을 생각하자.

이때, $0 \le u \le d-1$이고 $0 \le v \le m$이다. $m$은 임의로 잡은 값이며 추후에 결정되는 자연수이다.


만약 $x_0$가 $f(x_0) \equiv 0 \pmod{N}$을 만족하면, $g_{u,v}(x)$는 자동으로 모두 $N^m$의 배수이다.

그러므로 바라보는 대상을 $g_{u,v}$로 바꾸고, norm에 대한 제약조건을 $N^m$에 대한 조건으로 바꿔주자.


이제 $n = d(m+1)$이라 하자. $g_{u, v}(xX)$의 차수는 $u + vd$이므로, 이는 $0$부터 $n-1$까지의 값을 순서대로 갖는다.

$g_{u,v}(xX)$의 계수들을 벡터로 보고 Lattice를 만든 후, 이를 행렬로 생각해보자. 이 행렬은 자명하게 triangular하다.

$g_{u,v}(xX)$의 최고차항의 계수가 $X^{u+dv} \cdot N^{m-v}$이므로, 이 행렬의 determinant는 이를 각 $u \in [0, d)$, $v \in [0, m]$에 대하여 곱한 값인 $X^{n(n-1)/2} \cdot N^{dm(m+1)/2}$가 된다. 이제 LLL 알고리즘을 돌려서 'Shortest Vector'를 찾자.

 

여기서 얻는 'Shortest Vector'의 길이는 최대 $2^{(n-1)/4} \left( X^{n(n-1)/2} \cdot N^{dm(m+1)/2} \right)^{1/n}$이다.

이는 정리하면 최대 $(\sqrt{2}X)^{(n-1)/2} \cdot N^{m/2}$가 되고, 우리의 목표는 이 값이 $N^m / \sqrt{n}$ 미만이 되는 것이다.

그런데 식을 정리해보면 $X = N^{1/d - \epsilon}$일 때 생각보다 잘 부등식이 정리가 되지 않음을 알 수 있다.

이를 해결하기 위해서, $X = \frac{1}{2} N^{1/d - \epsilon}$을 생각해서 우선 Coppersmith's Theorem의 약한 버전을 증명하자.

그 후, $X$의 상위 비트에 대해서 brute force를 진행하면서 각각의 경우에 대하여 위 정리를 반복해서 사용해주자.

그러면 $X$가 $N^{1/d - \epsilon}$인 경우에 대해서도 다항식 시간에 해를 모두 구할 수 있다는 것을 알 수 있고, 나아가 $\epsilon = 0$인 경우까지도 다룰 수 있다.


이 알고리즘은 변수가 두 개인 경우로 확장될 수 있으며, 이를 통해서 다음 결과도 얻을 수 있다.


Theorem. (Coppersmith) $N = pq$가 $n$-bit RSA modulus라 하자. 

이때, $p$의 $n/4$ least significant bits을 알거나 $p$의 $n/4$ most significant bits를 알면, $N$을 다항식 시간에 소인수분해 할 수 있다.


실제로 이 정리에 해당하는 공격을 수행하기 위해서, 변수가 하나인 형태의 Coppersmith's Attack을 사용할 수도 있다.

위 식에서 $N = pq$라고 하면, 우리는 $N$은 알지만 $p$는 모르는 상태다.

만약 충분히 큰 $r$에 대해서 $u = p \pmod{2^r}$을 알고 있다면, 결국 $2^r x + u$라는 일차식이 $\pmod{p}$에서 작은 근을 갖는다는 것을 알 수 있다.

이를 최고차항의 계수가 $1$이도록 바꿔주면 $x + u \cdot (2^{-r} \pmod{N})$이라는 일차식을 얻는다.

우리는 실제로 $p$를 모르지만, $p$의 배수 $N$을 알고 있으므로 Coppersmith's Attack의 모든 부분을 수행할 수 있다.

즉, norm이 $p^m / \sqrt{n}$ 이하인 다항식을 만들기 위해서 $N$을 사용하는 방식을 택할 수 있다.

위 방법을 그대로 사용하면 부등식 계산 부분에서 뭔가 잘 안된다는 것을 알 수 있다.

대신, $x^i f(x)^m$ 형태의 다항식을 몇 개 추가해주면 LLL 알고리즘이 잘 돌아갈 조건을 제공함을 알 수 있다. 이 구현 참고. 

직관적으로 생각해보면, $N$을 사용한 다항식은 계수가 지나치게 큰 것이므로 ($p$를 모르니 그 배수를 쓴 것이니) $x^i f(x)^m$ 형태의 다항식을 추가해서 이를 희석시킨 것이라고 생각해도 될 것 같다. 아무튼 하면 된다 :) 이제 본격적으로 논문을 읽어보자.


4. RSA Key Generation, and RSALib


RSA 키는 어떻게 만들까? 예를 들어 1024 비트 키를 만들고 싶다고 하자. 

그러면 512 비트 소수 $p, q$를 찾아서, $N=pq$를 잡고, $e = 2^{16}+1$이라 한 다음, $d$를 $ed \equiv 1 \pmod{\phi(N)}$이 성립하도록 잡으면 된다. 

안전성을 위해서는 $p, q$가 갖춰야 할 여러 조건들이 있고, 이에 대해서는 앞선 리딩에서 다뤘다.

하지만 결론적으로 웬만하면 랜덤한 $p, q$를 잡으면 된다는 사실 역시 알고 있다.

소수 정리에 의해서 $x$ 근처의 자연수가 소수일 확률은 매우 대충보면 $1/\log x$ 정도다.

그러니까 랜덤한 자연수를 잡고, Miller-Rabin 등 소수 판별 알고리즘을 돌리는 것을 소수가 나오기 전까지 반복하면 소수를 얻을 수 있다.


그런데 RSA 키를 가능한 빠르게 생성하고 싶다고 가정하자. 어떻게 위 과정을 최적화할 수 있을까?

기본적으로 소수를 찾는 과정이 가장 느리므로, 이 과정을 더 빠르게하면 좋을 것 같다.

당장 랜덤한 자연수를 찍으면 2부터 100까지의 소수 중 하나의 배수가 될 확률이 상당히 높을텐데, 이거라도 미리 거르면 좋을 것 같다.

그래서 RSALib는 다음과 같은 방법을 채택했다. $n$이란 자연수를 먼저 설정하는데, 이는 RSA 키의 길이에 따라서 결정된다.

그 후, $M$을 첫 $n$개의 소수의 곱으로 설정한다. 이제 랜덤한 $k, a$를 잡아서 $k \cdot M + (65537^a \pmod{M})$을 소수 후보로 잡는다.


이렇게 하면 첫 $n$개의 소수는 약수로 갖지 않게 된다는 것이 자명해지고, 소수 판별을 반복하는 횟수가 줄어든다.

그런데 생각해보면, $M$은 여러 소수의 곱이니 원시근이 없고, $65537^a \pmod{M}$은 $M$과 서로소인 모든 나머지를 표현하지 못한다.

다르게 말하면 이는 이 새로운 방식이 생성할 수 없는 소수들이 있고, 실제로 이 방식은 랜덤하지 않다는 것이다.


또한, $p, q$가 이 방식으로 생성되었다면, $N \pmod{M}$ 역시 $65537$의 거듭제곱으로 표현할 수 있다.

$M$은 큰 값이지만 smooth 하므로, Pohlig-Hellman Algorithm으로 $N \equiv 65537^c \pmod{M}$인 $c$를 찾을 수 있다.

이는 생성된 키 $N$이 제대로 계산되었는지 (RSALib로 생성되었는지) 확인하는 방법이 될 수도 있다.

다시 강조하지만, $M$과 서로소인 나머지 중 $65537^a \pmod{M}$으로 표현될 수 있는 값의 비율은 매우 낮다.


이제 이 RSA 키의 특수한 형태를 극도로 활용하여, $N$을 아예 소인수분해하는 방법을 찾아보자.


5. The ROCA Vulnerability


생각보다 아이디어는 그렇게 어렵지 않다. $p = k \cdot M + (65537^a \pmod{M})$이라 하자.

만약 우리가 $a$를 안다면, $p$의 하위 비트를 아는 것이 되며 이는 Coppersmith's Attack을 사용할 환경을 제공한다.


$T$를 $\pmod{M}$에 대한 $65537$의 order라고 하자. $c$를 $N \equiv 65537^c \pmod{M}$인 값이라고 하자.

$T, c$는 모두 어렵지 않은 정수론 알고리즘을 (order-finding algorithm, Pohlig-Hellman) 사용하여 계산할 수 있다.


이제 $a$를 $c/2$부터 $c/2 + T/2$까지를 brute-force하면서 계산하면, $p$가 걸리던 $q$가 걸리던 뭔가 걸린다.

즉, Coppersmith 공격을 약 $T/2$번 반복하면, $N$을 소인수분해 할 수 있다. 여기서 $T$가 작으면 좋겠다는 생각을 할 수 있다.


이를 위해서 일종의 최적화를 한다. $M'|M$인 $M'$을 잡되, 다음 조건을 모두 만족하도록 하자.

조건 1: $\pmod{M'}$에 대한 $65537$의 order가 작은 편이다.

조건 2: $M'$이 충분히 커서, $p \pmod{M'}$을 고정하면 Coppersmith's Attack을 수행할 수 있다. 즉 $M' > N^{1/4}$.


$M'$을 최적화하는 과정은 휴리스틱, 그리디, 전탐색 등을 든든하게 섞어서 해시코드 식 접근을 했다고 한다.

이제 이 과정을 모두 합치면 $N$을 소인수분해 할 수 있다. 대표적인 구현은 이 코드를 참고하자.