문제파일을 받아보니 아래와 같은 파이썬 코드가 있었는데 

from math import lcm
from Crypto.Util.number import*

with open('flag.txt', 'rb') as f:
    flag = bytes_to_long(f.read().strip())

oops = getPrime(20) #e
p1 = getPrime(512)
p2 = getPrime(512)

haha = (p1-1)*(p2-1) #phi
crazy_number = pow(oops, -1, haha) #d
discord_mod = p1 * p2 #n
hehe_secret = pow(flag, crazy_number, discord_mod) #c

print('admin =', discord_mod)
print('hehe_secret =', hehe_secret)
print('crazy number =', crazy_number)

주석처리를 하면 이렇게 된다 이때 oops는 e가 되는데 getprime(20)이면 e값이 20자리 소수로 너무 큰 값이된다 

그래서 이때 wienr attack 을 사용하게 되면 flag 값이 나온다 

 

'''
Created on Dec 22, 2011

@author: pablocelayes
'''

def egcd(a,b):
    '''
    Extended Euclidean Algorithm
    returns x, y, gcd(a,b) such that ax + by = gcd(a,b)
    '''
    u, u1 = 1, 0
    v, v1 = 0, 1
    while b:
        q = a // b
        u, u1 = u1, u - q * u1
        v, v1 = v1, v - q * v1
        a, b = b, a - q * b
    return u, v, a

def gcd(a,b):
    '''
    2.8 times faster than egcd(a,b)[2]
    '''
    a,b=(b,a) if a<b else (a,b)
    while b:
        a,b=b,a%b
    return a

def modInverse(e,n):
    '''
    d such that de = 1 (mod n)
    e must be coprime to n
    this is assumed to be true
    '''
    return egcd(e,n)[0]%n

def totient(p,q):
    '''
    Calculates the totient of pq
    '''
    return (p-1)*(q-1)

def bitlength(x):
    '''
    Calculates the bitlength of x
    '''
    assert x >= 0
    n = 0
    while x > 0:
        n = n+1
        x = x>>1
    return n


