Just following my comment: Here is (an overkill of) an example using AES encryption in CTR mode.
In the comment, I stated the encryption will keep the plaintext's length unchanged but it's valid only for the binary format. If text needs to be printed the example here changes is to hex output which doubles the length.
Encrypting line by line probably makes no sense but this example should give an idea of how to accomplish the goal. Probably the encryption/decryption methods can be changed to Lorenz cipher for simplicity if security is not the goal of this kind of fake data masking.
EDIT: minimum running example for python 3.6 with cryptography module installed is available here: https://gist.github.com/Girgitt/7cbfe8e6ffdcf7eba333c348cdcd1642
EDIT: examples fixed for py3.6 (initially made for py2.7)
from unittest import TestCase
from FileContainingEncryptionClass import Encryption
class test_Encryption(TestCase):
def test_plaintext_encryption(self):
plaintext = 'some words to encrypt'
words_lengths = [len(item) for item in plaintext.split(" ")]
plaintext_joined = plaintext.replace(" ", "")
encryptor = Encryption('some key', 'some nonce')
encryptor.init_encryption()
encryptor.update_payload_to_encrypt(bytearray(plaintext_joined, 'utf8'))
cipher_as_text = ''.join([hex(item).lstrip('0x').zfill(2) for item in encryptor.encrypted_payload])
self.assertEqual("c8638dd3ee70e8a7bf9c1c943507fe61b8cb", cipher_as_text)
split_encrypted_in = []
for word_len in words_lengths:
split_encrypted_in.append(cipher_as_text[:2 * word_len])
cipher_as_text = cipher_as_text[2 * word_len:]
split_encrypted = " ".join(split_encrypted_in)
self.assertEqual("c8638dd3 ee70e8a7bf 9c1c 943507fe61b8cb", split_encrypted)
decryptor = Encryption('some key', 'some nonce')
decryptor.init_decryption()
joined_encrypted = split_encrypted.replace(" ", "")
self.assertEqual("c8638dd3ee70e8a7bf9c1c943507fe61b8cb", joined_encrypted)
binary_encrypted = bytearray.fromhex(joined_encrypted)
decryptor.update_payload_to_decrypt(binary_encrypted)
plaintext_joined = decryptor.decrypted_payload.decode('utf8')
self.assertEqual("somewordstoencrypt", ''.join([chr(ord(item)) for item in plaintext_joined]))
plaintext_words = []
plaintext_words_lengths = [int(len(item) / 2) for item in split_encrypted.split(" ")]
self.assertEqual([4, 5, 2, 7], plaintext_words_lengths)
for word_len in plaintext_words_lengths:
plaintext_words.append(plaintext_joined[:word_len])
plaintext_joined = plaintext_joined[word_len:]
decrypted_plaintext = ' '.join(plaintext_words)
self.assertEqual("some words to encrypt", decrypted_plaintext)
The example code uses the Encryption class:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
class Encryption(object):
def __init__(self, key='aKeyNobodyWIllEverUse', nonce='PleaseMakeMeRandomEachTime'):
key = str(key)
while len(key) < 32:
key += key
key = bytearray(key[:32], 'utf8')
nonce = str(nonce)
while len(nonce) < 16:
nonce += nonce
nonce = bytearray(nonce[:16], 'utf8')
backend = default_backend()
self._cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=backend)
self._encryptor = None
self._encrypted_payload = None
self.init_encryption()
self._decryptor = None
self._decrypted_payload = None
self.init_decryption()
def init_encryption(self):
self._encryptor = self._cipher.encryptor()
self._encrypted_payload = None
def update_payload_to_encrypt(self, payload):
if self._encryptor:
self._encrypted_payload = self._encryptor.update(payload)
@property
def encrypted_payload(self):
if self._encrypted_payload:
return self._encrypted_payload
return ''
def init_decryption(self):
self._decryptor = self._cipher.decryptor()
self._decrypted_payload = None
def update_payload_to_decrypt(self, payload):
if self._decryptor:
self._decrypted_payload = self._decryptor.update(payload)
@property
def decrypted_payload(self):
if self._decrypted_payload:
return self._decrypted_payload
return ''