diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 76db7da2a3..9d2aee0cd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,7 +31,11 @@ Fixed ``st2 action-alias execute 'pack install xxx'``. #4511 Contributed by Nick Maludy (Encore Technologies) - +* Fix datastore value encryption and make sure it also works correctly for unicode (non-ascii) + values. + + Reported by @dswebbthg, @nickbaum. (bug fix) #4513 #4527 #4528 + 2.10.0 - December 13, 2018 -------------------------- diff --git a/st2common/st2common/util/crypto.py b/st2common/st2common/util/crypto.py index e6b0ecfefd..834a6e88db 100644 --- a/st2common/st2common/util/crypto.py +++ b/st2common/st2common/util/crypto.py @@ -216,8 +216,14 @@ def cryptography_symmetric_encrypt(encrypt_key, plaintext): assert isinstance(aes_key_bytes, six.binary_type) assert isinstance(hmac_key_bytes, six.binary_type) + if isinstance(plaintext, (six.text_type, six.string_types)): + # Convert data to bytes + data = plaintext.encode('utf-8') + else: + data = plaintext + # Pad data - data = pkcs5_pad(plaintext) + data = pkcs5_pad(data) # Generate IV iv_bytes = os.urandom(KEYCZAR_AES_BLOCK_SIZE) @@ -230,10 +236,6 @@ def cryptography_symmetric_encrypt(encrypt_key, plaintext): # bytes) so we simply add 5 0's header_bytes = b'00000' - if isinstance(data, (six.text_type, six.string_types)): - # Convert data to bytes - data = data.encode('utf-8') - ciphertext_bytes = encryptor.update(data) + encryptor.finalize() msg_bytes = header_bytes + iv_bytes + ciphertext_bytes @@ -368,7 +370,7 @@ def pkcs5_pad(data): Pad data using PKCS5 """ pad = KEYCZAR_AES_BLOCK_SIZE - len(data) % KEYCZAR_AES_BLOCK_SIZE - data = data + pad * chr(pad) + data = data + pad * chr(pad).encode('utf-8') return data diff --git a/st2common/tests/unit/test_crypto_utils.py b/st2common/tests/unit/test_crypto_utils.py index 54ed22d9b3..2bc62835d8 100644 --- a/st2common/tests/unit/test_crypto_utils.py +++ b/st2common/tests/unit/test_crypto_utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Licensed to the StackStorm, Inc ('StackStorm') under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -53,6 +54,31 @@ def setUpClass(cls): super(CryptoUtilsTestCase, cls).setUpClass() CryptoUtilsTestCase.test_crypto_key = AESKey.generate() + def test_symmetric_encrypt_decrypt_short_string_needs_to_be_padded(self): + original = u'a' + crypto = symmetric_encrypt(CryptoUtilsTestCase.test_crypto_key, original) + plain = symmetric_decrypt(CryptoUtilsTestCase.test_crypto_key, crypto) + self.assertEqual(plain, original) + + def test_symmetric_encrypt_decrypt_utf8_character(self): + values = [ + u'£', + u'£££', + u'££££££', + u'č š hello đ č p ž Ž', + u'hello 💩', + u'💩💩💩💩💩' + u'💩💩💩', + u'💩😁' + ] + + for index, original in enumerate(values): + crypto = symmetric_encrypt(CryptoUtilsTestCase.test_crypto_key, original) + plain = symmetric_decrypt(CryptoUtilsTestCase.test_crypto_key, crypto) + self.assertEqual(plain, original) + + self.assertEqual(index, (len(values) - 1)) + def test_symmetric_encrypt_decrypt(self): original = 'secret' crypto = symmetric_encrypt(CryptoUtilsTestCase.test_crypto_key, original)