Skip to content

Commit 1029e22

Browse files
Sebastian Molendapubnub-release-bot
andauthored
Add support for switching cipher methods (#156)
* Add support for switching cipher methods through PNConfiguration * Validation of cipher methods * Default - CBC, fallback - None * Add fallback to file crypto * PubNub SDK 7.2.0 release. --------- Co-authored-by: PubNub Release Bot <[email protected]>
1 parent f33f7af commit 1029e22

File tree

11 files changed

+783
-27
lines changed

11 files changed

+783
-27
lines changed

.pubnub.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: python
2-
version: 7.1.0
2+
version: 7.2.0
33
schema: 1
44
scm: github.com/pubnub/python
55
sdks:
@@ -18,7 +18,7 @@ sdks:
1818
distributions:
1919
- distribution-type: library
2020
distribution-repository: package
21-
package-name: pubnub-7.1.0
21+
package-name: pubnub-7.2.0
2222
location: https://pypi.org/project/pubnub/
2323
supported-platforms:
2424
supported-operating-systems:
@@ -97,8 +97,8 @@ sdks:
9797
-
9898
distribution-type: library
9999
distribution-repository: git release
100-
package-name: pubnub-7.1.0
101-
location: https://github.com/pubnub/python/releases/download/7.1.0/pubnub-7.1.0.tar.gz
100+
package-name: pubnub-7.2.0
101+
location: https://github.com/pubnub/python/releases/download/7.2.0/pubnub-7.2.0.tar.gz
102102
supported-platforms:
103103
supported-operating-systems:
104104
Linux:
@@ -169,6 +169,11 @@ sdks:
169169
license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt
170170
is-required: Required
171171
changelog:
172+
- date: 2023-07-06
173+
version: 7.2.0
174+
changes:
175+
- type: feature
176+
text: "Introduced option to select ciphering method for encoding messages and files. The default behavior is unchanged. More can be read [in this comment](https://github.com/pubnub/python/pull/156#issuecomment-1623307799)."
172177
- date: 2023-01-17
173178
version: 7.1.0
174179
changes:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 7.2.0
2+
July 06 2023
3+
4+
#### Added
5+
- Introduced option to select ciphering method for encoding messages and files. The default behavior is unchanged. More can be read [in this comment](https://github.com/pubnub/python/pull/156#issuecomment-1623307799).
6+
17
## 7.1.0
28
January 17 2023
39

examples/crypto.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from Cryptodome.Cipher import AES
2+
from os import getenv
3+
from pubnub.pnconfiguration import PNConfiguration
4+
from pubnub.pubnub import PubNub
5+
from time import sleep
6+
7+
channel = 'cipher_algorithm_experiment'
8+
9+
10+
def PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) -> PubNub:
11+
config = config = PNConfiguration()
12+
config.publish_key = getenv('PN_KEY_PUBLISH')
13+
config.subscribe_key = getenv('PN_KEY_SUBSCRIBE')
14+
config.secret_key = getenv('PN_KEY_SECRET')
15+
config.cipher_key = getenv('PN_KEY_CIPHER')
16+
config.user_id = 'experiment'
17+
config.cipher_mode = cipher_mode
18+
config.fallback_cipher_mode = fallback_cipher_mode
19+
20+
return PubNub(config)
21+
22+
23+
# let's build history with legacy AES.CBC
24+
pn = PNFactory(cipher_mode=AES.MODE_CBC, fallback_cipher_mode=None)
25+
pn.publish().channel(channel).message('message encrypted with CBC').sync()
26+
pn.publish().channel(channel).message('message encrypted with CBC').sync()
27+
28+
# now with upgraded config
29+
pn = PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC)
30+
pn.publish().channel(channel).message('message encrypted with GCM').sync()
31+
pn.publish().channel(channel).message('message encrypted with GCM').sync()
32+
33+
# give some time to store messages
34+
sleep(3)
35+
36+
# after upgrade decoding with GCM and fallback CBC
37+
pn = PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC)
38+
messages = pn.history().channel(channel).sync()
39+
print([message.entry for message in messages.result.messages])
40+
41+
# before upgrade decoding with CBC and without fallback
42+
pn = PNFactory(cipher_mode=AES.MODE_CBC, fallback_cipher_mode=None)
43+
try:
44+
messages = pn.history().channel(channel).sync()
45+
print([message.entry for message in messages.result.messages])
46+
except UnicodeDecodeError:
47+
print('Unable to decode - Exception has been thrown')

pubnub/crypto.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import random
44
from base64 import decodebytes, encodebytes
55

6-
from .crypto_core import PubNubCrypto
6+
from pubnub.crypto_core import PubNubCrypto
77
from Cryptodome.Cipher import AES
88
from Cryptodome.Util.Padding import pad, unpad
99

@@ -12,14 +12,19 @@
1212

1313

1414
class PubNubCryptodome(PubNubCrypto):
15+
mode = AES.MODE_CBC
16+
fallback_mode = None
17+
1518
def __init__(self, pubnub_config):
1619
self.pubnub_configuration = pubnub_config
20+
self.mode = pubnub_config.cipher_mode
21+
self.fallback_mode = pubnub_config.fallback_cipher_mode
1722

