Exemplo n.º 1
0
    def __init__(
        self,
        master_key_provider,
        stream_to_encrypt,
        max_encryption_size=DEFAULT_MAX_ENCRYPTION_SIZE_SENTINEL,
        encryption_context=None
    ):
        """
        Provides a stream that wraps 'stream_to_encrypt' and allows reading data from the underlying stream
        encrypted under the provided master key.

        :param int max_encryption_size: (optional)
            Max number of bytes able to be encrypted by this StreamEncryptor. The default value differs
            based on the algorithm used. For GCM (the default algorithm) the default value is 2147483647 bytes.
            This is provided mainly for use with authenticated encryption algorithms that require verification
            of an authentication tag upon decryption. Because decrypting using these algorithms will buffer
            the entire payload into memory before returning it, this max_encryption_size provides a sanity
            check against encrypting payloads too large to decrypt. This is possible because encryption does not
            require holding the entire payload in memory.

            The 2147483647 byte limit was chosen because that is the maximum number of bytes that can be encrypted or
            decrypted by the OCI Java SDK.  This is to avoid users accidentally encrypting payloads in Python that
            cannot be decrypted in Java.

            Explicitly passing this value as None will disable the size check and allow encrypting payloads up to
            the maximum size supported by the algorithm.

        :param dict encryption_context: (optional)
            Optional additional data to be provided as input to authenticated encryption
            algorithms. This must be a dict with keys that are strings and values that are strings. Keys may NOT
            match the prefix oci-* as that namespace is reserved for OCI internal keys that may be added to the AAD.
        """
        self._algorithm = DEFAULT_ALGORITHM
        self._primary_master_key = master_key_provider.get_primary_master_key()
        if not self._primary_master_key:
            raise ValueError("master_key_provider must contain a primary master key in order to encrypt data")

        self._data_encryption_key = self._primary_master_key.generate_data_encryption_key(
            self._algorithm
        )
        self._stream_to_encrypt = stream_to_encrypt

        _validate_encryption_context(encryption_context)
        self._encryption_context = encryption_context
        self._encryption_context_bytes = convert_to_bytes(
            convert_encryption_context_to_string(encryption_context)
        )

        self.iv = generate_random_iv(self._algorithm.iv_len)
        cipher = Cipher(
            algorithm=self._algorithm.algorithm(
                self._data_encryption_key.plaintext_key_bytes
            ),
            mode=self._algorithm.mode(self.iv),
            backend=default_backend(),
        )

        # use encryptor directly instead of AESGCM class
        # https://cryptography.io/en/latest/hazmat/primitives/aead/#cryptography.hazmat.primitives.ciphers.aead.AESGCM
        # so that we can optionally stream data during encryption without holding the entire payload in memory
        self.encryptor = cipher.encryptor()

        self._max_encryption_size = None
        if max_encryption_size is DEFAULT_MAX_ENCRYPTION_SIZE_SENTINEL:
            if self._algorithm.mode.name == modes.GCM.name:
                self._max_encryption_size = DEFAULT_MAX_GCM_ENCRYPTION_SIZE
            else:
                raise ValueError("Unrecognized algorithm provided: {}".format(str(self._algorithm)))
        elif max_encryption_size is not None:
            if not isinstance(max_encryption_size, int):
                raise TypeError("argument max_encryption_size must be an integer")

            self._max_encryption_size = max_encryption_size

        self._header_content = None
        self._buffer = b""
        self._bytes_written_total = 0
        self._bytes_written_excluding_header = 0
        self._finalized = False
        self._lock = Lock()
        self._closed = False
Exemplo n.º 2
0
 def get_cipher(self, initialization_vector):
     """Generate cipher for AES algorithm."""
     cipher = Cipher(algorithms.AES(self.encryption_key),
                     modes.CBC(initialization_vector),
                     backend=self.backend)
     return cipher
