2019. 4. 16. 14:15, CTF/Crypto
#!/usr/bin/env python3
import argparse
import secrets
import random
import hashlib
import sys
import pickle
from copy import deepcopy
class Key:
PRIVATE_INFO = ['P', 'Q', 'D', 'DmP1', 'DmQ1']
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
assert self.bits % 8 == 0
def ispub(self):
return all(not hasattr(self, key) for key in self.PRIVATE_INFO)
def ispriv(self):
return all(hasattr(self, key) for key in self.PRIVATE_INFO)
def pub(self):
p = deepcopy(self)
for key in self.PRIVATE_INFO:
if hasattr(p, key):
delattr(p, key)
return p
def priv(self):
raise NotImplementedError()
def genprime(bits):
while True:
p = secrets.randbits(bits-1) | (1 << (bits-1))
if isprime(p): return p
def isprime(n):
return n > 1 and miller_rabin(n)
def miller_rabin(n, rounds=50):
assert n > 1
if not (n & 1): return n == 2
if n < 15: return n != 9
d, r = n-1, 0
while not (d & 1):
d >>= 1
r += 1
assert (d & 1) == 1 and d << r == n-1
for _ in range(rounds):
x = pow(random.randrange(2, n-1), d, n)
if x == 1 or x == n-1: continue
for i in range(1, r):
x = x * x % n
if x == n-1: break
else:
return False
return True
def egcd(a1, a2):
x1, x2 = 1, 0
y1, y2 = 0, 1
while a2:
q = a1 // a2
a1, a2 = a2, a1 - q * a2
x1, x2 = x2, x1 - q * x2
y1, y2 = y2, y1 - q * y2
return (x1, y1, a1)
def genkey(bits):
assert bits % 2 == 0
while True:
p = genprime(bits // 2)
q = genprime(bits // 2)
e = 65537
d, _, g = egcd(e, (p-1) * (q-1))
if g != 1: continue
iQmP, iPmQ, _ = egcd(q, p)
return Key(
N=p*q, P=p, Q=q, E=e, D=d%((p-1)*(q-1)), DmP1=d%(p-1), DmQ1=d%(q-1),
iQmP=iQmP%p, iPmQ=iPmQ%q, bits=bits,
)
def bytes2num(data):
return sum(x << (8 * i) for i, x in enumerate(data))
def num2bytes(data, size):
assert 0 <= data and (data >> (size * 8)) == 0
return bytes(data >> (8 * i) & 0xff for i in range(size))
def xor(d1, d2):
return bytes(
d1[i % len(d1)] ^ d2[i % len(d2)]
for i in range(max(len(d1), len(d2)))
)
def random_oracle(source, length, hash=hashlib.sha256):
return b''.join(
hash(source + num2bytes(idx, 4)).digest()
for idx in range((length - 1) // hash().digest_size + 1)
)[:length]
def pad(data, bits, hash=hashlib.sha256):
k = hash().digest_size
Xlen = (bits - 1) // 8 - k
assert len(data) + k <= Xlen
X = data.ljust(Xlen, b'\x00')
Y = num2bytes(secrets.randbits(k * 8), k)
X = xor(X, random_oracle(Y, Xlen, hash))
Y = xor(Y, random_oracle(X, k, hash))
return X + Y + b'\x00'
def unpad(data, hash=hashlib.sha256):
if data[-1] != 0: return None
data = data[:-1]
k = hash().digest_size
Xlen = len(data) - k
X, Y = data[:Xlen], data[-k:]
Y = xor(Y, random_oracle(X, k, hash))
X = xor(X, random_oracle(Y, Xlen, hash))
if all(b == 0 for b in X[-k:]):
return X[:-k]
return None
def encrypt(key, data):
data = bytes2num(pad(data, key.bits))
assert 0 <= data and data < key.N
data = pow(data, key.E, key.N)
return num2bytes(data, key.bits // 8)
def decrypt(key, data):
assert key.ispriv() and len(data) * 8 == key.bits
data = bytes2num(data)
assert 0 <= data and data < key.N
v1 = pow(data, key.DmP1, key.P)
v2 = pow(data, key.DmQ1, key.Q)
data = (v2 * key.P * key.iPmQ + v1 * key.Q * key.iQmP) % key.N
return unpad(num2bytes(data, key.bits // 8))
def action_decrypt(args):
data = args.i.read()
data = decrypt(args.k, data)
if data is None:
print('Failed to decrypt', file=sys.stderr)
exit(1)
args.o.write(data)
def action_encrypt(args):
data = args.i.read()
data = encrypt(args.k, data)
args.o.write(data)
def action_keygen(args):
key = genkey(args.bits)
pickle.dump(key, args.priv)
pickle.dump(key.pub(), args.pub)
def keysize(string):
try:
value = int(string)
except ValueError:
raise argparse.ArgumentTypeError(f'{string:r} is not a number')
if value % 8:
raise argparse.ArgumentTypeError(f'{value} must be a multiple of 8')
if 256 <= value and value < 8192:
return value
raise argparse.ArgumentTypeError(f'{value} out of range')
def keyfile(private=False):
def func(string):
f = argparse.FileType('rb')(string)
key = pickle.load(f)
if not isinstance(key, Key):
raise argparse.ArgumentTypeError(f'{string:r} did not specify a valid key')
if private and not key.ispriv():
raise argparse.ArgumentTypeError(f'{string:r} did not specify a private key')
return key
return func
def main():
main = argparse.ArgumentParser(description='The new age of RSA is upon us.')
main.set_defaults(func=None)
subs = main.add_subparsers(help='action to perform')
p = subs.add_parser('keygen', help='generate a new RSA key')
p.set_defaults(func=action_keygen)
p.add_argument('--bits', type=keysize, help='number of bits in the generated key', default=4096)
p.add_argument('--priv', type=argparse.FileType('wb'), metavar='KEY', help='where to store the private key', required=True)
p.add_argument('--pub', type=argparse.FileType('wb'), metavar='KEY', help='where to store the public key', required=True)
p = subs.add_parser('encrypt', help='encrypt your secrets')
p.set_defaults(func=action_encrypt)
p.add_argument('-i', type=argparse.FileType('rb'), metavar='FILE', default=sys.stdin.buffer, help='file to encrypt')
p.add_argument('-o', type=argparse.FileType('wb'), metavar='FILE', default=sys.stdout.buffer, help='encrypted output')
p.add_argument('-k', type=keyfile(), metavar='KEY', help='key to use', required=True)
p = subs.add_parser('decrypt', help='decrypt your secrets')
p.set_defaults(func=action_decrypt)
p.add_argument('-i', type=argparse.FileType('rb'), metavar='FILE', default=sys.stdin.buffer, help='file to decrypt')
p.add_argument('-o', type=argparse.FileType('wb'), metavar='FILE', default=sys.stdout.buffer, help='decrypted output')
p.add_argument('-k', type=keyfile(private=True), metavar='KEY', help='key to use', required=True)
args = main.parse_args()
if not args.func:
main.print_help()
exit(1)
args.func(args)
if __name__ == '__main__': main()
It is RSA challenge which has additional information, that is $a, b$ which satisfies $ap+bq = 1 (mod N)$, $0 < a < q, 0 < b < p$.(However, obviously, $p, q$ is not provided.)
It is possible to recover $p, q$ using $a, b$. Since $1<ap+bq \leq 2N$, $ap+bq = N+1$.
Substitute $p = N/q$ into $ap+bq = N+1$.
Then equation becomes $aq^2-(N+1)q+bN = 0$. So $p, q$ can be recovered.
import argparse
#import secrets
import random
import hashlib
import sys
import pickle
def gcd(a, b):
while(b):
a,b = b, a % b
return a
def egcd(a1, a2):
x1, x2 = 1, 0
y1, y2 = 0, 1
while a2:
q = a1 // a2
a1, a2 = a2, a1 - q * a2
x1, x2 = x2, x1 - q * x2
y1, y2 = y2, y1 - q * y2
return (x1, y1, a1)
def inv(a, m):
x, y,g = egcd(a, m)
if g != 1:
raise Exception('No modular inverse')
return x%m
class Key:
PRIVATE_INFO = ['P', 'Q', 'D', 'DmP1', 'DmQ1']
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
assert self.bits % 8 == 0
def ispub(self):
return all(not hasattr(self, key) for key in self.PRIVATE_INFO)
def ispriv(self):
return all(hasattr(self, key) for key in self.PRIVATE_INFO)
def pub(self):
p = deepcopy(self)
for key in self.PRIVATE_INFO:
if hasattr(p, key):
delattr(p, key)
return p
def priv(self):
raise NotImplementedError()
def mysqrt(d):
st = 1
en = 10**1300
while st<=en:
mid = (st+en)//2
if mid*mid == d: return mid
if mid*mid < d: st=mid+1
else: en=mid-1
return -1
def bytes2num(data):
return sum(x << (8 * i) for i, x in enumerate(data))
def num2bytes(data, size):
assert 0 <= data and (data >> (size * 8)) == 0
return bytes(data >> (8 * i) & 0xff for i in range(size))
def xor(d1, d2):
return bytes(
d1[i % len(d1)] ^ d2[i % len(d2)]
for i in range(max(len(d1), len(d2)))
)
def random_oracle(source, length, hash=hashlib.sha256):
return b''.join(
hash(source + num2bytes(idx, 4)).digest()
for idx in range((length - 1) // hash().digest_size + 1)
)[:length]
def pad(data, bits, hash=hashlib.sha256):
k = hash().digest_size
Xlen = (bits - 1) // 8 - k
assert len(data) + k <= Xlen
X = data.ljust(Xlen, b'\x00')
Y = num2bytes(secrets.randbits(k * 8), k)
X = xor(X, random_oracle(Y, Xlen, hash))
Y = xor(Y, random_oracle(X, k, hash))
return X + Y + b'\x00'
def unpad(data, hash=hashlib.sha256):
if data[-1] != 0: return None
data = data[:-1]
k = hash().digest_size
Xlen = len(data) - k
X, Y = data[:Xlen], data[-k:]
Y = xor(Y, random_oracle(X, k, hash))
X = xor(X, random_oracle(Y, Xlen, hash))
if all(b == 0 for b in X[-k:]):
return X[:-k]
return None
f = argparse.FileType('rb')('key.sad.pub')
key = pickle.load(f)
n = key.N
e = key.E
# aq+bp = 1 (mod n)
# solve aq^2-(n+1)q+bn = 0
a = key.iQmP
b = key.iPmQ
v1,v2,_ = egcd(a,b)
val = mysqrt((n+1)**2-4*a*b*n)
q = (n+1+val)//(2*a)
p = n//q
d = inv(e,(p-1)*(q-1))
assert(n==p*q)
assert(d*e%((p-1)*(q-1))==1)
C = bytes2num(open('flag.enc','rb').read())
#print(C)
#print(hex(C))
P = pow(C,d,n)
#print(hex(P))
print(unpad(num2bytes(P,512)))
'CTF > Crypto' 카테고리의 다른 글
[TWCTF 2019] M-Poly-Cipher (0) | 2019.09.02 |
---|---|
[TWCTF 2019] Simple Logic (0) | 2019.09.02 |
[TWCTF 2019] real-baby-rsa (0) | 2019.09.02 |
[0CTF/TCTF 2019] babyrsa (0) | 2019.03.28 |
[0CTF/TCTF 2019] zer0lfsr (0) | 2019.03.28 |
[0CTF/TCTF 2019] zero0des (0) | 2019.03.28 |
Comments