Skip to content

Commit c16ed4c

Browse files
migrate from PyCrypto to cryptograhy #17
1 parent 17dd419 commit c16ed4c

File tree

7 files changed

+75
-55
lines changed

7 files changed

+75
-55
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ gevent==1.1.0
55
mock==1.3.0
66
nose==1.3.7
77
protobuf==2.6.1
8-
pycrypto==2.6.1
8+
cryptography==1.3
99
PyYAML==3.11
1010
requests==2.9.1
1111
vcrpy==1.7.4

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
__version__ = f.readline().split('"')[1]
1313

1414
install_requires = [
15-
'pycrypto>=2.6.1',
15+
'cryptography>=1.3',
1616
'requests>=2.9.1',
1717
'vdf>=2.0',
1818
]

steam/client/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
gevent.monkey.patch_socket()
66
gevent.monkey.patch_ssl()
77

8-
from Crypto.Hash import SHA
8+
from steam.core.crypto import sha1_hash
99
from eventemitter import EventEmitter
1010
from steam.enums.emsg import EMsg
1111
from steam.enums import EResult, EOSType, EPersonaState
@@ -120,7 +120,7 @@ def _handle_update_machine_auth(self, message):
120120

121121
resp.body.filename = message.body.filename
122122
resp.body.eresult = EResult.OK
123-
resp.body.sha_file = SHA.new(message.body.bytes).digest()
123+
resp.body.sha_file = sha1_hash(message.body.bytes)
124124
resp.body.getlasterror = 0
125125
resp.body.offset = message.body.offset
126126
resp.body.cubwrote = message.body.cubtowrite
@@ -336,7 +336,7 @@ def auth_code_prompt(is_2fa, code_mismatch):
336336
message.body.eresult_sentryfile = EResult.FileNotFound
337337
else:
338338
message.body.eresult_sentryfile = EResult.OK
339-
message.body.sha_sentryfile = SHA.new(sentry).digest()
339+
message.body.sha_sentryfile = sha1_hash(sentry)
340340

341341
if auth_code:
342342
message.body.auth_code = auth_code