Exemplo n.º 3
0
    def _parse_signing_key_data(self, data, password):
        from paramiko.transport import Transport
        # We may eventually want this to be usable for other key types, as
        # OpenSSH moves to it, but for now this is just for Ed25519 keys.
        # This format is described here:
        # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
        # The description isn't totally complete, and I had to refer to the
        # source for a full implementation.
        message = Message(data)
        if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC:
            raise SSHException("Invalid key")

        ciphername = message.get_text()
        kdfname = message.get_text()
        kdfoptions = message.get_binary()
        num_keys = message.get_int()

        if kdfname == "none":
            # kdfname of "none" must have an empty kdfoptions, the ciphername
            # must be "none"
            if kdfoptions or ciphername != "none":
                raise SSHException("Invalid key")
        elif kdfname == "bcrypt":
            if not password:
                raise PasswordRequiredException(
                    "Private key file is encrypted")
            kdf = Message(kdfoptions)
            bcrypt_salt = kdf.get_binary()
            bcrypt_rounds = kdf.get_int()
        else:
            raise SSHException("Invalid key")

        if ciphername != "none" and ciphername not in Transport._cipher_info:
            raise SSHException("Invalid key")

        public_keys = []
        for _ in range(num_keys):
            pubkey = Message(message.get_binary())
            if pubkey.get_text() != "ssh-ed25519":
                raise SSHException("Invalid key")
            public_keys.append(pubkey.get_binary())

        private_ciphertext = message.get_binary()
        if ciphername == "none":
            private_data = private_ciphertext
        else:
            cipher = Transport._cipher_info[ciphername]
            key = bcrypt.kdf(
                password=b(password),
                salt=bcrypt_salt,
                desired_key_bytes=cipher["key-size"] + cipher["block-size"],
                rounds=bcrypt_rounds,
                # We can't control how many rounds are on disk, so no sense
                # warning about it.
                ignore_few_rounds=True,
            )
            decryptor = Cipher(cipher["class"](key[:cipher["key-size"]]),
                               cipher["mode"](key[cipher["key-size"]:]),
                               backend=default_backend()).decryptor()
            private_data = (decryptor.update(private_ciphertext) +
                            decryptor.finalize())

        message = Message(unpad(private_data))
        if message.get_int() != message.get_int():
            raise SSHException("Invalid key")

        signing_keys = []
        for i in range(num_keys):
            if message.get_text() != "ssh-ed25519":
                raise SSHException("Invalid key")
            # A copy of the public key, again, ignore.
            public = message.get_binary()
            key_data = message.get_binary()
            # The second half of the key data is yet another copy of the public
            # key...
            signing_key = nacl.signing.SigningKey(key_data[:32])
            # Verify that all the public keys are the same...
            assert (signing_key.verify_key.encode() == public == public_keys[i]
                    == key_data[32:])
            signing_keys.append(signing_key)
            # Comment, ignore.
            message.get_binary()

        if len(signing_keys) != 1:
            raise SSHException("Invalid key")
        return signing_keys[0]
Exemplo n.º 4
0
# exchange, rather than use it directly. The server does this, and returns an encrypted
# message:
data = server.get_encrypted_message()
iv = data["IV"]
ciphertext = data["Ciphertext"]

# Next we use the HKDF function to obtain a shared key, we haven't covered hashing yet,
# so this bit is done for you
if not shared_key: exit()
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

hkdf = HKDF(algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=b'dhexercise',
            backend=default_backend())
aes_key = hkdf.derive(shared_key)

### Task 4 ###
# Use AES CTR mode to decrypt the server's message with the aes_key, this is the same key the server will
# have derived using your shared_key from the exchange
cipher = Cipher(algorithms.AES(aes_key),
                modes.CTR(iv),
                backend=default_backend())
decryptor = cipher.decryptor()
message = decryptor.update(ciphertext) + decryptor.finalize()