def isqrt(n):
    '''
    Calculates the integer square root
    for arbitrary large nonnegative integers
    '''
    if n < 0:
        raise ValueError('square root not defined for negative numbers')
    
    if n == 0:
        return 0
    a, b = divmod(bitlength(n), 2)
    x = 2**(a+b)
    while True:
        y = (x + n//x)//2
        if y >= x:
            return x
        x = y


def is_perfect_square(n):
    '''
    If n is a perfect square it returns sqrt(n),
    
    otherwise returns -1
    '''
    h = n & 0xF; #last hexadecimal "digit"
    
    if h > 9:
        return -1 # return immediately in 6 cases out of 16.

    # Take advantage of Boolean short-circuit evaluation
    if ( h != 2 and h != 3 and h != 5 and h != 6 and h != 7 and h != 8 ):
        # take square root if you must
        t = isqrt(n)
        if t*t == n:
            return t
        else:
            return -1
    
    return -1



'''
Created on Dec 14, 2011

@author: pablocelayes
    
'''
# Types
CFListT = list[int]  # CF coefficients
CVListT = list[tuple[int, int]]  # Convergents at each coefficient level

def rational_to_contfrac(x: int, y: int) -> tuple[CFListT, CVListT]:
    """
    Converts a rational x/y fraction into
    a list of partial coefficients [a0, ..., an], and
    a list of convergents at each coefficient level [(n0, d0), (n1, d1), ...]

    The algorithm of computing the convergents from left to right is available
    in Section 9.1 of https://r-knott.surrey.ac.uk/Fibonacci/cfINTRO.html#CFtofract

    Args:
        x (int): numerator of the given rational number
        y (int): denominator of the given rational number

    Returns:
        tuple[CFListT, CVListT]: a tuple of coefficients and convergents at each
        coefficient level
    """
    a = x // y
    cflist = [a]
    cvlist = [(a, 1)]
    ppn, ppd = 1, 0  # pre-pre numerator and denominator of convergent
    pn, pd = a, 1  # pre numerator and denominator of convergent
    while a * y != x:
        x, y = y, x - a * y
        a = x // y
        cflist.append(a)
        cn, cd = a * pn + ppn, a * pd + ppd
        cvlist.append((cn, cd))
        ppn, ppd = pn, pd
        pn, pd = cn, cd
    return cflist, cvlist





def hack_RSA(e,n):
    '''
    Finds d knowing (e,n)
    applying the Wiener continued fraction attack
    '''
    _, convergents = rational_to_contfrac(e, n)
    
    for (k,d) in convergents:
        
        #check if d is actually the key
        if k!=0 and (e*d-1)%k == 0:
            phi = (e*d-1)//k
            s = n - phi + 1
            # check if the equation x^2 - s*x + n = 0
            # has integer roots
            discr = s*s - 4*n
            if(discr>=0):
                t = is_perfect_square(discr)
                if t!=-1 and (s+t)%2==0:
                    print("Hacked!")
                    return d

########### 풀이 #########



from math import lcm
from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes

# with open('flag.txt', 'rb') as f:
#     flag = bytes_to_long(f.read().strip())

# oops = getPrime(20)
# p1 = getPrime(512)
# p2 = getPrime(512)

# phi = (p1-1)*(p2-1)
# crazy_number = pow(oops, -1, phi)
# n = p1 * p2

# hehe_secret = pow(flag, crazy_number, n)


# # pow = flag ** cn % n
# print('admin =', n)
# print('hehe_secret =', hehe_secret)
# print('crazy number =', crazy_number)


admin_n = 115527789319991047725489235818351464993028412126352156293595566838475726455437233607597045733180526729630017323042204168151655259688176759042620103271351321127634573342826484117943690874998234854277777879701926505719709998116539185109829000375668558097546635835117245793477957255328281531908482325475746699343
ct = 10313360406806945962061388121732889879091144213622952631652830033549291457030908324247366447011281314834409468891636010186191788524395655522444948812334378330639344393086914411546459948482739784715070573110933928620269265241132766601148217497662982624793148613258672770168115838494270549212058890534015048102
cn_e = 13961211722558497461053729553295150730315735881906397707707726108341912436868560366671282172656669633051752478713856363392549457910240506816698590171533093796488195641999706024628359906449130009380765013072711649857727561073714362762834741590645780746758372687127351218867865135874062716318840013648817769047

d = hack_RSA(cn_e, admin_n)

# using lcm 
print(f'{d = }')
flag = pow(ct, d, admin_n)
print(f'{flag = }')
print(f'{long_to_bytes(flag) = }')

https://github.com/pablocelayes/rsa-wiener-attack/blob/master/RSAwienerHacker.py

 

rsa-wiener-attack/RSAwienerHacker.py at master · pablocelayes/rsa-wiener-attack

A Python implementation of the Wiener attack on RSA public-key encryption scheme. - pablocelayes/rsa-wiener-attack

github.com

위 코드를 참고하여 flag 값을 도출했다 

tjctf{congrats_on_rsa_e_djfkel2349!}

CRT, 중국인 나머지 정리는 

  1.  값을 알아야 하고
  2.  중국인 나머지를 사용하여 계산하면  새로운 값 을 얻을 수 있음
  3. 세제곱근 계산의 세제곱근을 구하면, 원래의 메세지를 복구
  4. 바이트로 변환: 이 결과를 바이트로 변환하면 flag 값이 나오게 된다.

이때 중국인 나머지 정리는 

서로 소인 자연수 n1, n2, … , nk와 임의의 정수 a1, a2, … , ak가 있을 때, 임의의 i(1 ≤ i ≤ k)에 대해 
x ≡ ai (mod ni) 로 표현되는 변수 x의 연립 합동 방정식에 대해
이 방정식이 성립하는 값 x=a가 항상 존재하며
또한 그 값은 n1 n2 … nk의 나머지값 안에서 유일하게 존재한다. 
즉, 방정식의 해는 모두 x ≡ a (mod n1 n2 … nk)로 표현 가능하다.

 

https://flyingdcat4.tistory.com/entry/Chinese-Remainder-Theorem%EC%A4%91%EA%B5%AD%EC%9D%B8%EC%9D%98-%EB%82%98%EB%A8%B8%EC%A7%80-%EC%A0%95%EB%A6%AC 블로그 참고

 

그래서 코드로 보자면 아래와 같이 나온다

from Crypto.Util.number import *
from sympy.ntheory.modular import crt  # 중국 나머지 정리 사용
import gmpy2 

e = 3  # 공개키가 3인 경우 (RSA의 일반적인 값)


n1 =
c1 = 
n2 = 
c2 = 
n3 = 
c3 = 

# 중국 나머지 정리를 사용하여 결합된 암호화된 값과 모듈러를 계산
# crt는 주어진 리스트의 나머지와 모듈러를 사용하여, 새로운 모듈러와 암호화된 값을 반환.
M, N = crt([c1, c2, c3], [n1, n2, n3])  # M은 결합된 암호화된 값, N은 결합된 모듈러스

# 세제곱근을 사용하여 암호화된 값을 해독
# gmpy2.iroot()는 M의 세제곱근을 반환 이는 e=3일 때, RSA 암호화의 복호화 방법임
m = gmpy2.iroot(M, 3)[0]  # 세제곱근의 첫 번째 반환 값은 정수 부분

plaintext = long_to_bytes(int(m))  # 정수를 바이트로 변환

print(plaintext.decode())  # 출력되는 문자열이 암호화된 원래 메시지

정수 모듈로 n으로 이루어진 집합은 덧셈과 곱셈의 연산을 포함하여 링(Ring)이 됩니다.
이는 집합 내의 두 요소를 더하거나 곱하는 경우에도 집합 내의 다른 요소가 반환된다는 것을 의미합니다.
모듈러스가 소수일 때: n = p인 경우, 우리는 집합 내의 모든 요소에 대한 역원을 보장받으며, 이로 인해 링은 필드(Field)로 승격됩니다. 이 필드를 유한체 Fp라고 합니다.Diffie-Hellman 프로토콜은 일반적으로 큰 소수인 유한체 Fp의 요소들로 작동합니다.
소수 p = 991과 요소 g = 209가 주어진 경우, g * d mod 991 = 1을 만족하는 역원 d를 찾아보겠습니다.

 

역원으로 d를 구하면 되는 문제이다 역원은 RSA에서도 했듯이 pycrypto 안에있는 inverse함수로 구할 수 있다.

from Crypto.Util.number import inverse
print(inverse(209, 991))

g(209) inverse 991 을 하면 당연히 d 가 나온다 

569 가 flag

'Cryptography' 카테고리의 다른 글

RSA CRT Attack 중국인 나머지 정리  (2) 2024.05.08
[python] .pem 파일에서 e,n 얻기  (0) 2024.03.27
RSA 낮은 지수 공격 cyling Attack code  (0) 2024.03.03
BraekerCTF Write up - Thus spoke machine  (0) 2024.02.29
RSA 역원  (0) 2024.02.17

wiener 공격과는 반대로 e값이 매우 작을때 사용된다 이때 e 값은 주로 3으로 설정된다 

 

from gmpy2 import iroot, local_context

c = 

with local_context() as ctx:
    ctx.precision = 3000
    m = iroot(c,3)[0]
   
    print(bytes.fromhex('%x' % int(m)))

'Cryptography' 카테고리의 다른 글

[python] .pem 파일에서 e,n 얻기  (0) 2024.03.27
[Cryptohack] Diffie-Hellman Starter 1 write up  (0) 2024.03.04
BraekerCTF Write up - Thus spoke machine  (0) 2024.02.29
RSA 역원  (0) 2024.02.17
RSA Wiener attack code  (0) 2024.02.17

wiener attack 이란 단순히 말해 e 값이 클때 사용할수 있는 공격이다.

대게 확률적으로 e값이 증폭적으로 크다면 d값이 작을 확률이 매우 높기때문에 

wiener 공격을 하면 d를 알아낼수있어 RSA 취약점인 개인키가 공개되기 때문에 이를 이용하여 flag를 알아낼수있다

 

RSA에서 텍스트파일로 저장된 Public key.pem 파일을 읽어와서 키 파일을 얻어오는 코드와 

wiener 공격을 한 파일에 담아 정리했다

https://github.com/pablocelayes/rsa-wiener-attack/blob/master/RSAwienerHacker.py

E,n값과 public key 텍스트 파일을 입력해 긁어오는 방식

키를 직접 추가 하고 싶다면 키를 가지고 오는 코드를 삭제후 본인이 입력하면 제대로 동작한다.

from Crypto.Util.number import*
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
from base64 import b64encode

# Get public key from a file
pubkey_file = open("public_key.pem", 'r')
pubkey = pubkey_file.read()
pubkey_file.close()

flag_file = open("flag.txt", 'r')
flag = int(flag_file.read(), 16)
flag_file.close()

msg = "plain text"           

# Create key using public key
keyPub = RSA.importKey(pubkey)
cipher = Cipher_PKCS1_v1_5.new(keyPub)
cipher_text = cipher.encrypt(msg.encode())
emsg = b64encode(cipher_text)
encryptedText = emsg.decode('utf-8')

n = keyPub.n
e = keyPub.e

def convergents_from_contfrac(frac):
    convs = [];
    for i in range(len(frac)):
        convs.append(contfrac_to_rational(frac[0:i]))
    return convs

def contfrac_to_rational (frac):
    if len(frac) == 0:
        return (0,1)
    num = frac[-1]
    denom = 1
    for _ in range(-2,-len(frac)-1,-1):
        num, denom = frac[_]*num+denom, num
    return (num,denom)

def is_perfect_square(n):
    h = n & 0xF; 
    
    if h > 9:
        return -1 

    
    if ( h != 2 and h != 3 and h != 5 and h != 6 and h != 7 and h != 8 ):
        # take square root if you must
        t = isqrt(n)
        if t*t == n:
            return t
        else:
            return -1
    
    return -1

def test_is_perfect_square():
    print("Testing is_perfect_square")
    testsuit = [4, 0, 15, 25, 18, 901, 1000, 1024]
    
    for n in testsuit:
        print("Is ", n, " a perfect square?")
        if is_perfect_square(n)!= -1:
            print("Yes!")
        else:
            print("Nope")
            
def isqrt(n):
    if n < 0:
        raise ValueError('square root not defined for negative numbers')
    
    if n == 0:
        return 0
    a, b = divmod(bitlength(n), 2)
    x = 2**(a+b)
    while True:
        y = (x + n//x)//2
        if y >= x:
            return x
        x = y

def bitlength(x):
    assert x >= 0
    n = 0
    while x > 0:
        n = n+1
        x = x>>1
    return n

def hack_RSA(e,n):
    
    # rational_to_contfrac ref
    x, y = e, n
    
    a = x//y
    pquotients = [a]
    while a * y != x:
        x,y = y,x-a*y
        a = x//y
        pquotients.append(a)
    frac = pquotients
    # rational_to_contfrac ref end


    convergents = convergents_from_contfrac(frac)
    
    for (k,d) in convergents:
        
        #check if d is actually the key
        if k!=0 and (e*d-1)%k == 0:
            phi = (e*d-1)//k
            s = n - phi + 1
            discr = s*s - 4*n
            if(discr>=0):
                t = is_perfect_square(discr)
                if t!=-1 and (s+t)%2==0:
                    print("Hacked!")
                    return d
 
d = hack_RSA(e,n) 
pt = pow(flag,d,n)
print(long_to_bytes(pt))

 

 

'Cryptography' 카테고리의 다른 글

BraekerCTF Write up - Thus spoke machine  (0) 2024.02.29
RSA 역원  (0) 2024.02.17
선택 암호문 공격(Chosen Ciphertext Attack) - testbook rsa  (0) 2024.02.14
RSA 수학적 원리 발표 / 세특  (1) 2024.01.03
Factordb 사용법  (0) 2024.01.03

 

testbook rsa 문제파일을 받아 열어보니 

#!/usr/bin/python3
from Crypto.Util.number import getStrongPrime, bytes_to_long, inverse


class RSA(object):
    def __init__(self):
        self.p = getStrongPrime(512)
        self.q = getStrongPrime(512)
        self.N = self.p * self.q
        self.e = 0x10001
        self.d = inverse(self.e, self.N - self.p - self.q + 1)

    def encrypt(self, pt):
        return pow(pt, self.e, self.N)

    def decrypt(self, ct):
        return pow(ct, self.d, self.N)


rsa = RSA()
FLAG = bytes_to_long(open("flag", "rb").read())
FLAG_enc = rsa.encrypt(FLAG)

print("Welcome to dream's RSA server")

while True:
    print("[1] Encrypt")
    print("[2] Decrypt")
    print("[3] Get info")

    choice = input()

    if choice == "1":
        print("Input plaintext (hex): ", end="")
        pt = bytes_to_long(bytes.fromhex(input()))
        print(rsa.encrypt(pt))

    elif choice == "2":
        print("Input ciphertext (hex): ", end="")      #2번을 누르고 플래그 값이나 암호문보다 값이 작은 N을 입력하면 치트를 쓰지말라고 한다 
        ct = bytes_to_long(bytes.fromhex(input()))
        if ct == FLAG_enc or ct > rsa.N:
            print("Do not cheat !")
        else:
            print(rsa.decrypt(ct))

    elif choice == "3":                       #3번을 누르면 공개키 (E,n)과 암호문을 준다.
        print(f"N: {rsa.N}")
        print(f"e: {rsa.e}")
        print(f"FLAG: {FLAG_enc}")

    else:
        print("Nope")

 

이런식으로 운영되는 방식이였다 



근데 공개키로 평문을 암호문으로 만들지 않나? 그럼 어떻게 공격하지..  싶었는데 찾아보니
선택 암호문 공격 (CCA) 를 알아야한다 



선택 암호문 공격은 임의로 선택된 암호문과 일치하는 평문으로부터 암호키를 알아내기 위해 시도하는 공격이다.
즉 원하는 암호문을 복호화해주는 경우 사용할 수 있는 공격 방법이다.
RSA에서 쓰이는 경우는 "곱셈에 대한 준동형사상(Homomorphism)의 성질"이라고 한다

곱셈에 대한 준동형사상은 다음 식으로 정리될 수 있으며, 이를 이용하면 다음과 같은 공격이 가능해진다.



암호화된 flag가 있고, 암/복호화 기능이 존재한다고 가정하면

1. 숫자 2를 암호화 한다.

2. 숫자 2를 암호화 한 값 *  flag를 암호화 한 값을 곱한다.

3. 결과 값을 숫자 2로 나누면 flag가 된다.





덧붙여, 이 방법을 막기 위해서 고안된 방법은 RSA-OAEP를 제안하였다.

RSA-OAEP는 암호화 단계에서 평문 해시값과 정해진 개수의 '0' 등으로 만들어진 "인증 정보"를 평문의 앞에 추가하고 RSA로 암호화한다.

이후 복호화단계에서 이 "인증 정보"가 보이지 않을 경우 평문을 알고 있는 사람이 만든 암호문이 아니라고 판단하여 복호화를 진행하지 않는다.

실제로 RSA-OAEP에서는 난수를 이용하는 등 암호문이 매번 다른 패턴이 되도록 하여 안전성을 높였다





즉, 이 문제에서는 

2^e * flag_enc 를 [2] Decrypt에 넣어 1/2 해주면 된다.



내가 참고한 blog에서는 pwn 툴을 사용했는데 처음 써봐서 베끼는 수준 이였지만..

이 문제를 통해 pwn툴의 이해와 CCA의 개념을 이해했다

 

from pwn import *
from Crypto.Util.number import *

p = remote('host3.dreamhack.games', 21275) # 서버랑 연결 

p.sendlineafter('info\n', '3') #기다렸다가, 3번째 메뉴 선택

p.recvuntil('N: ')             # N값 받아오기(개행문자 제거해 정수로 변환한 값을 n에 저장)
n = int(p.recvline()[:-1])

p.recvuntil('e: ')            # e값 받아오기 (개행문자 제거해 정수로 변환한 값을 e에 저장)
e = int(p.recvline()[:-1])

p.recvuntil('FLAG: ')        # FLAG값 받아오기(개행문자 제거해 정수로 변환한 값을 flag_enc에 저장)
flag_enc = int(p.recvline()[:-1])

exploit_flag = (pow(2,e) * flag_enc) % n  # exploit_flag에 2^e * flag_enc % n 값을 저장

p.sendlineafter('info\n', '2')                  # 2번째 메뉴 선택
p.sendlineafter('hex): ', hex(exploit_flag)[2:])# exploit_flag값을 16진수로 변환해 전송 16진수 변환시 0x가 붙으므로 [2:]로 0x를 제거

verflag = int(p.recvline()[:-1])  # verflag에 받아온 값을 정수로 변환해 저장
flag  = verflag // 2      # verflag값을 2로 나눈 몫을 flag에 저장
log.info("REAL FLAG: " + str(long_to_bytes(flag))) # flag값을 바이트로 변환해 출력

 

 

참고 / 인용 블로그 출처

https://m.blog.naver.com/PostView.naver?blogId=apple8718&logNo=222256582723&categoryNo=0&proxyReferer=

 

DreamHack - Textbook-RSA

문제에 주어진 코드는 다음과 같다. 임의로 p와 q를 생성한 후, 공개키 값으로 65537을 사용하여 flag 값을...

blog.naver.com

https://dokhakdubini.tistory.com/289

'Cryptography' 카테고리의 다른 글

RSA 역원  (0) 2024.02.17
RSA Wiener attack code  (0) 2024.02.17
RSA 수학적 원리 발표 / 세특  (1) 2024.01.03
Factordb 사용법  (0) 2024.01.03
[Python] Crypto 모듈 다운로드 명령어  (0) 2024.01.02

+ Recent posts