1823
def encrypt(self, key, msg, use_random_iv=False):
1924
secret = self.get_secret(key)
2025
initialization_vector = self.get_initialization_vector(use_random_iv)
2126

22-
cipher = AES.new(bytes(secret[0:32], 'utf-8'), AES.MODE_CBC, bytes(initialization_vector, 'utf-8'))
27+
cipher = AES.new(bytes(secret[0:32], 'utf-8'), self.mode, bytes(initialization_vector, 'utf-8'))
2328
encrypted_message = cipher.encrypt(self.pad(msg.encode('utf-8')))
2429
msg_with_iv = self.append_random_iv(encrypted_message, use_random_iv, bytes(initialization_vector, "utf-8"))
2530

@@ -30,8 +35,15 @@ def decrypt(self, key, msg, use_random_iv=False):
3035

3136
decoded_message = decodebytes(msg.encode("utf-8"))
3237
initialization_vector, extracted_message = self.extract_random_iv(decoded_message, use_random_iv)
33-
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, initialization_vector)
34-
plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'))
38+
cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, initialization_vector)
39+
try:
40+
plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'))
41+
except UnicodeDecodeError as e:
42+
if not self.fallback_mode:
43+
raise e
44+
45+
cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector)
46+
plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'))
3547

3648
try:
3749
return json.loads(plain)
@@ -71,7 +83,7 @@ class PubNubFileCrypto(PubNubCryptodome):
7183
def encrypt(self, key, file):
7284
secret = self.get_secret(key)
7385
initialization_vector = self.get_initialization_vector(use_random_iv=True)
74-
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, bytes(initialization_vector, 'utf-8'))
86+
cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, bytes(initialization_vector, 'utf-8'))
7587
initialization_vector = bytes(initialization_vector, 'utf-8')
7688

7789
return self.append_random_iv(
@@ -83,6 +95,11 @@ def encrypt(self, key, file):
8395
def decrypt(self, key, file):
8496
secret = self.get_secret(key)
8597
initialization_vector, extracted_file = self.extract_random_iv(file, use_random_iv=True)
86-
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, initialization_vector)
98+
try:
99+
cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, initialization_vector)
100+
result = unpad(cipher.decrypt(extracted_file), 16)
101+
except ValueError:
102+
cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector)
103+
result = unpad(cipher.decrypt(extracted_file), 16)
87104

88-
return unpad(cipher.decrypt(extracted_file), 16)
105+
return result

pubnub/pnconfiguration.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
from .enums import PNHeartbeatNotificationOptions, PNReconnectionPolicy
1+
from Cryptodome.Cipher import AES
2+
from pubnub.enums import PNHeartbeatNotificationOptions, PNReconnectionPolicy
3+
from pubnub.exceptions import PubNubException
24

35

46
class PNConfiguration(object):
57
DEFAULT_PRESENCE_TIMEOUT = 300
68
DEFAULT_HEARTBEAT_INTERVAL = 280
9+
ALLOWED_AES_MODES = [AES.MODE_CBC, AES.MODE_GCM]
710

811
def __init__(self):
912
# TODO: add validation
@@ -17,6 +20,8 @@ def __init__(self):
1720
self.publish_key = None
1821
self.secret_key = None
1922
self.cipher_key = None
23+
self._cipher_mode = AES.MODE_CBC
24+
self._fallback_cipher_mode = None
2025
self.auth_key = None
2126
self.filter_expression = None
2227
self.enable_subscribe = True
@@ -61,6 +66,30 @@ def set_presence_timeout_with_custom_interval(self, timeout, interval):
6166
def set_presence_timeout(self, timeout):
6267
self.set_presence_timeout_with_custom_interval(timeout, (timeout / 2) - 1)
6368

69+
@property
70+
def cipher_mode(self):
71+
return self._cipher_mode
72+
73+
@cipher_mode.setter
74+
def cipher_mode(self, cipher_mode):
75+
if cipher_mode not in self.ALLOWED_AES_MODES:
76+
raise PubNubException('Cipher mode not supported')
77+
if cipher_mode is not self._cipher_mode:
78+
self._cipher_mode = cipher_mode
79+
self.crypto_instance = None
80+
81+
@property
82+
def fallback_cipher_mode(self):
83+
return self._fallback_cipher_mode
84+
85+
@fallback_cipher_mode.setter
86+
def fallback_cipher_mode(self, fallback_cipher_mode):
87+
if fallback_cipher_mode not in self.ALLOWED_AES_MODES:
88+
raise PubNubException('Cipher mode not supported')
89+
if fallback_cipher_mode is not self._fallback_cipher_mode:
90+
self._fallback_cipher_mode = fallback_cipher_mode
91+
self.crypto_instance = None
92+
6493
@property
6594
def crypto(self):
6695
if self.crypto_instance is None:

pubnub/pubnub_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383

8484
class PubNubCore:
8585
"""A base class for PubNub Python API implementations"""
86-
SDK_VERSION = "7.1.0"
86+
SDK_VERSION = "7.2.0"
8787
SDK_NAME = "PubNub-Python"
8888

8989
TIMESTAMP_DIVIDER = 1000

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='pubnub',
5-
version='7.1.0',
5+
version='7.2.0',
66
description='PubNub Real-time push service in the cloud',
77
author='PubNub',
88
author_email='[email protected]',

0 commit comments

Comments
 (0)