if message != None:
    print(message.decode("UTF-8"))
    def decrypt_file(metadata, encryption_material, in_filename,
                     chunk_size=block_size * 4 * 1024, tmp_dir=None):
        """Decrypts a file and stores the output in the temporary directory.

        Args:
            metadata: The file's metadata input.
            encryption_material: The file's encryption material.
            in_filename: The name of the input file.
            chunk_size: The size of read chunks (Default value = block_size * 4 * 1024).
            tmp_dir: Temporary directory to use, optional (Default value = None).

        Returns:
            The decrypted file's location.
        """
        logger = getLogger(__name__)
        use_openssl_only = os.getenv('SF_USE_OPENSSL_ONLY', 'False') == 'True'
        key_base64 = metadata.key
        iv_base64 = metadata.iv
        decoded_key = base64.standard_b64decode(
            encryption_material.query_stage_master_key)
        key_bytes = base64.standard_b64decode(key_base64)
        iv_bytes = base64.standard_b64decode(iv_base64)

        if not use_openssl_only:
            key_cipher = AES.new(key=decoded_key, mode=AES.MODE_ECB)
            file_key = PKCS5_UNPAD(key_cipher.decrypt(key_bytes))
            data_cipher = AES.new(key=file_key, mode=AES.MODE_CBC, IV=iv_bytes)
        else:
            backend = default_backend()
            cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
            decryptor = cipher.decryptor()
            file_key = PKCS5_UNPAD(decryptor.update(key_bytes) + decryptor.finalize())
            cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_bytes), backend=backend)
            decryptor = cipher.decryptor()

        temp_output_fd, temp_output_file = tempfile.mkstemp(
            text=False, dir=tmp_dir,
            prefix=os.path.basename(in_filename) + "#")
        total_file_size = 0
        prev_chunk = None
        logger.debug('encrypted file: %s, tmp file: %s',
                     in_filename, temp_output_file)
        with open(in_filename, 'rb') as infile:
            with os.fdopen(temp_output_fd, 'wb') as outfile:
                while True:
                    chunk = infile.read(chunk_size)
                    if len(chunk) == 0:
                        break
                    total_file_size += len(chunk)
                    if not use_openssl_only:
                        d = data_cipher.decrypt(chunk)
                    else:
                        d = decryptor.update(chunk)
                    outfile.write(d)
                    prev_chunk = d
                if prev_chunk is not None:
                    total_file_size -= PKCS5_OFFSET(prev_chunk)
                if use_openssl_only:
                    outfile.write(decryptor.finalize())
                outfile.truncate(total_file_size)
        return temp_output_file
Exemplo n.º 6
0
 def test_instantiate_with_non_algorithm(self, backend):
     algorithm = object()
     with pytest.raises(TypeError):
         Cipher(algorithm, mode=None, backend=backend)
Exemplo n.º 7
0
def aes128cfb8(shared_secret):
    cipher = Cipher(algorithms.AES(shared_secret),
                    modes.CFB8(shared_secret),
                    backend=default_backend())
    return cipher
Exemplo n.º 8
0
import os
import time
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
backend = default_backend()
key = os.urandom(16)
iv = os.urandom(8)
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend=backend)

encryptor = cipher.encryptor()
start = time.time()
ct = encryptor.update(
    b"aaa secret message jksajrulkejsljk jkjjsewiujhetsh kjhgytuoilkjhgf lkjhgfrtyuiopoi secret message jksajrulkejsljk jkjjsewiujhetsh kjhgytuoilkjhgf lkjhgfrtyuiopoia secret message jksajrulkejsljk jkjjsewiujhetsh kjhgytuoilkjhgf lkjhgfrtyuiopoi secret message jksajrulkejsljk jkjjsewiujhetsh kjhgytuoilkjhgf lkjhgfrtyuiopoi"
) + encryptor.finalize()
end = time.time()
#print ct
print "encryption execution time: " + str(end - start)

decryptor = cipher.decryptor()
start = time.time()
decryptor.update(ct) + decryptor.finalize()
end = time.time()
print "Decryption execution time: " + str(end - start)
Exemplo n.º 9
0
def encrypt(data):
    cipher = Cipher(algorithms.AES(key),
                    modes.CTR(nonce),
                    backend=default_backend()).encryptor()
    return cipher.update(data) + cipher.finalize()
Exemplo n.º 10
0
def _get_aes_cipher(key, iv, tag, mode=modes.GCM):
    mode = mode(iv, tag) if mode == modes.GCM else mode(iv)
    return Cipher(algorithms.AES(key), mode, backend=CRYPTO_BACKEND)
Exemplo n.º 11
0
 def update_aes(self, key):
     self.aes = Cipher(algorithms.AES(key), modes.CBC(self.iv),
                       backend=default_backend())
Exemplo n.º 12
0
 def _aes_decrypt(self, key, iv, encrypted_data):
     cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=_backend)
     aes = cipher.decryptor()
     return self._strip_padding(aes.update(encrypted_data) + aes.finalize())
