CTF Crypto task를 풀다 보면, 간혹 m2 mod p 값으로부터 m의 값을 복원해야 하는 경우가 존재하며, 이때 사용할 수 있는 알고리즘이 Tonelli-Shanks Algorithm이다.

Tonelli-Shanks Algorithm은 Z/pZ에서 제곱근을 O(log2p)에 해결하는 알고리즘으로써, 조금 응용한다면 이차방정식까지도 동일한 시간복잡도에 해결 가능한 알고리즘이다.

 

주어진 홀수인 소수 pnZ/pZ에 대해 x2n (mod p)x를 출력하는 Tonelli-Shanks Algorithm은 다음과 같은 단계를 거쳐 진행된다.

  1. np121 (mod p)인지 확인한다. 만약 아니라면 제곱근이 존재하지 않는다.
  2. p1=Q2S인 홀수 Q와 음이 아닌 정수 S를 구한다.
  3. i:=2i를 초기화한 후 ip121 (mod p)이 아닐 때까지 i를 증가시키는 과정을 반복한다. 종료 시점의 i 값을 z라고 정의한다.
  4. 다음과 같이 변수에 값을 대입한다.
    M:=S
    c:≡zQ (mod p)
    t:≡nQ (mod p)
    R:≡nQ+12 (mod p)
  5. 다음 과정을 반복한다.
    t=0이면 0을 출력하고 종료한다.
    t=1이면 R을 출력하고 종료한다.
    0<i<Mt2i1 (mod p)를 만족하는 최소의 자연수 i를 찾는다.
    b:≡c2Mi1 (mod p)
    M:=i
    c:≡b2 (mod p)
    t:≡tb2 (mod p)
    R:≡Rb (mod p)

Tonelli-Shanks 알고리즘의 정당성 증명

알고리즘의 정당성 증명은 다음과 같이 기술할 수 있다.

우선 다음의 두 명제를 확인하자.

Claim 1.

  • mod p에서 0이 아닌 quadratic residue는 정확히 p12개 존재한다.

1kp12일 때 k2(pk)2 (mod p)이다. 따라서 0이 아닌 quadratic residue는 최대 p12개 존재한다.

1a<bp12인 두 정수 a,b를 생각해보자. b2a2(a+b)(ab) (mod p)이고, 2a+bp1, ab이므로 b2a20 (mod p)이다.

따라서 1,2,3,...,p12는 제곱하였을 때 서로 다른 값을 가지므로 정확히 p12개의 0이 아닌 quadratic residue가 존재한다.

 

Claim 2.

  • 'nmod p에서 quadratic residue이다'와 np121 (mod p)는 동치이다.

nmod p에서 quadratic residue인 것과 x2n (mod p)x가 존재하는 것은 동치이다.

이는 페르마 소정리에 의해 np12(x2)p12xp11 (mod p)과 동치이다.

 

변수들의 초기값을 다시 기술하면 다음과 같다.

  • M=S
  • czQ (mod p)
  • tnQ (mod p)
  • RnQ+12 (mod p)

상기된 초기값에 관해 다음과 같은 사실을 알 수 있다.

  • z는 quadratic non-residue이므로 c2M1zQ2S1zp121 (mod p)이 성립한다.
  • n은 quadratic residue이므로 t2M1nQ2S1np121 (mod p)이 성립한다.
  • R2nQ+1nt (mod p)이다.

한 번의 반복에서 M,c,t,R에 새로이 대입되는 값 M,c,t,R는 다음과 같이 정의됨을 기억하자.

  • bc2Mi1 (mod p)
  • M0<M<Mt2M1 (mod p)를 만족하는 최소의 자연수
  • cb2 (mod p)
  • ttb2 (mod p)
  • RRb (mod p)

이러한 정의에 따라 변화한 값에 대해서도 초기값에서와 동일한 성질들이 몇가지 성립함을 확인할 수 있다.

  • c2M1b2ic2M11 (mod p)
  • t2M1t2M1b2M(1)×(1)1 (mod p)
  • R2R2b2ntb2nt (mod p)

이때 t2M11 (mod p)이므로 0<M<MM은 항상 존재하며 M의 값이 항상 감소한다. 이러한 반복 과정에서 M=1이 되는 순간 1t2M1t1 (mod p)이므로 t1 (mod p)이며, R2ntn (mod p)이므로 Rx2n (mod p)의 해가 된다.

 


Tonelli-Shanks 알고리즘의 시간복잡도 증명

위에서 기술한 알고리즘의 동작 과정 중 1., 2., 4.는 거듭제곱 연산과 대입 연산만으로 구성되므로 분할정복을 이용한 거듭제곱을 사용하면 O(logp)에 수행 가능함을 알 수 있다.

3.의 경우 가장 작은 quadratic non-residue x에 대해 O(x)의 시간에 수행되는데, Claim 2에서 증명하였듯 어떤 residue가 quadratic non-residue일 확률은 50%이므로 평균 2회의 반복으로 quadratic non-residue를 찾을 수 있다. 최악의 경우에도 p 이하의 quadratic non-residue가 존재함이 보장된다. 본 글에서 다루기에는 다소 글이 난잡해지므로 생략한다. (증명)

5.에서 i를 찾는 과정은 O(M)의 시간을 소요하며 M=O(logp)이므로 이 과정은 O(logp)의 시간 내에 종결된다. 이외의 과정 역시 O(logp)에 종료됨을 간단히 확인할 수 있으며, 이러한 과정이 최대 O(logp)번 반복되므로 O(log2p)의 시간에 종결됨을 알 수 있다.

따라서 3.의 소요 시간을 평균적인 상수 시간으로 가정한다면 O(logp+1+log2p)=O(log2p)의 시간에 Tonelli-Shanks 알고리즘이 종료됨을 알 수 있다.


Tonelli-Shanks 알고리즘의 구현

CTF 암호학에서 자주 사용되는 언어는 SageMath, Python이다. SageMath에서는 sage.rings.finite_rings.integer_mod의 square_root_mod_prime을 통해 간단하게 Tonelli-Shanks 알고리즘을 사용할 수 있으며, square_root_mod_prime_power와 같은 확장 또한 사용 가능하다. 따라서 개인적으로 SageMath를 이용하길 권장하나, 그렇지 못하는 경우를 위해 Python에서 Tonelli-Shanks를 구현하는 코드를 간단하게 소개한다.

def tonelli_shanks(n,p):
    def isQR(x,p):# check whether x is quadratic residue
        return pow(x,(p-1)//2,p)==1
    # (1.)
    if not isQR(n,p):
        return -1

    # (2.)
    Q,S=p-1,0
    while Q%2==0:
        S+=1
        Q//=2

    # (3.)
    z=None
    for x in range(2,p):
        if not isQR(x,p):
            z=x
            break
    # (4.)
    M,c,t,R=S,pow(z,Q,p),pow(n,Q,p),pow(n,(Q+1)//2,p)

    # (5.)
    while True:
        if t==0:
            return 0
        elif t==1:
            return R
        k=t*t%p
        ii=None
        for i in range(1,M):
            if k==1:
                ii=i
                break
            k*=k
            k%=p
        b=pow(c,2**(M-i-1),p)%p
        M=ii%p
        c=b*b%p
        t=t*c%p
        R=R*b%p

# test
if __name__=='__main__':
    n,p=map(int,input().split())
    print("STARTED")
    print(tonelli_shanks(n,p))

+ Recent posts