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,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

View File

@ -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