Symmetric cryptography

The symmetric module provides the function to perform encryption and decryption operation and also MACs. Naturally, the functions with the suffix _encrypt are for encryption and _decrypt for decryption. The underlying block cipher used is Advanced Encryption Standard (AES).

There are also lower level primitives such as XOR, pad and unpad.

Finally, there is a hash function sha256_hash, and functions create_mac and verify_mac for creating and verifying Message Authentication Codes (MACs), respectively.

pv080_crypto.symmetric.pad(data: bytes, block_size: int) bytes[source]

Appends PKCS7 padding to data so that its size is a multiple of block_size.

Parameters:
  • data – The unpadded data which we want to pad.

  • block_size – The block size in bits, usually given by the cipher that we want to use.

Returns:

The padded data.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_ecb_encrypt(key, padded_pt)
pv080_crypto.symmetric.unpad(padded_data: bytes, block_size: int) bytes[source]

Removes PKCS7 padding from padded_data.

Parameters:
  • padded_data – The padded data which we want to unpad.

  • block_size – The block size in bits, usually given by the cipher that we want to use.

Returns:

The unpadded data.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_ecb_encrypt(key, padded_pt)
>>> decrypted = aes_ecb_decrypt(key, ct)
>>> assert unpad(decrypted, 128) == pt
pv080_crypto.symmetric.aes_ecb_encrypt(key: bytes, padded_plaintext: bytes) bytes[source]

Encrypts padded_plaintext with key using AES-ECB.

Parameters:
  • key – The encryption key of size 128, 192, or 256 bits.

  • padded_plaintext – The bytes to be encrypted (must be padded to 128 bits).

Returns:

The bytes of the encrypted ciphertext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_ecb_encrypt(key, padded_pt)
pv080_crypto.symmetric.aes_ecb_decrypt(key: bytes, ciphertext: bytes) bytes[source]

Decrypts ciphertext with key using AES-ECB.

Parameters:
  • key – The encryption key of size 128, 192, or 256 bits.

  • ciphertext – The bytes to be decrypted.

Returns:

The bytes of the padded plaintext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_ecb_encrypt(key, padded_pt)
>>> decrypted = aes_ecb_decrypt(key, ct)
>>> assert unpad(decrypted, 128) == pt
pv080_crypto.symmetric.aes_cbc_encrypt(key: bytes, iv: bytes, padded_plaintext: bytes) bytes[source]

Encrypts padded_plaintext with key using AES-CBC and iv.

Parameters:
  • key – The encryption key of size 128, 192, or 256 bits.

  • iv – The initialization vector (must be of size 128 bits).

  • padded_plaintext – The bytes to be encrypted (must be padded to 128 bits).

Returns:

The bytes of the encrypted ciphertext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> iv = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_cbc_encrypt(key, iv, padded_pt)
pv080_crypto.symmetric.aes_cbc_decrypt(key: bytes, iv: bytes, ciphertext: bytes) bytes[source]

Decrypts ciphertext with key using AES-CBC and iv.

Parameters:
  • key – The encryption key of size 128, 192, or 256 bits.

  • iv – The initialization vector (must be of size 128 bits).

  • ciphertext – The bytes to be decrypted.

Returns:

The bytes of the padded plaintext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> iv = secrets.token_bytes(16)
>>> pt = b'message'
>>> padded_pt = pad(pt, 128)
>>> ct = aes_cbc_encrypt(key, iv, padded_pt)
>>> decrypted = aes_cbc_decrypt(key, iv, ct)
>>> assert unpad(decrypted, 128) == pt
pv080_crypto.symmetric.XOR(array1: bytes, array2: bytes) bytes[source]

Performs the bitwise XOR operation on two given bytestrings.

Parameters:
  • array1 – The first operand.

  • array2 – The second operand.

Returns:

The bitwise XOR of array1 and array2.

Example:

>>>
>>> xor = XOR(bytes.fromhex('01ff'), bytes.fromhex('03fe'))
>>> assert xor == bytes.fromhex('0201')
pv080_crypto.symmetric.chacha20_encrypt(key: bytes, nonce: bytes, plaintext: bytes) bytes[source]