Exemplo n.º 13
0
def decrypt_aes_128_ecb(ctxt, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    decryptor = cipher.decryptor()
    decrypted_data = decryptor.update(ctxt) + decryptor.finalize()
    message = pkcs7_strip(decrypted_data)
    return message
Exemplo n.º 14
0
def encrypt_aes_128_ecb(msg, key):
    padded_msg = pkcs7_padding(msg, block_size=16)
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    encryptor = cipher.encryptor()
    return encryptor.update(padded_msg) + encryptor.finalize()
Exemplo n.º 15
0
with open(plainTextfile, "rb") as inputFile:
    giveMeBytes = bytearray(inputFile.read())  # <-- byte array of plaintext

#computes hash tag on the plaintext and appends it to byte array
digest = hashes.Hash(hashes.SHA1(), backend)
digest.update(giveMeBytes)
some = digest.finalize()[:16]  #check out
iv = os.urandom(16)

extendedPlainText = giveMeBytes
extendedPlainText.join([extendedPlainText, some])

passDigest = hashes.Hash(hashes.SHA1(), backend)
passDigest.update(passDate)

key = passDigest.finalize()[:16]

padder = padding.PKCS7(128).padder()
paddedArray = padder.update(bytes(extendedPlainText))

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend)
ciphText = cipher.encryptor().update(
    paddedArray) + cipher.encryptor().finalize()

with open(cipherTextfile, "wb") as outputFile:
    out = iv + ciphText
    outputFile.write(out)

inputFile.close()
outputFile.close()
Exemplo n.º 16
0
    def decrypt_with_header(self, username):
        """Decrypt a credential file using the header. It handles 3DES and AES256 algorithms.
        Tries to parse the decrypted object into a plain credential object type. If it fails,
        probably due to an invalid username use to decrypt it, raises an exception.

        :param username: Username to use when decrypting
        :type username: string

        :return: decrypted object
        :rtype: SAPCredv2_Cred_Plain

        :raise SAPCredv2_Decryption_Error: if there's an error decrypting the object
        """

        blob = str(self.cipher)
        header = SAPCredv2_Cred_Cipher(blob)

        # Validate supported version
        if header.version != 1:
            raise SAPCredv2_Decryption_Error("Version not supported")

        # Validate and select proper algorithm
        if header.algorithm == CIPHER_ALGORITHM_3DES:
            algorithm = algorithms.TripleDES
        elif header.algorithm == CIPHER_ALGORITHM_AES256:
            algorithm = algorithms.AES
        else:
            raise SAPCredv2_Decryption_Error("Algorithm not supported")

        def xor(string, start):
            """XOR a given string using a fixed key and a starting number."""
            key = 0x15a4e35
            x = start
            y = ""
            for c in string:
                x *= key
                x += 1
                y += chr(ord(c) ^ (x & 0xff))
            return y

        def derive_key(key, header, salt, username):
            """Derive a key using SAP's algorithm. The key is derived using SHA256 and xor from an
            initial key, a header, salt and username.
            """
            digest = Hash(SHA256(), backend=default_backend())
            digest.update(key)
            digest.update(header)
            digest.update(salt)
            digest.update(xor(username, ord(salt[0])))
            digest.update("" * 0x20)
            hashed = digest.finalize()
            derived_key = xor(hashed, ord(salt[1]))
            return derived_key

        # Derive the key using SAP's algorithm
        key = derive_key(cred_key_fmt, blob[0:4], header.salt, username)

        # Decrypt the cipher text with the derived key and IV
        decryptor = Cipher(algorithm(key),
                           modes.CBC(header.iv),
                           backend=default_backend()).decryptor()
        plain = decryptor.update(header.cipher_text) + decryptor.finalize()

        # Perform a final xor over the decrypted content with a fixed key
        plain = xor(plain, 0x64FB914E)

        return SAPCredv2_Cred_Plain(plain)
Exemplo n.º 17
0
 def test_creates_decryptor(self, backend):
     cipher = Cipher(algorithms.AES(binascii.unhexlify(b"0" * 32)),
                     modes.CBC(binascii.unhexlify(b"0" * 32)), backend)
     assert isinstance(cipher.decryptor(), base.CipherContext)
Exemplo n.º 18
0
 def __init__(self, rc4key):
     self.rc4key = rc4key
     self.txcrypt = Cipher(ARC4(rc4key), mode=None, backend=default_backend()).encryptor()
     self.rxcrypt = Cipher(ARC4(rc4key), mode=None, backend=default_backend()).decryptor()
