| @ -0,0 +1,111 @@ | |||
| ''' | |||
| 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 | |||
| #TEST functions | |||
| 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") | |||
| if __name__ == "__main__": | |||
| test_is_perfect_square() | |||
| @ -0,0 +1,63 @@ | |||
| ''' | |||
| Created on Dec 14, 2011 | |||
| @author: pablocelayes | |||
| ''' | |||
| def rational_to_contfrac(x,y): | |||
| ''' | |||
| Converts a rational x/y fraction into | |||
| a list of partial quotients [a0, ..., an] | |||
| ''' | |||
| a = x//y | |||
| pquotients = [a] | |||
| while a * y != x: | |||
| x,y = y,x-a*y | |||
| a = x//y | |||
| pquotients.append(a) | |||
| return pquotients | |||
| #TODO: efficient method that calculates convergents on-the-go, without doing partial quotients first | |||
| def convergents_from_contfrac(frac): | |||
| ''' | |||
| computes the list of convergents | |||
| using the list of partial quotients | |||
| ''' | |||
| convs = []; | |||
| for i in range(len(frac)): | |||
| convs.append(contfrac_to_rational(frac[0:i])) | |||
| return convs | |||
| def contfrac_to_rational (frac): | |||
| '''Converts a finite continued fraction [a0, ..., an] | |||
| to an x/y rational. | |||
| ''' | |||
| 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 test1(): | |||
| ''' | |||
| Verify that the basic continued-fraction manipulation stuff works. | |||
| ''' | |||
| testnums = [(1, 1), (1, 2), (5, 15), (27, 73), (73, 27)] | |||
| for r in testnums: | |||
| (num, denom) = r | |||
| print('rational number:') | |||
| print(r) | |||
| contfrac = rational_to_contfrac (num, denom) | |||
| print('continued fraction:') | |||
| print(contfrac) | |||
| print('convergents:') | |||
| print(convergents_from_contfrac(contfrac)) | |||
| print('***********************************') | |||
| if __name__ == "__main__": | |||
| test1() | |||
| @ -0,0 +1,91 @@ | |||
| import random, sys | |||
| def miller_rabin_pass(a, s, d, n): | |||
| ''' | |||
| n is an odd number with | |||
| n-1 = (2^s)d, and d odd | |||
| and a is the base: 1 < a < n-1 | |||
| returns True iff n passes the MillerRabinTest for a | |||
| ''' | |||
| a_to_power = pow(a, d, n) | |||
| i=0 | |||
| #Invariant: a_to_power = a^(d*2^i) mod n | |||
| # we test whether (a^d) = 1 mod n | |||
| if a_to_power == 1: | |||
| return True | |||
| # we test whether a^(d*2^i) = n-1 mod n | |||
| # for 0<=i<=s-1 | |||
| while(i < s-1): | |||
| if a_to_power == n - 1: | |||
| return True | |||
| a_to_power = (a_to_power * a_to_power) % n | |||
| i+=1 | |||
| # we reach here if the test failed until i=s-2 | |||
| return a_to_power == n - 1 | |||
| def miller_rabin(n): | |||
| ''' | |||
| Applies the MillerRabin Test to n (odd) | |||
| returns True iff n passes the MillerRabinTest for | |||
| K random bases | |||
| ''' | |||
| #Compute s and d such that n-1 = (2^s)d, with d odd | |||
| d = n-1 | |||
| s = 0 | |||
| while d%2 == 0: | |||
| d >>= 1 | |||
| s+=1 | |||
| #Applies the test K times | |||
| #The probability of a false positive is less than (1/4)^K | |||
| K = 20 | |||
| i=1 | |||
| while(i<=K): | |||
| # 1 < a < n-1 | |||
| a = random.randrange(2,n-1) | |||
| if not miller_rabin_pass(a, s, d, n): | |||
| return False | |||
| i += 1 | |||
| return True | |||
| def gen_prime(nbits): | |||
| ''' | |||
| Generates a prime of b bits using the | |||
| miller_rabin_test | |||
| ''' | |||
| while True: | |||
| p = random.getrandbits(nbits) | |||
| #force p to have nbits and be odd | |||
| p |= 2**nbits | 1 | |||
| if miller_rabin(p): | |||
| return p | |||
| break | |||
| def gen_prime_range(start, stop): | |||
| ''' | |||
| Generates a prime within the given range | |||
| using the miller_rabin_test | |||
| ''' | |||
| while True: | |||
| p = random.randrange(start,stop-1) | |||
| p |= 1 | |||
| if miller_rabin(p): | |||
| return p | |||
| break | |||
| if __name__ == "__main__": | |||
| if sys.argv[1] == "test": | |||
| n = sys.argv[2] | |||
| print (miller_rabin(n) and "PRIME" or "COMPOSITE") | |||
| elif sys.argv[1] == "genprime": | |||
| nbits = int(sys.argv[2]) | |||
| print(gen_prime(nbits)) | |||
| @ -0,0 +1,149 @@ | |||
| #!/usr/bin/env python | |||
| import socket | |||
| from thread import start_new_thread | |||
| from datetime import datetime | |||
| from os import urandom | |||
| from random import randrange as random | |||
| import RSAvulnerableKeyGenerator as keygen | |||
| FLAG = 'SlashRootCTF{W13nn3r_w1nn3r_RSA_d1nn3r}' | |||
| HOST = '0.0.0.0' | |||
| PORT = 6070 | |||
| BUFF = 1024 | |||
| BIT = 256 | |||
| MAX = 10 | |||
| def banner(): | |||
| return '''\ | |||
| _______________________________________________ | |||
| | ____ ____ ____ ___ ____ _ _ ____ _ _ | | |||
| | |__/ [__ |__| | | | |_/ |___ |\ | | | |||
| | | \ ___] | | | |__| | \_ |___ | \| | | |||
| | ____ ____ _ _ ____ ____ ____ ___ ____ ____ | | |||
| | | __ |___ |\ | |___ |__/ |__| | | | |__/ | | |||
| | |__] |___ | \| |___ | \ | | | |__| | \ | | |||
| |_____________________________________________| | |||
| | [1] Generate RSA | | |||
| | [2] Generate Token | | |||
| | [3] Generate Flag | | |||
| |_____________________________________________| | |||
| >>> ''' | |||
| def log(message, address, filename='RSATG.log'): | |||
| with open(filename, 'a') as log: | |||
| timestamp = datetime.now().strftime('%d/%m/%Y %H:%M:%S') | |||
| log.write('[%s][%s:%d] %s\n' % (timestamp, address[0], address[1], str(message))) | |||
| def serve_client(client, address, receive=''): | |||
| try: | |||
| client.send(banner()) | |||
| token = [] | |||
| while True: | |||
| data = '' | |||
| receive = client.recv(BUFF).strip() | |||
| if receive == '1': | |||
| log('<<< Generating RSA', address) | |||
| data = genRSA(client, address) | |||
| elif receive == '2': | |||
| log('<<< Generating Token', address) | |||
| data, token = genToken(client, address) | |||
| log('>>> Sending Token: %s' % format(token), address) | |||
| elif receive == '3': | |||
| log('<<< Generating flag', address) | |||
| if token: | |||
| client.send('Token : ') | |||
| if format(token) == client.recv(BUFF).strip(): | |||
| ID = urandom(16).encode('hex') | |||
| log('<-> RSA ID: %s[%s]' % (ID, format(token)), address) | |||
| log('>>> Sending Flag: %s[%s]' % (ID, format(token)), address) | |||
| client.send('''\ | |||
| RSA ID\t : %s | |||
| FLAG\t : %s | |||
| *Sertakan TOKEN dan RSA ID pada writeup agar poin dihitung!\n''' % (ID, FLAG)) | |||
| break | |||
| else: | |||
| log('>-< Wrong Token: %s|%s' % (receive, format(token)), address) | |||
| data = 'Try Again!\nYour token is %s\n' % format(token) | |||
| else: | |||
| log('>-< Empty Token!', address) | |||
| data = 'Generate your token!\n' | |||
| token = [] | |||
| client.send(data + '[1|2|3]>>> ') | |||
| log('Disconnected', address) | |||
| except Exception as message: | |||
| log(message, address, 'error.log') | |||
| log('>-< Disconnected because error: %s' % message, address) | |||
| finally: | |||
| client.close() | |||
| log('--- Disconnected', address) | |||
| def format(token): | |||
| return '-'.join(token) | |||
| def RSA(bit): | |||
| e, n, d = keygen.generateKeys(bit) | |||
| p = random(1000, 9999) | |||
| c = pow(p, e, n) | |||
| return e, n, d, p, c | |||
| def genRSA(client, address): | |||
| e, n ,d, p, c = RSA(BIT) | |||
| client.send('e = %s \nn = %s \nc = %s \np = ' % (e, n, c)) | |||
| r = client.recv(BUFF).strip() | |||
| if r == str(p): | |||
| log('<-> Correct %s{e:%s,n:%s,d:%s,p:%s,c:%s)' % (r, e, n, d, p, c), address) | |||
| return '\n\m/ Correct \m/\n' | |||
| else: | |||
| log('>-< Wrong %s{e:%s,n:%s,d:%s,p:%s,c:%s)' % (r, e, n, d, p, c), address) | |||
| return '\nWrong :P is %s\n' % p | |||
| def genToken(client, address): | |||
| token = [] | |||
| data = 'Token has been generated!\n' | |||
| for i in range(1, 6): | |||
| e, n ,d, p, c = RSA(BIT) | |||
| client.send('Token #%i\ne = %s \nn = %s \nc = %s \np = ' % (i, e, n, c)) | |||
| r = client.recv(BUFF).strip() | |||
| if r == str(p): | |||
| log('<-> Correct #%d: %s{e:%s,n:%s,d:%s,p:%s,c:%s)' % (i, r, e, n, d, p, c), address) | |||
| token.append(str(p)) | |||
| else: | |||
| token = [] | |||
| data = '\nWrong :P is %s\n' % p | |||
| log('>-< Wrong #%d: %s{e:%s,n:%s,d:%s,p:%s,c:%s)' % (i, r, e, n, d, p, c), address) | |||
| break | |||
| return data, token | |||
| def main(): | |||
| server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||
| server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |||
| server.bind((HOST, PORT)) | |||
| server.listen(MAX) | |||
| log('(+) Power On Server', [HOST, PORT]) | |||
| while True: | |||
| try: | |||
| client, address = server.accept() | |||
| log('<<< Client connected from IP %s with PORT %d' % (address), address) | |||
| start_new_thread(serve_client, (client, address)) | |||
| except Exception as message: | |||
| log(message, [HOST, PORT], 'error.log') | |||
| except KeyboardInterrupt: | |||
| log('(-) Power Off Server', [HOST, PORT]) | |||
| break | |||
| server.close() | |||
| if __name__ == '__main__': | |||
| main() | |||
| @ -0,0 +1,94 @@ | |||
| ''' | |||
| Created on Dec 14, 2011 | |||
| @author: pablocelayes | |||
| ''' | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8 -*- | |||
| """\ | |||
| This module generates RSA-keys which are vulnerable to | |||
| the Wiener continued fraction attack | |||
| (see RSAfracCont.pdf) | |||
| The RSA keys are obtained as follows: | |||
| 1. Choose two prime numbers p and q | |||
| 2. Compute n=pq | |||
| 3. Compute phi(n)=(p-1)(q-1) | |||
| 4. Choose e coprime to phi(n) such that gcd(e,n)=1 | |||
| 5. Compute d = e^(-1) mod (phi(n)) | |||
| 6. e is the publickey; n is also made public (determines the block size); d is the privatekey | |||
| Encryption is as follows: | |||
| 1. Size of data to be encrypted must be less than n | |||
| 2. ciphertext=pow(plaintext,publickey,n) | |||
| Decryption is as follows: | |||
| 1. Size of data to be decrypted must be less than n | |||
| 2. plaintext=pow(ciphertext,privatekey,n) | |||
| ------------------------------- | |||
| RSA-keys are Wiener-vulnerable if d < (n^(1/4))/sqrt(6) | |||
| """ | |||
| import random, MillerRabin, Arithmetic | |||
| def getPrimePair(bits=512): | |||
| ''' | |||
| genera un par de primos p , q con | |||
| p de nbits y | |||
| p < q < 2p | |||
| ''' | |||
| assert bits%4==0 | |||
| p = MillerRabin.gen_prime(bits) | |||
| q = MillerRabin.gen_prime_range(p+1, 2*p) | |||
| return p,q | |||
| def generateKeys(nbits=1024): | |||
| ''' | |||
| Generates a key pair | |||
| public = (e,n) | |||
| private = d | |||
| such that | |||
| n is nbits long | |||
| (e,n) is vulnerable to the Wiener Continued Fraction Attack | |||
| ''' | |||
| # nbits >= 1024 is recommended | |||
| assert nbits%4==0 | |||
| p,q = getPrimePair(nbits//2) | |||
| n = p*q | |||
| phi = Arithmetic.totient(p, q) | |||
| # generate a d such that: | |||
| # (d,n) = 1 | |||
| # 36d^4 < n | |||
| good_d = False | |||
| while not good_d: | |||
| d = random.getrandbits(nbits//4) | |||
| if (Arithmetic.gcd(d,phi) == 1 and 36*pow(d,4) < n): | |||
| good_d = True | |||
| e = Arithmetic.modInverse(d,phi) | |||
| return e,n,d | |||
| if __name__ == "__main__": | |||
| print("hey") | |||
| for i in range(5): | |||
| e,n,d = generateKeys() | |||
| print ("Clave Publica:") | |||
| print("e =") | |||
| print(e) | |||
| print("n =") | |||
| print(n) | |||
| print ("Clave Privada:") | |||
| print("d =") | |||
| print(d) | |||
| print("-----------------------") | |||
| @ -0,0 +1,64 @@ | |||
| ''' | |||
| Created on Dec 14, 2011 | |||
| @author: pablocelayes | |||
| ''' | |||
| import ContinuedFractions, Arithmetic, RSAvulnerableKeyGenerator | |||
| def hack_RSA(e,n): | |||
| ''' | |||
| Finds d knowing (e,n) | |||
| applying the Wiener continued fraction attack | |||
| ''' | |||
| frac = ContinuedFractions.rational_to_contfrac(e, n) | |||
| convergents = ContinuedFractions.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 | |||
| # check if the equation x^2 - s*x + n = 0 | |||
| # has integer roots | |||
| discr = s*s - 4*n | |||
| if(discr>=0): | |||
| t = Arithmetic.is_perfect_square(discr) | |||
| if t!=-1 and (s+t)%2==0: | |||
| #print("Hacked!") | |||
| return d | |||
| # TEST functions | |||
| def test_hack_RSA(): | |||
| print("Testing Wiener Attack") | |||
| times = 5 | |||
| while(times>0): | |||
| e,n,d = RSAvulnerableKeyGenerator.generateKeys(1024) | |||
| print("(e,n) is (", e, ", ", n, ")") | |||
| print("d = ", d) | |||
| hacked_d = hack_RSA(e, n) | |||
| if d == hacked_d: | |||
| print("Hack WORKED!") | |||
| else: | |||
| print("Hack FAILED") | |||
| print("d = ", d, ", hacked_d = ", hacked_d) | |||
| print("-------------------------") | |||
| times -= 1 | |||
| if __name__ == "__main__": | |||
| #test_is_perfect_square() | |||
| #print("-------------------------") | |||
| test_hack_RSA() | |||