Symmetric Encryption

Excerpt from Symmetric-key algorithm on Wikipedia:

Symmetric-key algorithms are algorithms for cryptography that use the same cryptographic keys for both the encryption of plaintext and the decryption of ciphertext. The keys may be identical, or there may be a simple transformation to go between the two keys.

The keys, in practice, represent a shared secret between two or more parties that can be used to maintain a private information link. The requirement that both parties have access to the secret key is one of the main drawbacks of symmetric-key encryption, in comparison to public-key encryption (also known as asymmetric-key encryption).

However, symmetric-key encryption algorithms are usually better for bulk encryption. Except for the one-time pad, they have a smaller key size, which means less storage space and faster transmission. Due to this, asymmetric-key encryption is often used to exchange the secret key for symmetric-key encryption.

AES Encryption

The classic encryption from the early 2000’s, established by US NIST in 2001.

Excerpt from Advanced Encryption Standard on Wikipedia.:

AES is a variant of the Rijndael block cipher developed by two Belgian cryptographers, Joan Daemen and Vincent Rijmen, who submitted a proposal to NIST during the AES > selection process. Rijndael is a family of ciphers with different key and block sizes. For AES, NIST selected three members of the Rijndael family, each with a block size > of 128 bits, but three different key lengths: 128, 192 and 256 bits.

AES has been adopted by the U.S. government. It supersedes the Data Encryption Standard (DES), which was published in 1977. The algorithm described by AES is a > symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data.

In the United States, AES was announced by the NIST as U.S. FIPS PUB 197 (FIPS 197) on November 26, 2001. This announcement followed a five-year standardization process in > which fifteen competing designs were presented and evaluated, before the Rijndael cipher was selected as the most suitable.

AES is included in the ISO/IEC 18033-3 standard. AES became effective as a U.S. federal government standard on May 26, 2002, after approval by U.S. Secretary of Commerce > Donald Evans. AES is available in many different encryption packages, and is the first (and only) publicly accessible cipher approved by the U.S. National Security Agency > (NSA) for top secret information when used in an NSA approved cryptographic module.

import random
import string
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend
import os
import time
import statistics
import pandas as pd
from lib.util.DataGenerator import DataGenerator


class SymmetricEncrypter_AES:
    def __init__(self):
        pass

    def derive_key(self, password: str, salt: bytes) -> bytes:
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,  # AES-256 requires a 256-bit (32-byte) key
            salt=salt,
            iterations=100000,
            backend=default_backend(),
        )
        return kdf.derive(password.encode())

    def encrypt(self, data: str, password: str) -> (bytes, bytes, bytes):
        salt = os.urandom(16)  # Generate a random salt
        key = self.derive_key(password, salt)  # Derive the key
        iv = os.urandom(16)  # AES requires a 16-byte IV

        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        padder = PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
        ciphertext = encryptor.update(padded_data) + encryptor.finalize()

        return ciphertext, salt, iv

    def decrypt(self, ciphertext: bytes, password: str, salt: bytes, iv: bytes) -> str:
        key = self.derive_key(password, salt)
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        decryptor = cipher.decryptor()

        padded_data = decryptor.update(ciphertext) + decryptor.finalize()
        unpadder = PKCS7(algorithms.AES.block_size).unpadder()
        data = unpadder.update(padded_data) + unpadder.finalize()

        return data.decode()


def run_standard_test():
    encrypter = SymmetricEncrypter_AES()
    key_string = "strong password"

    test_sizes = [1000, 10000, 100000, 1000000, 5000000, 10000000, 20000000]
    epochs = 3

    results = []

    for size in test_sizes:
        data = DataGenerator.generate_ascii_text_data(size, charToSymbolRatio=0.05)

        encrypt_times = []
        decrypt_times = []

        for epoch in range(epochs):
            start_time = time.time()
            ciphertext, salt, iv = encrypter.encrypt(data, key_string)
            encrypt_time = time.time() - start_time
            encrypt_times.append(encrypt_time)

            start_time = time.time()
            decrypted_data = encrypter.decrypt(ciphertext, key_string, salt, iv)
            decrypt_time = time.time() - start_time
            decrypt_times.append(decrypt_time)

        avg_encrypt = statistics.mean(encrypt_times)
        avg_decrypt = statistics.mean(decrypt_times)

        results.append(
            {
                "Character Size": f"{size / 1000} KChars",
                "Byte Size": f"{size / 1_000_000} MB",
                "Avg. Encryption Time (s)": f"{avg_encrypt:.4f}",
                "Encryption Speed (chars/s)": f"{size / avg_encrypt:.0f}",
                "Avg. Decryption Time (s)": f"{avg_decrypt:.4f}",
                "Decryption Speed (chars/s)": f"{size / avg_decrypt:.0f}",
                "Encryption Speed": f"{(size / 1_000_000) / avg_encrypt:.2f} MB/s",
                "Decryption Speed": f"{(size / 1_000_000) / avg_decrypt:.2f} MB/s",
            }
        )

    df = pd.DataFrame(results)
    display(df)


run_standard_test()
[LOG]: Generated 1000 chars; took 0ms | charToSymbolRatio: 0.05
[LOG]: Generated 10000 chars; took 3ms | charToSymbolRatio: 0.05
[LOG]: Generated 100000 chars; took 28ms | charToSymbolRatio: 0.05
[LOG]: Generated 1000000 chars; took 337ms | charToSymbolRatio: 0.05
[LOG]: Generated 5000000 chars; took 1949ms | charToSymbolRatio: 0.05
[LOG]: Generated 10000000 chars; took 3841ms | charToSymbolRatio: 0.05
[LOG]: Generated 20000000 chars; took 7702ms | charToSymbolRatio: 0.05
Character Size Byte Size Avg. Encryption Time (s) Encryption Speed (chars/s) Avg. Decryption Time (s) Decryption Speed (chars/s) Encryption Speed Decryption Speed
0 1.0 KChars 0.001 MB 0.0259 38556 0.0242 41269 0.04 MB/s 0.04 MB/s
1 10.0 KChars 0.01 MB 0.0267 374379 0.0270 369771 0.37 MB/s 0.37 MB/s
2 100.0 KChars 0.1 MB 0.0259 3860109 0.0266 3755477 3.86 MB/s 3.76 MB/s
3 1000.0 KChars 1.0 MB 0.0263 38028971 0.0251 39860464 38.03 MB/s 39.86 MB/s
4 5000.0 KChars 5.0 MB 0.0330 151642640 0.0299 167231223 151.64 MB/s 167.23 MB/s
5 10000.0 KChars 10.0 MB 0.0361 277089516 0.0290 345166646 277.09 MB/s 345.17 MB/s
6 20000.0 KChars 20.0 MB 0.0527 379501183 0.0403 496082603 379.50 MB/s 496.08 MB/s

pyca/cryptography is written in Rust and is reaping the benefits of modern computation patterns, as compared to C, the git-repo contains 30.1% Rust; of which they introduced in 2020, or cryptography 3.4 to improve security and performance of the module.

Composable Encryption Tools

Using the power of Python, we can add some intelligent composition tools here to manage our encryption algorithms. Within ./lib, we have some tools to create streamline encryption harnesses.

from lib.EncryptionAlgorithm import EncryptionAlgorithm
Back to top