Use ChaCha20 to encrypt plaintext using key and nonce.

Parameters:
  • key – The bytes of the key (size 32 bytes).

  • nonce – The bytes of the nonce (size 16 bytes).

Returns:

The bytes of the ciphertext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(32)
>>> nonce = secrets.token_bytes(16)
>>> ciphertext = chacha20_encrypt(key=key, nonce=nonce, plaintext=b"message")
pv080_crypto.symmetric.chacha20_decrypt(key: bytes, nonce: bytes, ciphertext: bytes) bytes[source]

Use ChaCha20 to decrypt ciphertext using key and nonce.

Parameters:
  • key – The bytes of the key (size 32 bytes).

  • nonce – The bytes of the nonce (size 16 bytes).

Returns:

The bytes of the plaintext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(32)
>>> nonce = secrets.token_bytes(16)
>>> message = b"hello world"
>>> ciphertext = chacha20_encrypt(key=key, nonce=nonce, plaintext=message)
>>> assert message == chacha20_decrypt(key=key, nonce=nonce, ciphertext=ciphertext)
pv080_crypto.symmetric.aes_encrypt(key: bytes, plaintext: bytes) bytes[source]

Use AES-CBC to encrypt plaintext using key. The function also appends PKCS#7 padding and prepends a random IV to the ciphertext.

Parameters:
  • key – The bytes of the key (16, 24, or 32 bytes in size).

  • plaintext – The plaintext bytes to be encrypted.

Returns:

The bytes of the ciphertext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> ciphertext = aes_encrypt(key=key, plaintext=b"my message")
pv080_crypto.symmetric.aes_decrypt(key: bytes, ciphertext: bytes) bytes[source]

Use AES-CBC to decrypt ciphertext using key. The ciphertext should be encrypted using aes_encrypt.

Parameters:
  • key – The bytes of the key (16, 24, or 32 bytes).

  • ciphertext – The ciphertext bytes to be decrypted.

Returns:

The bytes of the plaintext.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(16)
>>> message = b"hello world"
>>> ciphertext = aes_encrypt(key=key, plaintext=message)
>>> assert message == aes_decrypt(key=key, ciphertext=ciphertext)
pv080_crypto.symmetric.sha256_hash(data: bytes) bytes[source]

Compute the SHA256 hash of the given data.

Parameters:

data – The bytes of the data to hash.

Returns:

The digest of the data (32 bytes).

Example:

>>>
>>> message = b"Hello world"
>>> digest = sha256_hash(message)
>>> print(digest.hex(sep=' '))
'64 ec 88 ca 00 b2 68 e5 ba 1a 35 67 8a 1b 53 16 d2 12 f4 f3 66 b2 47 72 32 53 4a 8a ec a3 7f 3c'
pv080_crypto.symmetric.create_mac(key: bytes, data: bytes) bytes[source]

Calculate Message Authentication Code of data (using AES-CBC), i.e. encrypt data using key and AES-CBC with initialization vector equal to zero bytes.

Parameters:
  • key – The bytes of the key (16, 24, or 32 bytes).

  • data – The data that will be MAC’d.

Returns:

The MAC value (16 bytes).

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(32)
>>> mac = create_mac(key=key, data=b"some data to MAC")
pv080_crypto.symmetric.verify_mac(key: bytes, data: bytes, mac: bytes) bool[source]

Verify that the MAC (using AES-CBC) of data matches mac.

Parameters:
  • key – The bytes of the key (16, 24, or 32 bytes).

  • data – The data that will be MAC’ed.

  • mac – The value against which we verify (16 bytes).

Returns:

True if the verification succeeds, False otherwise.

Example:

>>>
>>> import secrets
>>> key = secrets.token_bytes(32)
>>> data=b"some data to MAC"
>>> mac = create_mac(key=key, data=data)
>>> assert verify_mac(key=key, data=data, mac=mac)