Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change KeyBasedEncryptor's padding #1508

Merged
merged 8 commits into from
Oct 5, 2021
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- PBA table collapse in security report on data change. #1423
- Unsigned Windows agent binaries in Linux packages are now signed. #1444
- Some of the gathered credentials no longer appear in database plaintext. #1454
- Encryptor breaking with UTF-8 characters. (Passwords in different languages can be submitted in
the config successfully now.) #1490


### Security
- Generate a random password when creating a new user for CommunicateAsNewUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# is maintained.
from Crypto import Random # noqa: DUO133 # nosec: B413
from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413
from Crypto.Util import Padding # noqa: DUO133

from monkey_island.cc.server_utils.encryption import IEncryptor

Expand All @@ -29,19 +30,12 @@ def __init__(self, key: bytes):
def encrypt(self, plaintext: str) -> str:
cipher_iv = Random.new().read(AES.block_size)
cipher = AES.new(self._key, AES.MODE_CBC, cipher_iv)
return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(plaintext).encode())).decode()
padded_plaintext = Padding.pad(plaintext.encode(), self._BLOCK_SIZE)
return base64.b64encode(cipher_iv + cipher.encrypt(padded_plaintext)).decode()

def decrypt(self, ciphertext: str):
enc_message = base64.b64decode(ciphertext)
cipher_iv = enc_message[0 : AES.block_size]
cipher = AES.new(self._key, AES.MODE_CBC, cipher_iv)
return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode())

# TODO: Review and evaluate the security of the padding function
def _pad(self, message):
return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr(
self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)
)

def _unpad(self, message: str):
return message[0 : -ord(message[len(message) - 1])]
padded_plaintext = cipher.decrypt(enc_message[AES.block_size :])
return Padding.unpad(padded_plaintext, self._BLOCK_SIZE).decode()
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from monkey_island.cc.server_utils.encryption import KeyBasedEncryptor

PLAINTEXT = "password"
PLAINTEXT_MULTIPLE_BLOCK_SIZE = "banana" * KeyBasedEncryptor._BLOCK_SIZE
PLAINTEXT_UTF8_1 = "slaptažodis" # "password" in Lithuanian
PLAINTEXT_UTF8_2 = "弟" # Japanese
PLAINTEXT_UTF8_3 = "ж" # Ukranian

KEY = b"\x84\xd4qA\xb5\xd4Y\x9bH.\x14\xab\xd8\xc7+g\x12\xfa\x80'%\xfd#\xf8c\x94\xb9\x96_\xf4\xc51"

kb_encryptor = KeyBasedEncryptor(KEY)


def test_encrypt_decrypt_string_with_key():
encrypted = kb_encryptor.encrypt(PLAINTEXT)
decrypted = kb_encryptor.decrypt(encrypted)
assert decrypted == PLAINTEXT


@pytest.mark.parametrize("plaintext", [PLAINTEXT_UTF8_1, PLAINTEXT_UTF8_2, PLAINTEXT_UTF8_3])
def test_encrypt_decrypt_string_utf8_with_key(plaintext):
encrypted = kb_encryptor.encrypt(plaintext)
decrypted = kb_encryptor.decrypt(encrypted)
assert decrypted == plaintext


def test_encrypt_decrypt_string_multiple_block_size_with_key():
encrypted = kb_encryptor.encrypt(PLAINTEXT_MULTIPLE_BLOCK_SIZE)
decrypted = kb_encryptor.decrypt(encrypted)
assert decrypted == PLAINTEXT_MULTIPLE_BLOCK_SIZE