Now works on Py2.7
This commit is contained in:
parent
94ed33bb15
commit
a3c300ee3e
44
crypt.py
44
crypt.py
@ -10,28 +10,15 @@ from __future__ import print_function
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
# AES 256 IGE part
|
||||
|
||||
class IGE:
|
||||
def __init__(self, key, iv):
|
||||
if len(key) != 32:
|
||||
raise ValueError("key must be 32 bytes long (was " +
|
||||
str(len(key)) + " bytes)")
|
||||
if len(iv) != 32:
|
||||
raise ValueError("iv must be 32 bytes long (was " +
|
||||
str(len(iv)) + " bytes)")
|
||||
def ige_encrypt(message, key, iv):
|
||||
return _ige(message, key, iv, operation="encrypt")
|
||||
|
||||
self.key = key
|
||||
self.iv = iv
|
||||
def ige_decrypt(message, key, iv):
|
||||
return _ige(message, key, iv, operation="decrypt")
|
||||
|
||||
self.cipher = AES.new(key, AES.MODE_ECB, iv)
|
||||
|
||||
def encrypt(self, message):
|
||||
return self._ige(message, operation="encrypt")
|
||||
|
||||
def decrypt(self, message):
|
||||
return self._ige(message, operation="decrypt")
|
||||
|
||||
def _ige(self, message, operation="decrypt"):
|
||||
def _ige(message, key, iv, operation="decrypt"):
|
||||
"""Given a key, given an iv, and message
|
||||
do whatever operation asked in the operation field.
|
||||
Operation will be checked for: "decrypt" and "encrypt" strings.
|
||||
@ -40,14 +27,23 @@ class IGE:
|
||||
key must be 32 byte
|
||||
iv must be 32 byte (it's not internally used in AES 256 ECB, but it's
|
||||
needed for IGE)"""
|
||||
blocksize = self.cipher.block_size
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("key must be 32 bytes long (was " +
|
||||
str(len(key)) + " bytes)")
|
||||
if len(iv) != 32:
|
||||
raise ValueError("iv must be 32 bytes long (was " +
|
||||
str(len(iv)) + " bytes)")
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB, iv)
|
||||
blocksize = cipher.block_size
|
||||
|
||||
if len(message) % blocksize != 0:
|
||||
raise ValueError("message must be a multiple of 16 bytes (try adding " +
|
||||
str(16 - len(message) % 16) + " bytes of padding)")
|
||||
|
||||
ivp = self.iv[0:blocksize]
|
||||
ivp2 = self.iv[blocksize:]
|
||||
ivp = iv[0:blocksize]
|
||||
ivp2 = iv[blocksize:]
|
||||
|
||||
ciphered = bytearray()
|
||||
|
||||
@ -55,13 +51,13 @@ class IGE:
|
||||
indata = message[i:i+blocksize]
|
||||
if operation == "decrypt":
|
||||
xored = strxor(indata, ivp2)
|
||||
decrypt_xored = self.cipher.decrypt(xored)
|
||||
decrypt_xored = cipher.decrypt(xored)
|
||||
outdata = strxor(decrypt_xored, ivp)
|
||||
ivp = indata
|
||||
ivp2 = outdata
|
||||
elif operation == "encrypt":
|
||||
xored = strxor(indata, ivp)
|
||||
encrypt_xored = self.cipher.encrypt(xored)
|
||||
encrypt_xored = cipher.encrypt(xored)
|
||||
outdata = strxor(encrypt_xored, ivp2)
|
||||
ivp = outdata
|
||||
ivp2 = indata
|
||||
|
72
mtproto.py
72
mtproto.py
@ -19,6 +19,7 @@ import struct
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
|
||||
# local modules
|
||||
import crypt
|
||||
@ -36,15 +37,13 @@ def vis(bs):
|
||||
bs = bytearray(bs)
|
||||
symbols_in_one_line = 8
|
||||
n = len(bs) // symbols_in_one_line
|
||||
i = 0
|
||||
for i in range(n):
|
||||
print(str(i*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[i*symbols_in_one_line:(i+1)*symbols_in_one_line]])) # for every 8 symbols line
|
||||
if not len(bs) % symbols_in_one_line == 0:
|
||||
print(str((i+1)*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[(i+1)*symbols_in_one_line:]])+"\n") # for last line
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Session:
|
||||
""" Manages TCP Transport. encryption and message frames """
|
||||
def __init__(self, ip, port, auth_key=None):
|
||||
@ -73,8 +72,10 @@ class Session:
|
||||
# Message id: https://core.telegram.org/mtproto/description#message-identifier-msg-id
|
||||
# http://stackoverflow.com/questions/8777753/converting-datetime-date-to-utc-timestamp-in-python
|
||||
# to make it work in py2 and py3 (py3 has the timestamp() method but py2 doesnt)
|
||||
curr_timestamp = (datetime.utcfromtimestamp(time()) - datetime(1970, 1, 1)).total_seconds()
|
||||
msg_id = int(curr_timestamp*2**30)*4
|
||||
#curr_timestamp = (datetime.utcfromtimestamp(time()) - datetime(1970, 1, 1)).total_seconds()
|
||||
|
||||
msg_id = int(time()*2**30)*4
|
||||
|
||||
#msg_id = int(datetime.utcnow().timestamp()*2**30)*4
|
||||
|
||||
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||
@ -141,7 +142,7 @@ class Session:
|
||||
def create_auth_key(self):
|
||||
|
||||
nonce = os.urandom(16)
|
||||
print("Requesting PQ")
|
||||
print("Requesting pq")
|
||||
|
||||
ResPQ = self.method_call('req_pq', nonce=nonce)
|
||||
server_nonce = ResPQ['server_nonce']
|
||||
@ -149,22 +150,18 @@ class Session:
|
||||
# TODO: selecting RSA public key based on this fingerprint
|
||||
public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0]
|
||||
|
||||
PQ_bytes = ResPQ['pq']
|
||||
pq_bytes = ResPQ['pq']
|
||||
|
||||
# TODO: from_bytes here
|
||||
PQ = struct.unpack('>q', PQ_bytes)[0]
|
||||
[p, q] = prime.primefactors(PQ)
|
||||
pq = struct.unpack('>q', pq_bytes)[0]
|
||||
[p, q] = prime.primefactors(pq)
|
||||
if p > q: (p, q) = (q, p)
|
||||
assert p*q == PQ and p < q
|
||||
|
||||
print("Factorization %d = %d * %d" % (PQ, p, q))
|
||||
|
||||
# TODO: to_bytes here
|
||||
P_bytes = struct.pack('>i', p)
|
||||
Q_bytes = struct.pack('>i', q)
|
||||
assert p*q == pq and p < q
|
||||
|
||||
print("Factorization %d = %d * %d" % (pq, p, q))
|
||||
p_bytes = long_to_bytes(p)
|
||||
q_bytes = long_to_bytes(q)
|
||||
f = open(os.path.join(os.path.dirname(__file__), "rsa.pub"))
|
||||
|
||||
key = RSA.importKey(f.read())
|
||||
|
||||
z = io.BytesIO()
|
||||
@ -172,9 +169,9 @@ class Session:
|
||||
new_nonce = os.urandom(32)
|
||||
|
||||
TL.serialize_obj(z, 'p_q_inner_data',
|
||||
pq=PQ_bytes,
|
||||
p=P_bytes,
|
||||
q=Q_bytes,
|
||||
pq=pq_bytes,
|
||||
p=p_bytes,
|
||||
q=q_bytes,
|
||||
nonce=nonce,
|
||||
server_nonce=server_nonce,
|
||||
new_nonce=new_nonce)
|
||||
@ -189,8 +186,8 @@ class Session:
|
||||
server_DH_params = self.method_call('req_DH_params',
|
||||
nonce=nonce, # 16 bytes
|
||||
server_nonce=server_nonce,
|
||||
p=P_bytes,
|
||||
q=Q_bytes,
|
||||
p=p_bytes,
|
||||
q=q_bytes,
|
||||
public_key_fingerprint=public_key_fingerprint,
|
||||
encrypted_data=encrypted_data)
|
||||
assert nonce == server_DH_params['nonce']
|
||||
@ -201,9 +198,7 @@ class Session:
|
||||
tmp_aes_key = SHA.new(new_nonce + server_nonce).digest() + SHA.new(server_nonce + new_nonce).digest()[0:12]
|
||||
tmp_aes_iv = SHA.new(server_nonce + new_nonce).digest()[12:20] + SHA.new(new_nonce + new_nonce).digest() + new_nonce[0:4]
|
||||
|
||||
crypter = crypt.IGE(tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
answer_with_hash = crypter.decrypt(encrypted_answer)
|
||||
answer_with_hash = crypt.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
answer_hash = answer_with_hash[:20]
|
||||
answer = answer_with_hash[20:]
|
||||
@ -219,17 +214,15 @@ class Session:
|
||||
self.timedelta = server_time - time()
|
||||
print("Server-client time delta = %.1f s" % self.timedelta)
|
||||
|
||||
dh_prime = int.from_bytes(dh_prime_str,'big')
|
||||
g_a = int.from_bytes(g_a_str,'big')
|
||||
dh_prime = bytes_to_long(dh_prime_str)
|
||||
g_a = bytes_to_long(g_a_str)
|
||||
assert prime.isprime(dh_prime)
|
||||
retry_id = 0
|
||||
while retry_id<25:
|
||||
try:
|
||||
b_str = os.urandom(256)
|
||||
b = int.from_bytes(b_str,'big')
|
||||
b = bytes_to_long(b_str)
|
||||
g_b = pow(g, b, dh_prime)
|
||||
|
||||
g_b_str = int.to_bytes(g_b, g_b.bit_length() // 8 + 1, 'big')
|
||||
g_b_str = long_to_bytes(g_b)
|
||||
|
||||
z = io.BytesIO()
|
||||
TL.serialize_obj(z, 'client_DH_inner_data',
|
||||
@ -240,7 +233,7 @@ class Session:
|
||||
data = z.getvalue()
|
||||
data_with_sha = SHA.new(data).digest()+data
|
||||
data_with_sha_padded = data_with_sha + os.urandom(-len(data_with_sha) % 16)
|
||||
encrypted_data = crypter.encrypt(data_with_sha_padded)
|
||||
encrypted_data = crypt.ige_encrypt(data_with_sha_padded, tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
Set_client_DH_params_answer = self.method_call('set_client_DH_params',
|
||||
nonce=nonce,
|
||||
@ -248,7 +241,7 @@ class Session:
|
||||
encrypted_data=encrypted_data)
|
||||
|
||||
auth_key = pow(g_a, b, dh_prime)
|
||||
auth_key_str = int.to_bytes(auth_key, auth_key.bit_length() // 8 + 1, 'big')
|
||||
auth_key_str = long_to_bytes(auth_key)
|
||||
auth_key_sha = SHA.new(auth_key_str).digest()
|
||||
auth_key_hash = auth_key_sha[-8:]
|
||||
auth_key_aux_hash = auth_key_sha[:8]
|
||||
@ -265,8 +258,13 @@ class Session:
|
||||
self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8])
|
||||
self.auth_key = auth_key_str
|
||||
print("Auth key generated")
|
||||
break
|
||||
except:
|
||||
print("Retry DH key exchange")
|
||||
retry_id += 1
|
||||
|
||||
def aes_calculate(self, msg_key, direction="to server"):
|
||||
x = 0 if direction == "to server" else 8
|
||||
sha1_a = SHA.new(msg_key + self.auth_key[x:x+32]).digest()
|
||||
sha1_b = SHA.new(self.auth_key[x+32:x+48] + msg_key + self.auth_key[48+x:64+x]).digest()
|
||||
sha1_c = SHA.new(self.auth_key[x+64:x+96] + msg_key).digest()
|
||||
sha1_d = SHA.new(msg_key + self.auth_key[x+96:x+128]).digest()
|
||||
aes_key = sha1_a[0:8] + sha1_b[8:20] + sha1_c[4:16]
|
||||
aes_iv = sha1_a[8:20] + sha1_b[0:8] + sha1_c[16:20] + sha1_d[0:8]
|
||||
return aes_key, aes_iv
|
||||
|
Loading…
Reference in New Issue
Block a user