From 5b3f60db7d19c4ced5b9b1e43981291bbdd00df9 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 2 Jan 2016 12:31:04 +0000 Subject: [PATCH] implemented HMAC crypto --- steam/core/cm.py | 39 ++++++++++++++++++++++++++++++--------- steam/core/crypto.py | 44 ++++++++++++++++++++++++++++++++++++++------ steam/core/msg.py | 5 +++++ 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/steam/core/cm.py b/steam/core/cm.py index 808b833..9c12f58 100644 --- a/steam/core/cm.py +++ b/steam/core/cm.py @@ -100,6 +100,7 @@ class CMClient(EventEmitter): self.connected = False self.key = None + self.hmac_secret = None self.steam_id = None self.session_id = None @@ -124,7 +125,10 @@ class CMClient(EventEmitter): data = message.serialize() if self.key: - data = crypto.encrypt(data, self.key) + if self.hmac_secret: + data = crypto.symmetric_encrypt_HMAC(data, self.key, self.hmac_secret) + else: + data = crypto.symmetric_encrypt(data, self.key) self.connection.put_message(data) @@ -139,7 +143,15 @@ class CMClient(EventEmitter): continue if self.key: - message = crypto.decrypt(message, self.key) + if self.hmac_secret: + try: + message = crypto.symmetric_decrypt_HMAC(message, self.key, self.hmac_secret) + except RuntimeError as e: + logger.exception(e) + gevent.spawn(self.disconnect) + return + else: + message = crypto.symmetric_decrypt(message, self.key) self._parse_message(message) @@ -176,14 +188,20 @@ class CMClient(EventEmitter): def _handle_encrypt_request(self, msg): logger.debug("Securing channel") - if msg.body.protocolVersion != 1: - raise RuntimeError("Unsupported protocol version") - if msg.body.universe != EUniverse.Public: - raise RuntimeError("Unsupported universe") + try: + if msg.body.protocolVersion != 1: + raise RuntimeError("Unsupported protocol version") + if msg.body.universe != EUniverse.Public: + raise RuntimeError("Unsupported universe") + except RuntimeError as e: + logger.exception(e) + gevent.spawn(self.disconnect) + return resp = Msg(EMsg.ChannelEncryptResponse) - key, resp.body.key = crypto.generate_session_key() + challenge = msg.body.challenge + key, resp.body.key = crypto.generate_session_key(challenge) resp.body.crc = binascii.crc32(resp.body.key) & 0xffffffff self.send_message(resp) @@ -192,12 +210,15 @@ class CMClient(EventEmitter): if msg.body.eresult != EResult.OK: logger.debug("Failed to secure channel: %s" % msg.body.eresult) - self.disconnect() + gevent.spawn(self.disconnect) return logger.debug("Channel secured") self.key = key + if challenge: + self.hmac_secret = key[:16] + self.emit('channel_secured') def _handle_multi(self, msg): @@ -210,7 +231,7 @@ class CMClient(EventEmitter): if len(data) != msg.body.size_unzipped: logger.fatal("Unzipped size mismatch") - self.disconnect() + gevent.spawn(self.disconnect) return else: data = msg.body.message_body diff --git a/steam/core/crypto.py b/steam/core/crypto.py index 7b772ab..3872a3d 100644 --- a/steam/core/crypto.py +++ b/steam/core/crypto.py @@ -2,6 +2,7 @@ from base64 import b64decode from Crypto import Random from Crypto.Cipher import PKCS1_OAEP, AES from Crypto.PublicKey import RSA +from Crypto.Hash import HMAC, SHA public_key = """ MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDf7BrWLBBmLBc1OhSwfFkRf53T @@ -15,21 +16,52 @@ pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) unpad = lambda s: s[0:-ord(s[-1])] -def generate_session_key(): +def generate_session_key(hmac_secret=''): session_key = Random.new().read(32) cipher = PKCS1_OAEP.new(RSA.importKey(b64decode(public_key))) - encrypted_session_key = cipher.encrypt(session_key) + encrypted_session_key = cipher.encrypt(session_key + hmac_secret) return (session_key, encrypted_session_key) - -def encrypt(message, key): +def symmetric_encrypt(message, key): iv = Random.new().read(BS) + return symmetric_encrypt_with_iv(message, key, iv) + +def symmetric_encrypt_HMAC(message, key, hmac_secret): + random_bytes = Random.new().read(3) + + hmac = HMAC.new(hmac_secret, digestmod=SHA) + hmac.update(random_bytes) + hmac.update(message) + + iv = hmac.digest()[:13] + random_bytes + + return symmetric_encrypt_with_iv(message, key, iv) + +def symmetric_encrypt_with_iv(message, key, iv): encrypted_iv = AES.new(key, AES.MODE_ECB).encrypt(iv) cyphertext = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(message)) return encrypted_iv + cyphertext +def symmetric_decrypt(cyphertext, key): + iv = symmetric_decrypt_iv(cyphertext, key) + return symmetric_decrypt_with_iv(cyphertext, key, iv) + +def symmetric_decrypt_HMAC(cyphertext, key, hmac_secret): + iv = symmetric_decrypt_iv(cyphertext, key) + message = symmetric_decrypt_with_iv(cyphertext, key, iv) + + hmac = HMAC.new(hmac_secret, digestmod=SHA) + hmac.update(iv[-3:]) + hmac.update(message) + + if iv[:13] != hmac.digest()[:13]: + raise RuntimeError("Unable to decrypt message. HMAC does not match.") + + return message + +def symmetric_decrypt_iv(cyphertext, key): + return AES.new(key, AES.MODE_ECB).decrypt(cyphertext[:BS]) -def decrypt(cyphertext, key): - iv = AES.new(key, AES.MODE_ECB).decrypt(cyphertext[:BS]) +def symmetric_decrypt_with_iv(cyphertext, key, iv): message = AES.new(key, AES.MODE_CBC, iv).decrypt(cyphertext[BS:]) return unpad(message) diff --git a/steam/core/msg.py b/steam/core/msg.py index 7c0076c..676e7a5 100644 --- a/steam/core/msg.py +++ b/steam/core/msg.py @@ -301,6 +301,7 @@ class ChannelEncryptRequest: else: self.protocolVersion = 1 self.universe = EUniverse.Invalid + self.challenge = '' def serialize(self): return struct.pack("= 16: + self.challenge = data[8:] + def __str__(self): return '\n'.join(["protocolVersion: %s" % self.protocolVersion, "universe: %s" % repr(self.universe), + "challenge: %s" % repr(self.challenge), ])