steam/client/builtins/web.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""
22
Web related features
33
"""
4-
from Crypto.Hash import SHA
5-
from Crypto.Random import new as randombytes
4+
from binascii import hexlify
65
from steam import WebAPI
7-
from steam.core.crypto import generate_session_key, symmetric_encrypt
6+
from steam.core.crypto import generate_session_key, symmetric_encrypt, sha1_hash, random_bytes
87
from steam.util.web import make_requests_session
98

109

@@ -40,7 +39,7 @@ def get_web_session_cookies(self):
4039
return None
4140

4241
return {
43-
'sessionid': SHA.new(randombytes().read(32)).hexdigest(),
42+
'sessionid': hexlify(sha1_hash(random_bytes(32))),
4443
'steamLogin': resp['authenticateuser']['token'],
4544
'steamLoginSecure': resp['authenticateuser']['tokensecure'],
4645
}

steam/core/crypto.py

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import sys
2+
from os import urandom as random_bytes
23
from struct import pack
34
from base64 import b64decode
4-
from Crypto import Random
5-
from Crypto.Cipher import PKCS1_OAEP, AES
6-
from Crypto.PublicKey import RSA
7-
from Crypto.Hash import HMAC, SHA
8-
9-
public_key = """
5+
from cryptography.hazmat.primitives.hmac import HMAC
6+
from cryptography.hazmat.primitives.hashes import Hash, SHA1
7+
from cryptography.hazmat.primitives.asymmetric.padding import PSS, OAEP, MGF1
8+
from cryptography.hazmat.primitives.ciphers import Cipher
9+
from cryptography.hazmat.primitives.ciphers.algorithms import AES
10+
from cryptography.hazmat.primitives.ciphers.modes import CBC, ECB
11+
import cryptography.hazmat.backends
12+
backend = cryptography.hazmat.backends.default_backend()
13+
14+
15+
class UniverseKey(object):
16+
Public = backend.load_der_public_key(b64decode("""
1017
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDf7BrWLBBmLBc1OhSwfFkRf53T
1118
2Ct64+AVzRkeRuh7h3SiGEYxqQMUeYKO6UWiSRKpI2hzic9pobFhRr3Bvr/WARvY
1219
gdTckPv+T1JzZsuVcNfFjrocejN1oWI0Rrtgt4Bo+hOneoo3S57G9F1fOpn5nsQ6
1320
6WOiu4gZKODnFMBCiQIBEQ==
14-
"""
21+
"""))
1522

16-
BS = AES.block_size
23+
BS = 16
1724
pad = lambda s: s + (BS - len(s) % BS) * pack('B', BS - len(s) % BS)
1825

1926
if sys.version_info < (3,):
@@ -29,29 +36,35 @@ def generate_session_key(hmac_secret=b''):
2936
:return: (session_key, encrypted_session_key) tuple
3037
:rtype: :class:`tuple`
3138
"""
32-
session_key = Random.new().read(32)
33-
cipher = PKCS1_OAEP.new(RSA.importKey(b64decode(public_key)))
34-
encrypted_session_key = cipher.encrypt(session_key + hmac_secret)
39+
session_key = random_bytes(32)
40+
encrypted_session_key = UniverseKey.Public.encrypt(session_key + hmac_secret,
41+
OAEP(MGF1(SHA1()), SHA1(), None)
42+
)
3543
return (session_key, encrypted_session_key)
3644

3745
def symmetric_encrypt(message, key):
38-
iv = Random.new().read(BS)
46+
iv = random_bytes(BS)
3947
return symmetric_encrypt_with_iv(message, key, iv)
4048

4149
def symmetric_encrypt_HMAC(message, key, hmac_secret):
42-
random_bytes = Random.new().read(3)
50+
prefix = random_bytes(3)
4351

44-
hmac = HMAC.new(hmac_secret, digestmod=SHA)
45-
hmac.update(random_bytes)
52+
hmac = HMAC(hmac_secret, SHA1(), backend)
53+
hmac.update(prefix)
4654
hmac.update(message)
4755

48-
iv = hmac.digest()[:13] + random_bytes
56+
iv = hmac.finalize()[:13] + prefix
4957

5058
return symmetric_encrypt_with_iv(message, key, iv)
5159

60+
def symmetric_encrypt_iv(iv, key):
61+
encryptor = Cipher(AES(key), ECB(), backend).encryptor()
62+
return encryptor.update(iv) + encryptor.finalize()
63+
5264
def symmetric_encrypt_with_iv(message, key, iv):
53-
encrypted_iv = AES.new(key, AES.MODE_ECB).encrypt(iv)
54-
cyphertext = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(message))
65+
encrypted_iv = symmetric_encrypt_iv(iv, key)
66+
encryptor = Cipher(AES(key), CBC(iv), backend).encryptor()
67+
cyphertext = encryptor.update(pad(message)) + encryptor.finalize()
5568
return encrypted_iv + cyphertext
5669

5770
def symmetric_decrypt(cyphertext, key):
@@ -63,18 +76,24 @@ def symmetric_decrypt_HMAC(cyphertext, key, hmac_secret):
6376
iv = symmetric_decrypt_iv(cyphertext, key)
6477
message = symmetric_decrypt_with_iv(cyphertext, key, iv)
6578

66-
hmac = HMAC.new(hmac_secret, digestmod=SHA)
79+
hmac = HMAC(hmac_secret, SHA1(), backend)
6780
hmac.update(iv[-3:])
6881
hmac.update(message)
6982

70-
if iv[:13] != hmac.digest()[:13]:
83+
if iv[:13] != hmac.finalize()[:13]:
7184
raise RuntimeError("Unable to decrypt message. HMAC does not match.")
7285

7386
return message
7487

7588
def symmetric_decrypt_iv(cyphertext, key):
76-
return AES.new(key, AES.MODE_ECB).decrypt(cyphertext[:BS])
89+
decryptor = Cipher(AES(key), ECB(), backend).decryptor()
90+
return decryptor.update(cyphertext[:BS]) + decryptor.finalize()
7791

7892
def symmetric_decrypt_with_iv(cyphertext, key, iv):
79-
message = AES.new(key, AES.MODE_CBC, iv).decrypt(cyphertext[BS:])
80-
return unpad(message)
93+
decryptor = Cipher(AES(key), CBC(iv), backend).decryptor()
94+
return unpad(decryptor.update(cyphertext[BS:]) + decryptor.finalize())
95+
96+
def sha1_hash(data):
97+
sha = Hash(SHA1(), backend)
98+
sha.update(data)
99+
return sha.finalize()

steam/webauth.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
import time
4242
import sys
4343
from base64 import b64encode
44-
from Crypto.PublicKey import RSA
45-
from Crypto.Cipher import PKCS1_v1_5
44+
45+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
46+
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
47+
from steam.core.crypto import backend
48+
4649
from steam.util.web import make_requests_session
4750
from steam import SteamID
4851

@@ -53,7 +56,7 @@
5356

5457

5558
class WebAuth(object):
56-
cipher = None
59+
key = None
5760
complete = False #: whether authentication has been completed successfully
5861
session = None #: :class:`requests.Session` (with auth cookies after auth is complete)
5962
captcha_gid = -1
@@ -93,15 +96,15 @@ def get_rsa_key(self, username):
9396

9497
return resp
9598

96-
def _make_cipher(self):
97-
if not self.cipher:
99+
def _load_key(self):
100+
if not self.key:
98101
resp = self.get_rsa_key(self.username)
99102

100-
rsa = RSA.construct((intBase(resp['publickey_mod'], 16),
101-
intBase(resp['publickey_exp'], 16),
102-
))
103+
nums = RSAPublicNumbers(intBase(resp['publickey_exp'], 16),
104+
intBase(resp['publickey_mod'], 16),
105+
)
103106

104-
self.cipher = PKCS1_v1_5.new(rsa)
107+
self.key = backend.load_rsa_public_numbers(nums)
105108
self.timestamp = resp['timestamp']
106109

107110
def login(self, captcha='', email_code='', twofactor_code='', language='english'):
@@ -126,11 +129,11 @@ def login(self, captcha='', email_code='', twofactor_code='', language='english'
126129
if self.complete:
127130
return self.session
128131

129-
self._make_cipher()
132+
self._load_key()
130133

131134
params = {
132135
'username' : self.username,
133-
"password": b64encode(self.cipher.encrypt(self.password)),
136+
"password": b64encode(self.key.encrypt(self.password, PKCS1v15())),
134137
"emailauth": email_code,
135138
"emailsteamid": str(self.steamid) if email_code else '',
136139
"twofactorcode": twofactor_code,

tests/test_core_crypto.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,15 @@
77

88
class crypto_testcase(unittest.TestCase):
99
def setUp(self):
10-
class NotRandom:
11-
def read(self, n):
12-
return b'1' * n
10+
patcher = mock.patch('os.urandom')
11+
self.addCleanup(patcher.stop)
12+
self.urandom = patcher.start()
13+
self.urandom.side_effect = lambda n: b'1' * n
1314

14-
def fakeNew():
15-
return NotRandom()
16-
17-
self._oldnew = crypto.Random.new
18-
crypto.Random.new = fakeNew
19-
20-
def tearDown(self):
21-
crypto.Random.new = self._oldnew
15+
patcher = mock.patch('steam.core.crypto.random_bytes')
16+
self.addCleanup(patcher.stop)
17+
self.random_bytes = patcher.start()
18+
self.random_bytes.side_effect = lambda n: b'1' * n
2219

2320
def test_keygen(self):
2421
expected_key = b'1' * 32
@@ -70,4 +67,6 @@ def test_encryption(self):
7067
with self.assertRaises(RuntimeError):
7168
crypto.symmetric_decrypt_HMAC(cyphertext, key, b'4'*16)
7269

73-
70+
def test_sha1_hash(self):
71+
self.assertEqual(crypto.sha1_hash(b'123'), b'@\xbd\x00\x15c\x08_\xc3Qe2\x9e\xa1\xff\\^\xcb\xdb\xbe\xef')
72+
self.assertEqual(crypto.sha1_hash(b'999999'), b'\x1fU#\xa8\xf55(\x9b4\x01\xb2\x99X\xd0\x1b)f\xeda\xd2')

0 commit comments

Comments
 (0)