Exemplo n.º 19
0
 def __init__(self, key):
     """Initialize a new AESCipher."""
     self.block_size = 16
     self.cipher = Cipher(algorithms.AES(key), modes.ECB(),
                          default_backend())
Exemplo n.º 20
0
def Mydecrypt(ciphertext, tag, iv, key):
    decryptor = Cipher(algorithms.AES(key),
                       modes.GCM(iv, tag),
                       backend=default_backend()).decryptor()

    return decryptor.update(ciphertext) + decryptor.finalize()
Exemplo n.º 21
0
def _mysql_aes_engine(key):
    """Create MYSQL AES cipher engine."""
    return Cipher(algorithms.AES(key), modes.ECB(), default_backend())
Exemplo n.º 22
0
def aes_ecb_decrypt(key: Key, cipher_text: bytes) -> bytes:
	decryptor = Cipher(algorithms.AES(key=key.key_bytes), ECB(), default_backend()).decryptor()  # type: CipherContext
	decryptor.update(cipher_text)
	return decryptor.finalize()
Exemplo n.º 23
0
key = b'-8B key-'
nonce = Random.new().read(DES.block_size / 2)
ctr = Counter.new(DES.block_size * 8 / 2, prefix=nonce)
##Warn: B304
cipher = DES.new(key, DES.MODE_CTR, counter=ctr)
plaintext = b'We are no longer the knights who say ni!'
msg = nonce + cipher.encrypt(plaintext)

key = b'Super secret key'
##Warn: B304
cipher = XOR.new(key)
plaintext = b'Encrypt me'
msg = cipher.encrypt(plaintext)

##Warn: B304
cipher = Cipher(algorithms.ARC4(key), mode=None, backend=default_backend())
encryptor = cipher.encryptor()
ct = encryptor.update(b"a secret message")

##Warn: B304
cipher = Cipher(algorithms.Blowfish(key), mode=None, backend=default_backend())
encryptor = cipher.encryptor()
ct = encryptor.update(b"a secret message")

##Warn: B304
cipher = Cipher(algorithms.IDEA(key), mode=None, backend=default_backend())
encryptor = cipher.encryptor()
ct = encryptor.update(b"a secret message")

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 31137))
Exemplo n.º 24
0
def aes256ctr(iv: IV, key: SecretKey, data: bytes) -> bytes:
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend = default_backend())
    encryptor = cipher.encryptor()
    return encryptor.update(data) + encryptor.finalize()
    def encrypt_file(encryption_material, in_filename,
                     chunk_size=block_size * 4 * 1024, tmp_dir=None):
        """Encrypts a file in a temporary directory.

        Args:
            encryption_material: The encryption material for file.
            in_filename: The input file's name.
            chunk_size: The size of read chunks (Default value = block_size * 4 * 1024).
            tmp_dir: Temporary directory to use, optional (Default value = None).

        Returns:
            The encrypted file's location.
        """
        logger = getLogger(__name__)
        use_openssl_only = os.getenv('SF_USE_OPENSSL_ONLY', 'False') == 'True'
        decoded_key = base64.standard_b64decode(
            encryption_material.query_stage_master_key)
        key_size = len(decoded_key)
        logger.debug('key_size = %s', key_size)

        # Generate key for data encryption
        iv_data = SnowflakeEncryptionUtil.get_secure_random(block_size)
        file_key = SnowflakeEncryptionUtil.get_secure_random(key_size)
        if not use_openssl_only:
            data_cipher = AES.new(key=file_key, mode=AES.MODE_CBC, IV=iv_data)
        else:
            backend = default_backend()
            cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_data), backend=backend)
            encryptor = cipher.encryptor()

        temp_output_fd, temp_output_file = tempfile.mkstemp(
            text=False, dir=tmp_dir,
            prefix=os.path.basename(in_filename) + "#")
        padded = False
        logger.debug('unencrypted file: %s, temp file: %s, tmp_dir: %s',
                     in_filename, temp_output_file, tmp_dir)
        with open(in_filename, 'rb') as infile:
            with os.fdopen(temp_output_fd, 'wb') as outfile:
                while True:
                    chunk = infile.read(chunk_size)
                    if len(chunk) == 0:
                        break
                    elif len(chunk) % block_size != 0:
                        chunk = PKCS5_PAD(chunk, block_size)
                        padded = True
                    if not use_openssl_only:
                        outfile.write(data_cipher.encrypt(chunk))
                    else:
                        outfile.write(encryptor.update(chunk))
                if not padded:
                    if not use_openssl_only:
                        outfile.write(data_cipher.encrypt(
                            block_size * chr(block_size).encode(UTF8)))
                    else:
                        outfile.write(encryptor.update(
                            block_size * chr(block_size).encode(UTF8)))
                if use_openssl_only:
                    outfile.write(encryptor.finalize())

        # encrypt key with QRMK
        if not use_openssl_only:
            key_cipher = AES.new(key=decoded_key, mode=AES.MODE_ECB)
            enc_kek = key_cipher.encrypt(PKCS5_PAD(file_key, block_size))
        else:
            cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
            encryptor = cipher.encryptor()
            enc_kek = encryptor.update(PKCS5_PAD(file_key, block_size)) + encryptor.finalize()

        mat_desc = MaterialDescriptor(
            smk_id=encryption_material.smk_id,
            query_id=encryption_material.query_id,
            key_size=key_size * 8)
        metadata = EncryptionMetadata(
            key=base64.b64encode(enc_kek).decode('utf-8'),
            iv=base64.b64encode(iv_data).decode('utf-8'),
            matdesc=matdesc_to_unicode(mat_desc),
        )
        return metadata, temp_output_file
