Now works on Py2.7

This commit is contained in:
Anton Grigoryev 2015-03-17 17:26:36 +03:00
parent 94ed33bb15
commit a3c300ee3e
2 changed files with 119 additions and 125 deletions

View File

@ -10,62 +10,58 @@ from __future__ import print_function
from Crypto.Util.strxor import strxor from Crypto.Util.strxor import strxor
from Crypto.Cipher import AES from Crypto.Cipher import AES
# AES 256 IGE part
class IGE: def ige_encrypt(message, key, iv):
def __init__(self, key, iv): return _ige(message, key, iv, operation="encrypt")
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)")
self.key = key def ige_decrypt(message, key, iv):
self.iv = iv return _ige(message, key, iv, operation="decrypt")
self.cipher = AES.new(key, AES.MODE_ECB, iv) 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.
Returns the message encrypted/decrypted.
message must be a multiple by 16 bytes (for division in 16 byte blocks)
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)"""
def encrypt(self, message): if len(key) != 32:
return self._ige(message, operation="encrypt") 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 decrypt(self, message): cipher = AES.new(key, AES.MODE_ECB, iv)
return self._ige(message, operation="decrypt") blocksize = cipher.block_size
def _ige(self, message, operation="decrypt"): if len(message) % blocksize != 0:
"""Given a key, given an iv, and message raise ValueError("message must be a multiple of 16 bytes (try adding " +
do whatever operation asked in the operation field. str(16 - len(message) % 16) + " bytes of padding)")
Operation will be checked for: "decrypt" and "encrypt" strings.
Returns the message encrypted/decrypted.
message must be a multiple by 16 bytes (for division in 16 byte blocks)
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(message) % blocksize != 0: ivp = iv[0:blocksize]
raise ValueError("message must be a multiple of 16 bytes (try adding " + ivp2 = iv[blocksize:]
str(16 - len(message) % 16) + " bytes of padding)")
ivp = self.iv[0:blocksize] ciphered = bytearray()
ivp2 = self.iv[blocksize:]
ciphered = bytearray() for i in range(0, len(message), blocksize):
indata = message[i:i+blocksize]
for i in range(0, len(message), blocksize): if operation == "decrypt":
indata = message[i:i+blocksize] xored = strxor(indata, ivp2)
if operation == "decrypt": decrypt_xored = cipher.decrypt(xored)
xored = strxor(indata, ivp2) outdata = strxor(decrypt_xored, ivp)
decrypt_xored = self.cipher.decrypt(xored) ivp = indata
outdata = strxor(decrypt_xored, ivp) ivp2 = outdata
ivp = indata elif operation == "encrypt":
ivp2 = outdata xored = strxor(indata, ivp)
elif operation == "encrypt": encrypt_xored = cipher.encrypt(xored)
xored = strxor(indata, ivp) outdata = strxor(encrypt_xored, ivp2)
encrypt_xored = self.cipher.encrypt(xored) ivp = outdata
outdata = strxor(encrypt_xored, ivp2) ivp2 = indata
ivp = outdata else:
ivp2 = indata raise ValueError("operation must be either 'decrypt' or 'encrypt'")
else: ciphered.extend(outdata)
raise ValueError("operation must be either 'decrypt' or 'encrypt'") return ciphered
ciphered.extend(outdata)
return ciphered

View File

