diff options
author | Mike Crute <mike@crute.us> | 2017-09-30 22:18:26 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2017-09-30 23:09:18 +0000 |
commit | 6b97dd6d8a01ff56b7f70a160fbaf59ddec5c60f (patch) | |
tree | 8b563221ef8bcb1cc9f4f7c739c791dc21142ee9 | |
parent | fdfb18a78e0014d362a2e0654eabf706094e4f09 (diff) | |
download | pydora-6b97dd6d8a01ff56b7f70a160fbaf59ddec5c60f.tar.bz2 pydora-6b97dd6d8a01ff56b7f70a160fbaf59ddec5c60f.tar.xz pydora-6b97dd6d8a01ff56b7f70a160fbaf59ddec5c60f.zip |
Remove PyCrypto and add Cryptography
PyCrypto is no longer supported nor recommended. Cryptography is the
replacement package which is actively maintained by the Python core
committers. Also Cryptography is bundled as binary wheels for all the
platforms we support which removes the install-tiem compiler
requirement, making it easier to distribute Pydora. This commit replaces
all usages of PyCrypto with Cryptography but is completely API
compatible with the previous version.
-rw-r--r-- | pandora/transport.py | 72 | ||||
-rwxr-xr-x | setup.py | 8 | ||||
-rw-r--r-- | tests/test_pandora/test_clientbuilder.py | 8 |
3 files changed, 62 insertions, 26 deletions
diff --git a/pandora/transport.py b/pandora/transport.py index a8f885e..94e5f6d 100644 --- a/pandora/transport.py +++ b/pandora/transport.py | |||
@@ -15,7 +15,10 @@ import json | |||
15 | import base64 | 15 | import base64 |
16 | import requests | 16 | import requests |
17 | from requests.adapters import HTTPAdapter | 17 | from requests.adapters import HTTPAdapter |
18 | from Crypto.Cipher import Blowfish | 18 | from cryptography.hazmat.backends import default_backend |
19 | from cryptography.hazmat.primitives.ciphers import Cipher | ||
20 | from cryptography.hazmat.primitives.ciphers.modes import ECB | ||
21 | from cryptography.hazmat.primitives.ciphers.algorithms import Blowfish | ||
19 | 22 | ||
20 | from .errors import PandoraException | 23 | from .errors import PandoraException |
21 | 24 | ||
@@ -236,6 +239,51 @@ class APITransport(object): | |||
236 | return self._parse_response(result) | 239 | return self._parse_response(result) |
237 | 240 | ||
238 | 241 | ||
242 | class BlowfishCryptor(object): | ||
243 | """Low-Level Blowfish Cryptography | ||
244 | |||
245 | Handles symmetric Blowfish cryptography of raw byte messages with or | ||
246 | without padding. Does not handle messages that are encoded in other formats | ||
247 | like hex or base64. | ||
248 | """ | ||
249 | |||
250 | def __init__(self, key): | ||
251 | self.cipher = Cipher( | ||
252 | Blowfish(key.encode("ascii")), ECB(), backend=default_backend()) | ||
253 | |||
254 | @staticmethod | ||
255 | def _add_padding(data): | ||
256 | block_size = Blowfish.block_size | ||
257 | pad_size = len(data) % block_size | ||
258 | padding = bytes(chr(pad_size) * (block_size - pad_size), "ascii") | ||
259 | return data.encode("utf-8") + padding | ||
260 | |||
261 | @staticmethod | ||
262 | def _strip_padding(data): | ||
263 | pad_size = int(data[-1]) | ||
264 | if not data[-pad_size:] == bytes((pad_size,)) * pad_size: | ||
265 | raise ValueError('Invalid padding') | ||
266 | return data[:-pad_size] | ||
267 | |||
268 | @staticmethod | ||
269 | def _make_bytearray(data): | ||
270 | return bytearray(len(data) + (Blowfish.block_size - 1)) | ||
271 | |||
272 | def decrypt(self, data, strip_padding=True): | ||
273 | buf = self._make_bytearray(data) | ||
274 | dec = self.cipher.decryptor() | ||
275 | len_dec = dec.update_into(data, buf) | ||
276 | data = bytes(buf[:len_dec]) + dec.finalize() | ||
277 | return self._strip_padding(data) if strip_padding else data | ||
278 | |||
279 | def encrypt(self, data): | ||
280 | data = self._add_padding(data) | ||
281 | enc = self.cipher.encryptor() | ||
282 | buf = self._make_bytearray(data) | ||
283 | len_enc = enc.update_into(data, buf) | ||
284 | return bytes(buf[:len_enc]) + enc.finalize() | ||
285 | |||
286 | |||
239 | class Encryptor(object): | 287 | class Encryptor(object): |
240 | """Pandora Blowfish Encryptor | 288 | """Pandora Blowfish Encryptor |
241 | 289 | ||
@@ -244,8 +292,8 @@ class Encryptor(object): | |||
244 | """ | 292 | """ |
245 | 293 | ||
246 | def __init__(self, in_key, out_key): | 294 | def __init__(self, in_key, out_key): |
247 | self.bf_out = Blowfish.new(out_key, Blowfish.MODE_ECB) | 295 | self.bf_out = BlowfishCryptor(out_key) |
248 | self.bf_in = Blowfish.new(in_key, Blowfish.MODE_ECB) | 296 | self.bf_in = BlowfishCryptor(in_key) |
249 | 297 | ||
250 | @staticmethod | 298 | @staticmethod |
251 | def _decode_hex(data): | 299 | def _decode_hex(data): |
@@ -256,22 +304,10 @@ class Encryptor(object): | |||
256 | return base64.b16encode(data).lower() | 304 | return base64.b16encode(data).lower() |
257 | 305 | ||
258 | def decrypt(self, data): | 306 | def decrypt(self, data): |
259 | data = self.bf_out.decrypt(self._decode_hex(data)) | 307 | return json.loads(self.bf_out.decrypt(self._decode_hex(data))) |
260 | return json.loads(self.strip_padding(data)) | ||
261 | 308 | ||
262 | def decrypt_sync_time(self, data): | 309 | def decrypt_sync_time(self, data): |
263 | return int(self.bf_in.decrypt(self._decode_hex(data))[4:-2]) | 310 | return int(self.bf_in.decrypt(self._decode_hex(data), False)[4:-2]) |
264 | |||
265 | def add_padding(self, data): | ||
266 | block_size = Blowfish.block_size | ||
267 | pad_size = len(data) % block_size | ||
268 | return data + (chr(pad_size) * (block_size - pad_size)) | ||
269 | |||
270 | def strip_padding(self, data): | ||
271 | pad_size = int(data[-1]) | ||
272 | if not data[-pad_size:] == bytes((pad_size,)) * pad_size: | ||
273 | raise ValueError('Invalid padding') | ||
274 | return data[:-pad_size] | ||
275 | 311 | ||
276 | def encrypt(self, data): | 312 | def encrypt(self, data): |
277 | return self._encode_hex(self.bf_out.encrypt(self.add_padding(data))) | 313 | return self._encode_hex(self.bf_out.encrypt(data)) |
@@ -56,12 +56,12 @@ setup( | |||
56 | "flake8>=3.3", | 56 | "flake8>=3.3", |
57 | ], | 57 | ], |
58 | tests_require=[ | 58 | tests_require=[ |
59 | "mock>=2.0", | 59 | "mock>=2,<3", |
60 | "coverage>=4.1", | 60 | "coverage>=4.1,<5", |
61 | ], | 61 | ], |
62 | install_requires=[ | 62 | install_requires=[ |
63 | "pycrypto>=2.6", | 63 | "cryptography>=2,<3", |
64 | "requests>=2", | 64 | "requests>=2,<3", |
65 | ], | 65 | ], |
66 | entry_points={ | 66 | entry_points={ |
67 | "console_scripts": [ | 67 | "console_scripts": [ |
diff --git a/tests/test_pandora/test_clientbuilder.py b/tests/test_pandora/test_clientbuilder.py index 27117b0..5ac6e7d 100644 --- a/tests/test_pandora/test_clientbuilder.py +++ b/tests/test_pandora/test_clientbuilder.py | |||
@@ -65,8 +65,8 @@ class TestSettingsDictBuilder(TestCase): | |||
65 | @classmethod | 65 | @classmethod |
66 | def _build_minimal(self): | 66 | def _build_minimal(self): |
67 | return cb.SettingsDictBuilder({ | 67 | return cb.SettingsDictBuilder({ |
68 | "DECRYPTION_KEY": "dec", | 68 | "DECRYPTION_KEY": "blowfishkey", |
69 | "ENCRYPTION_KEY": "enc", | 69 | "ENCRYPTION_KEY": "blowfishkey", |
70 | "PARTNER_USER": "user", | 70 | "PARTNER_USER": "user", |
71 | "PARTNER_PASSWORD": "pass", | 71 | "PARTNER_PASSWORD": "pass", |
72 | "DEVICE": "dev", | 72 | "DEVICE": "dev", |
@@ -75,8 +75,8 @@ class TestSettingsDictBuilder(TestCase): | |||
75 | @classmethod | 75 | @classmethod |
76 | def _build_maximal(self): | 76 | def _build_maximal(self): |
77 | return cb.SettingsDictBuilder({ | 77 | return cb.SettingsDictBuilder({ |
78 | "DECRYPTION_KEY": "dec", | 78 | "DECRYPTION_KEY": "blowfishkey", |
79 | "ENCRYPTION_KEY": "enc", | 79 | "ENCRYPTION_KEY": "blowfishkey", |
80 | "PARTNER_USER": "user", | 80 | "PARTNER_USER": "user", |
81 | "PARTNER_PASSWORD": "pass", | 81 | "PARTNER_PASSWORD": "pass", |
82 | "DEVICE": "dev", | 82 | "DEVICE": "dev", |