Exemplo n.º 26
0
def encryptAES(data, key, iv):
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), default_backend())
    encryptor = cipher.encryptor()
    encData = encryptor.update(data) + encryptor.finalize()
    return encData
Exemplo n.º 27
0
# NEVER USE ECB is not secure

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os

key = os.urandom(16)  # 16 bytes -> 128 bits: AES-128

aesCipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())

aesEncryptor = aesCipher.encryptor()
aesDecryptor = aesCipher.decryptor()
Exemplo n.º 28
0
def decryptAES(ciphertext, key, iv):
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), default_backend())
    decryptor = cipher.decryptor()
    return decryptor.update(ciphertext) + decryptor.finalize()
Exemplo n.º 29
0
def decrypt(key, iv, associated_data, ciphertext, tag):
    decryptor = Cipher(algorithms.AES(key),
                       modes.GCM(iv, tag),
                       backend=default_backend()).decryptor()
    decryptor.authenticate_additional_data(associated_data)
    return decryptor.update(ciphertext) + decryptor.finalize()
Exemplo n.º 30
0
def cryptography_symmetric_encrypt(encrypt_key, plaintext):
    """
    Encrypt the provided plaintext using AES encryption.

    NOTE 1: This function return a string which is fully compatible with Keyczar.Encrypt() method.

    NOTE 2: This function is loosely based on keyczar AESKey.Encrypt() (Apache 2.0 license).

    The final encrypted string value consists of:

    [message bytes][HMAC signature bytes for the message] where message consists of
    [keyczar header plaintext][IV bytes][ciphertext bytes]

    NOTE: Header itself is unused, but it's added so the format is compatible with keyczar format.

    """
    assert isinstance(encrypt_key,
                      AESKey), 'encrypt_key needs to be AESKey class instance'
    assert isinstance(plaintext, (six.text_type, six.string_types, six.binary_type)), \
        'plaintext needs to either be a string/unicode or bytes'

    aes_key_bytes = encrypt_key.aes_key_bytes
    hmac_key_bytes = encrypt_key.hmac_key_bytes

    assert isinstance(aes_key_bytes, six.binary_type)
    assert isinstance(hmac_key_bytes, six.binary_type)

    # Pad data
    data = pkcs5_pad(plaintext)

    # Generate IV
    iv_bytes = os.urandom(KEYCZAR_AES_BLOCK_SIZE)

    backend = default_backend()
    cipher = Cipher(algorithms.AES(aes_key_bytes),
                    modes.CBC(iv_bytes),
                    backend=backend)
    encryptor = cipher.encryptor()

    # NOTE: We don't care about actual Keyczar header value, we only care about the length (5
    # 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

    # Generate HMAC signature for the message (header + IV + ciphertext)
    h = hmac.HMAC(hmac_key_bytes, hashes.SHA1(), backend=backend)
    h.update(msg_bytes)
    sig_bytes = h.finalize()

    result = msg_bytes + sig_bytes

    # Convert resulting byte string to hex notation ASCII string
    result = binascii.hexlify(result).upper()

    return result