@ -19,6 +19,7 @@ import struct
from Crypto.Hash import SHA from Crypto.Hash import SHA
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Util.strxor import strxor from Crypto.Util.strxor import strxor
from Crypto.Util.number import long_to_bytes, bytes_to_long
# local modules # local modules
import crypt import crypt
@ -36,15 +37,13 @@ def vis(bs):
bs = bytearray(bs) bs = bytearray(bs)
symbols_in_one_line = 8 symbols_in_one_line = 8
n = len(bs) // symbols_in_one_line n = len(bs) // symbols_in_one_line
i = 0
for i in range(n): 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 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: 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 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: class Session:
""" Manages TCP Transport. encryption and message frames """ """ Manages TCP Transport. encryption and message frames """
def __init__(self, ip, port, auth_key=None): 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 # 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 # 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) # 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() #curr_timestamp = (datetime.utcfromtimestamp(time()) - datetime(1970, 1, 1)).total_seconds()
msg_id = int(curr_timestamp*2**30)*4
msg_id = int(time()*2**30)*4
#msg_id = int(datetime.utcnow().timestamp()*2**30)*4 #msg_id = int(datetime.utcnow().timestamp()*2**30)*4
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' + return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
@ -101,8 +102,8 @@ class Session:
self.sock.send(step2) self.sock.send(step2)
self.number += 1 self.number += 1
# Sending message visualisation to console # Sending message visualisation to console
# print('>>') # print('>>')
# vis(step2) # vis(step2)
def recv_message(self): def recv_message(self):
""" """
@ -141,7 +142,7 @@ class Session:
def create_auth_key(self): def create_auth_key(self):
nonce = os.urandom(16) nonce = os.urandom(16)
print("Requesting PQ") print("Requesting pq")
ResPQ = self.method_call('req_pq', nonce=nonce) ResPQ = self.method_call('req_pq', nonce=nonce)
server_nonce = ResPQ['server_nonce'] server_nonce = ResPQ['server_nonce']
@ -149,22 +150,18 @@ class Session:
# TODO: selecting RSA public key based on this fingerprint # TODO: selecting RSA public key based on this fingerprint
public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0] public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0]
PQ_bytes = ResPQ['pq'] pq_bytes = ResPQ['pq']
# TODO: from_bytes here # TODO: from_bytes here
PQ = struct.unpack('>q', PQ_bytes)[0] pq = struct.unpack('>q', pq_bytes)[0]
[p, q] = prime.primefactors(PQ) [p, q] = prime.primefactors(pq)
if p > q: (p, q) = (q, p) if p > q: (p, q) = (q, p)
assert p*q == PQ and p < q 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)
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")) f = open(os.path.join(os.path.dirname(__file__), "rsa.pub"))
key = RSA.importKey(f.read()) key = RSA.importKey(f.read())
z = io.BytesIO() z = io.BytesIO()
@ -172,12 +169,12 @@ class Session:
new_nonce = os.urandom(32) new_nonce = os.urandom(32)
TL.serialize_obj(z, 'p_q_inner_data', TL.serialize_obj(z, 'p_q_inner_data',
pq=PQ_bytes, pq=pq_bytes,
p=P_bytes, p=p_bytes,
q=Q_bytes, q=q_bytes,
nonce=nonce, nonce=nonce,
server_nonce=server_nonce, server_nonce=server_nonce,
new_nonce=new_nonce) new_nonce=new_nonce)
data = z.getvalue() data = z.getvalue()
sha_digest = SHA.new(data).digest() sha_digest = SHA.new(data).digest()
@ -187,12 +184,12 @@ class Session:
print("Starting Diffie Hellman key exchange") print("Starting Diffie Hellman key exchange")
server_DH_params = self.method_call('req_DH_params', server_DH_params = self.method_call('req_DH_params',
nonce=nonce, # 16 bytes nonce=nonce, # 16 bytes
server_nonce=server_nonce, server_nonce=server_nonce,
p=P_bytes, p=p_bytes,
q=Q_bytes, q=q_bytes,
public_key_fingerprint=public_key_fingerprint, public_key_fingerprint=public_key_fingerprint,
encrypted_data=encrypted_data) encrypted_data=encrypted_data)
assert nonce == server_DH_params['nonce'] assert nonce == server_DH_params['nonce']
assert server_nonce == server_DH_params['server_nonce'] assert server_nonce == server_DH_params['server_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_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] 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 = crypt.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
answer_with_hash = crypter.decrypt(encrypted_answer)
answer_hash = answer_with_hash[:20] answer_hash = answer_with_hash[:20]
answer = answer_with_hash[20:] answer = answer_with_hash[20:]
@ -219,54 +214,57 @@ class Session:
self.timedelta = server_time - time() self.timedelta = server_time - time()
print("Server-client time delta = %.1f s" % self.timedelta) print("Server-client time delta = %.1f s" % self.timedelta)
dh_prime = int.from_bytes(dh_prime_str,'big') dh_prime = bytes_to_long(dh_prime_str)
g_a = int.from_bytes(g_a_str,'big') g_a = bytes_to_long(g_a_str)
assert prime.isprime(dh_prime) assert prime.isprime(dh_prime)
retry_id = 0 retry_id = 0
while retry_id<25: b_str = os.urandom(256)
try: b = bytes_to_long(b_str)
b_str = os.urandom(256) g_b = pow(g, b, dh_prime)
b = int.from_bytes(b_str,'big')
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() z = io.BytesIO()
TL.serialize_obj(z, 'client_DH_inner_data', TL.serialize_obj(z, 'client_DH_inner_data',
nonce=nonce, nonce=nonce,
server_nonce=server_nonce, server_nonce=server_nonce,
retry_id=retry_id, retry_id=retry_id,
g_b=g_b_str) g_b=g_b_str)
data = z.getvalue() data = z.getvalue()
data_with_sha = SHA.new(data).digest()+data data_with_sha = SHA.new(data).digest()+data
data_with_sha_padded = data_with_sha + os.urandom(-len(data_with_sha) % 16) 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', Set_client_DH_params_answer = self.method_call('set_client_DH_params',
nonce=nonce, nonce=nonce,
server_nonce=server_nonce, server_nonce=server_nonce,
encrypted_data=encrypted_data) encrypted_data=encrypted_data)
auth_key = pow(g_a, b, dh_prime) 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_sha = SHA.new(auth_key_str).digest()
auth_key_hash = auth_key_sha[-8:] auth_key_hash = auth_key_sha[-8:]
auth_key_aux_hash = auth_key_sha[:8] auth_key_aux_hash = auth_key_sha[:8]
new_nonce_hash1 = SHA.new(new_nonce+b'\x01'+auth_key_aux_hash).digest()[-16:] new_nonce_hash1 = SHA.new(new_nonce+b'\x01'+auth_key_aux_hash).digest()[-16:]
new_nonce_hash2 = SHA.new(new_nonce+b'\x02'+auth_key_aux_hash).digest()[-16:] new_nonce_hash2 = SHA.new(new_nonce+b'\x02'+auth_key_aux_hash).digest()[-16:]
new_nonce_hash3 = SHA.new(new_nonce+b'\x03'+auth_key_aux_hash).digest()[-16:] new_nonce_hash3 = SHA.new(new_nonce+b'\x03'+auth_key_aux_hash).digest()[-16:]
assert Set_client_DH_params_answer['nonce'] == nonce assert Set_client_DH_params_answer['nonce'] == nonce
assert Set_client_DH_params_answer['server_nonce'] == server_nonce assert Set_client_DH_params_answer['server_nonce'] == server_nonce
assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1 assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1
print("Diffie Hellman key exchange processed successfully") print("Diffie Hellman key exchange processed successfully")
self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8]) self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8])
self.auth_key = auth_key_str self.auth_key = auth_key_str
print("Auth key generated") 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