/
backdoor.py
111 lines (98 loc) · 3.98 KB
/
backdoor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env python
import sys
import gmpy
import curve25519
from struct import pack
from hashlib import sha256
from binascii import hexlify, unhexlify
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
MASTER_PUB_HEX = 'ce75b4ddd279d5f8009b454fa0f025861b65ebfbe2ada0823e9d5f6c3a15cf58'
# MASTER_PUB_HEX = '06F1A4EDF328C5E44AD32D5AA33FB7EF10B9A0FEE3AC1D3BA8E2FACD97643A43'
# A simple AES-CTR based CSPRNG
class AESPRNG(object):
def __init__(self, seed):
key = sha256(seed).digest()
self.buf = b''
self.buf_left = 0
self.counter = 0
self.cipher = AES.new(key, AES.MODE_ECB)
def randbytes(self, n):
ret = b''
requested = n
while requested > 0:
# Grab all unused bytes in the buffer and then refill it
if requested > self.buf_left:
ret += self.buf[(16-self.buf_left):]
requested -= self.buf_left
# Encrypt the big-endian counter value for
# the next block of pseudorandom bytes
self.buf = self.cipher.encrypt(pack('>QQ', 0, self.counter))
self.counter += 1
self.buf_left = 16
# Grab only the bytes we need from the buffer
else:
ret += self.buf[(16-self.buf_left):(16-self.buf_left+requested)]
self.buf_left -= requested
requested = 0
return ret
# overwrite some bytes in orig at a specificed offset
def replace_at(orig, replace, offset):
return orig[0:offset] + replace + orig[offset+len(replace):]
def build_key(bits=2048, e=65537, embed='', pos=1, randfunc=None):
# generate base key
rsa = RSA.generate(bits, randfunc)
# extract modulus as a string
n_str = unhexlify(str(hex(rsa.n))[2:])
# print(str(hex(rsa.n)), n_str)
# embed data into the modulus
n_hex = hexlify(replace_at(n_str, embed, pos))
n = gmpy.mpz(n_hex, 16)
p = rsa.p
# compute a starting point to look for a new q value
pre_q = n / p
# use the next prime as the new q value
q = gmpy.next_prime(gmpy.mpz(pre_q))
n = p * q
phi = (p-1) * (q-1)
# compute new private exponent
d = gmpy.invert(e, phi)
# make sure that p is smaller than q
if p > q:
(p, q) = (q, p)
return RSA.construct((int(n), int(e), int(d), int(p), int(q)))
def recover_seed(modulus='', pos=1):
# recreate the master private key from the passphrase
master = curve25519.Private(secret=bytes(unhexlify(MASTER_PUB_HEX)))
print('master', master.serialize())
# extract the ephemeral public key from modulus
mod_str = unhexlify(str(hex(modulus))[2:])
# print(hex(modulus), mod_str, mod_str[pos:pos+32], len(mod_str[pos:pos+32]))
ephem_pub = curve25519.Public(mod_str[pos:pos+32])
# compute seed with master private and ephemeral public
return (master.get_shared_key(ephem_pub), ephem_pub)
def recover_key(n='', bit_count=1024):
(seed, ephem_pub) = recover_seed(modulus=n, pos=80)
print(seed, ephem_pub)
prng = AESPRNG(seed)
ephem_pub = ephem_pub.serialize()
# deterministic key generation from seed
rsa = build_key(bits=bit_count, embed=ephem_pub, pos=80, randfunc=prng.randbytes)
print(rsa.p, rsa.q, rsa.e, rsa.d)
def backdoor_rsa(bit_count=1024):
# deserialize master ECDH public key embedded in program
master_pub = curve25519.Public(unhexlify(MASTER_PUB_HEX))
print('master_pub', master_pub.serialize())
# generate a random (yes, actually random) ECDH private key
ephem = curve25519.Private()
# derive the corresponding public key for later embedding
ephem_pub = ephem.get_public()
# print(ephem, ephem_pub)
# combine the ECDH keys to generate the seed
seed = ephem.get_shared_key(master_pub)
print(seed, ephem_pub)
prng = AESPRNG(seed)
ephem_pub = ephem_pub.serialize()
# deterministic key generation from seed
rsa = build_key(bits=bit_count, embed=ephem_pub, pos=80, randfunc=prng.randbytes)
return rsa